在设计数据库时,存储多个真/假值的首选方法是什么?

时间:2010-02-04 13:46:20

标签: sql-server oracle database-design query-optimization

如标题中所述,在设计数据库时,处理具有多个列的表的首选方法是什么,这些列仅将真值/假值存储为单个值或值(例如“Y / N:或”0) / 1“)?同样,在不同的数据库(例如Oracle和SQL Server)之间可能会出现一些可能影响列处理方式的问题吗?

10 个答案:

答案 0 :(得分:14)

SQL Server中,有BIT数据类型。您可以在那里存储0或1,比较值但不运行MINMAX

Oracle中,您只需使用NUMBERCHAR(1)

MySQLPostgreSQL中,任何数据类型都可以隐式转换为BOOLEAN

两个系统都支持BOOLEAN数据类型,您可以在WHEREON子句中使用,而不使用运算符:

SELECT  *
FROM    mytable
WHERE   col1

,这在SQL ServerOracle中是不可能的(你需要在那里有某种或谓词)。

MySQL中,BOOLEANTINYINT(1)的同义词。

PostgreSQL中(就存储而言),但从逻辑上讲,它不能隐式转换为任何其他类型。

答案 1 :(得分:5)

根据我自己的经验,我更喜欢char(1)代表'Y'或'N'。使用0和1可能会有点混乱,这取决于我已经喝了多少啤酒,C ++ main()函数在成功时返回0。 ENUM和BIT类型比它们的价值更麻烦。

值得注意的是,MySQL information_schema使用VARCHAR(3)表示“是”或“否”。

示例:

information_schema.USER_PRIVILEGES (
  ...
  IS_GRANTABLE VARCHAR(3) NOT NULL DEFAULT ''
) 

答案 2 :(得分:4)

您可能需要考虑另一种用于存储布尔值的数据模型,而不是布尔数据类型,这在以下情况下可能特别合适:

  • 当你有很多是/否列时。
  • 当您可能需要在将来添加更多是/否列时。
  • 当yes / no值不经常变化时。

定义用户权限可能是上述的典型示例。请考虑以下表格:

Table "Users":             (user_id, name, surname, country)

Table "Permissions":       (permission_id, permission_text)

Table "Users_Permissions": (user_id, permission_id)

Permissions表中,您将定义可能适用于用户的所有可能权限。对于每个yes / no属性,您必须在Permissions表中添加一行。您可能已经注意到,这使得以后添加新权限变得非常容易,而无需修改数据库模式。

使用上述模型,您可以通过在user_id表中为permission_id分配Users_Permissions来指示TRUE值。否则默认为FALSE。

例如:

Table "Permissions"

permission_id   text
-----------------------------------
1               "Read Questions"
2               "Answer Questions"
3               "Edit Questions"
4               "Close Questions"


Table "Users_Permissions"

user_id         permission_id
-----------------------------------
1               1
1               2
1               3
2               1
2               3

<强>优点

  • 建立索引:您可以轻松使用索引查询特定事实。
  • 空格:当您有许多错误值时,默认约定会节省空间。
  • 规范化:事实在自己的表格中定义(在PermissionsUsers_Permissions表格中。)您可以轻松地存储有关每个事实的更多信息。

<强>缺点

  • 查询:简单查询需要JOIN。
  • 设置为False :要将值设置为false,您必须删除一行(来自Users_Permissions表。)否则,您可以使用“已删除”标记Users_Permissions表,它还允许您存储审计跟踪的信息,例如修改权限和由谁修改。如果删除该行,则无法存储此信息。

答案 3 :(得分:2)

对您正在使用的特定数据库引擎使用任何有意义的内容。它是需要处理它的数据库的接口。如果数据库的代码端接口已经充分模块化,那么它只不过是一个简单的单行更改来处理底层数据库中的不同布尔类型。

答案 4 :(得分:2)

如果您的DBMS支持布尔数据类型,如MySQL,请使用它。如果没有,就像Oracle一样,我通常使用值为Y或N的char(1)。在后一种情况下,最好编写几个函数来将Java或C ++或任何布尔类型转换为从Y / N,所以你避免使用冗余代码来做到这一点。这是一个非常简单的功能,但是它必须处理诸如空值或除Y或N以外的值之类的情况,并且您希望始终如一地执行此操作。

我肯定不会通过位操作将标志打包到单个变量中。是的,这将节省一些磁盘空间,但价格复杂性和错误机会要大得多。如果你的DBMS不支持位操作 - 而且我从来没有想要做这样的事情,我不知道我的头顶哪个,如果有的话 - 那你就会有基于这样的标志进行选择或排序真的很困难。当然,您可以检索满足其他条件的所有记录,然后让调用代码清除具有正确标志值的记录。但是,如果只有一小部分记录具有所需的标志值,并且您有一个连接许多其他记录的查询,该怎么办?就像“select employee.name,sum(pay.amount)from employee join pay with using(employee_id)where employee.executive = true and pay.bonus = true”。使用where子句,您可能会检索非常适度的记录。没有它,您将检索整个数据库。

这些天磁盘空间很便宜,因此任何磁盘节省都可能不重要。如果你真的有大量的标志 - 每条记录有数百或数千个标志 - 我想可能有一个包装它们的情况。但那将是我的设计选择列表。

编辑:让我详细说明编写一个类来将“SQL boolean”转换为“Java boolean”。这同样适用于任何语言,但我将使用Java作为示例。

如果你的DBMS有一个内置的布尔类型,那么使用Java你可以直接将它读入带有ResultSet.getBoolean()的布尔变量。

但是如果你必须将它存储为char“Y”或“N”,那么你必须把它读成一个字符串。因此,声明一个这样的类是有道理的:

class MyBoolean
{
  boolean value;
  final static MyBoolean TRUE=new MyBoolean(true), FALSE=new MyBoolean(false);
  public MyBoolean(boolean b)
  {
    value=b;
  }
  public MyBoolean(String s)
  {
    if (s==null)
      return null;
    else if (s.equals("Y"))
      return MyBoolean.TRUE;
    else
      return MyBoolean.FALSE;
  }
  public static String toString(MyBoolean b)
  {
    if (b==null)
      return null;
    else if (b.value)
      return "Y";
    else
      reutrn "N";
  }
  public String toString()
  {
    return toString(this);
  }
}

然后你可以使用“MyBoolean flag = new MyBoolean(rs.getString(”flag“))轻松地从数据库中获取布尔值;”并使用“rs.setString(”flag“,flag.toString())写入数据库;”

当然,如果你有其他需要做的布尔值,你可以在课堂上添加你需要的任何其他逻辑。如果出于某些目的,你想将布尔显示为T / F或是/否或开/关等等,你可以添加备用toString变体 - toTFString或toString(value,truetext,falsetext)或其他 - 而不是写类似的代码一遍又一遍。

答案 5 :(得分:2)

我认为“Y / N”值比“1/0”更有意义。使用Oracle,我将执行以下操作以使数据库引擎尽可能多地验证数据:

  • 将列定义为char(1)
  • 添加可能的检查约束 值被限制为“in('Y', 'N')
  • 如果符合业务规则, 让它们不可空 - 这可以 隐含地防止问题 假设任何不是'Y'的东西 在SQL
  • 中的值为“N”

答案 6 :(得分:1)

我建议您创建另一个表,而不是添加列。听我说...

假设您有一个名为Customer的表:

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100)
)

现在,假设您想要指明是否允许客户显示在搜索结果中。一种选择是添加一些代表两种可能状态之一的列:

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100),
  Searchable BOOLEAN /* or CHAR(1) or BIT... */
)

您的搜索查询将如下所示:

SELECT CustomerID, Name
  FROM Customer
 WHERE Name LIKE '%TheCustomerNameIAmLookingFor%'
   AND Searchable = TRUE /* or 'Y' or 0... */

这很简单。这个帖子中的很多人都在为这个列选择哪种数据类型提供了很好的建议,以便在各种数据库中很好地发挥语法。

替代方案:创建单独的表

我将创建一个单独的表来存储每个可搜索客户的Customer,而不是向CustomerID添加另一列。

CREATE TABLE Customer
(
  CustomerID NUMBER,
  Name       VARCHAR(100)
)

CREATE TABLE SearchableCustomer
(
  CustomerID NUMBER
)

在这种情况下,如果CustomerID表中存在SearchableCustomer,则客户被视为可搜索。现在,搜索客户的查询变为:

SELECT CustomerID, Name
  FROM Customer
 WHERE Name LIKE '%TheCustomerNameIAmLookingFor%'
   AND CustomerID IN (SELECT CustomerID FROM SearchableCustomer)

你会发现这个策略在RDBMS之间是可以转移的:

  • 查找可搜索的客户使用IN子句或JOIN
  • 让客户可搜索使用INSERT声明
  • 使客户不可搜索使用DELETE声明

惊喜福利

如果您将SearchableCustomer视图而不是表格,则可以根据需要自由定义可搜索的客户:

CREATE VIEW SearchableCustomer AS
SELECT CustomerID
  FROM Customer
 WHERE Name LIKE 'S%' /* For some reason, management only cares about customers whose name starts with 'S' */

您的搜索查询根本不会改变! :D根据我的经验,这导致了巨大的灵活性。

答案 7 :(得分:0)

位列通常用于表示T / F或Y / N类型值,至少在SQL Server中是这样。虽然数据库纯粹主义者可能会告诉您Bit列在数据库中没有位置,因为它们“太靠近硬件” - Joe Celko。

答案 8 :(得分:0)

“SELECT * 来自mytable colhere col1

,这在SQL Server和Oracle中是不可能的(你需要在那里有某种类型或谓词)。“

这只是为了表明Oracle和SQL服务器确实是一个荒谬可笑的憎恶。

如果声明col1是BOOLEAN类型,则表达式“col1” IS 是谓词。

如果WHERE子句的语义要求其表达式只是求值为真值,并且某些列被声明为“真值”类型,那么应该允许和支持“WHERE that-c​​olumn” 。期。任何系统不仅仅会使其作者暴露出无能为力的平庸庸俗。

答案 9 :(得分:-1)

我通常会在没有BIT / BOOLEAN值的情况下执行此操作。相反,我有三张桌子。假设我们有一个项目管理系统,它有项目,这些项目有很多属性。

然后我们有表格:

Project
 - Project_ID (INT),
 - Name (VARCHAR)

Attribute
 - Attribute_ID (INT),
 - Name (VARCHAR)

ProjectAttribute_Rel
 - Project_ID (INT),
 - Attribute_ID (INT)

项目的属性是真还是假取决于ProjectAttribute_Rel中是否有一行。

通常,您将在代码中处理Attribute_ID,因此当您阅读项目的属性(您可能在其中具有Project_ID)时,您就是这样做的(PHP任意用作示例):

$arrAttributes = array();
$oQuery = mysql_query('
    SELECT Attribute_ID
    FROM ProjectAttribute_Rel
    WHERE Project_ID = '.addslashes($iProjectId).'
');
while ($rowAttribute = mysql_fetch_assoc($oQuery)) {
    $arrAttributes[] = $rowAttribute['Attribute_ID'];
}

此时,您可以通过检查项目属性是否存在于$ arrAttributes中来检查项目的属性是否为真。在PHP中,这将是:

if (in_array($arrAttributes, $iAttributeId)) {
    // Project attribute is true!
}

此方法还允许您执行所有排序特技以避免在更新时列出大量属性,再次选择时(因为SELECT *代码中的代码不好),插入时等等。这是因为您始终可以遍历表Attribute以查找可用属性,因此如果您添加一个并且您以这种方式执行操作,则添加/编辑/删除属性是微不足道的。赔率是你的SQL甚至不需要改变,因为属性本​​身是在数据库中定义的,而不是在代码中定义的。

希望这有帮助。