在设计数据库时,存储多个真/假值的首选方式是啥?
Posted
技术标签:
【中文标题】在设计数据库时,存储多个真/假值的首选方式是啥?【英文标题】:When designing databases, what is the preferred way to store multiple true / false values?在设计数据库时,存储多个真/假值的首选方式是什么? 【发布时间】:2011-01-13 02:12:09 【问题描述】:如标题中所述,在设计数据库时,处理具有多个列的表的首选方法是什么/1")?同样,在不同的数据库(例如 Oracle 和 SQL Server)之间是否可能会出现一些问题,这些问题可能会影响列的处理方式?
【问题讨论】:
【参考方案1】:在SQL Server
中,有BIT
数据类型。您可以在那里存储 0 或 1,比较值但不能运行 MIN
或 MAX
。
在Oracle
中,您只需使用NUMBER
或CHAR(1)
。
在mysql
和PostgreSQL
中,任何数据类型都可以隐式转换为BOOLEAN
。
两个系统都支持BOOLEAN
数据类型,您可以在WHERE
或ON
子句中按原样使用,无需运算符:
SELECT *
FROM mytable
WHERE col1
,这在 SQL Server
和 Oracle
中是不可能的(你需要在那里有某种类型或谓词)。
在MySQL
中,BOOLEAN
是TINYINT(1)
的同义词。
在PostgreSQL
中也是如此(就存储而言),但从逻辑上讲,它不能隐式转换为任何其他类型。
【讨论】:
是否存在与拥有多个字段有关的任何设计问题,或者它们是否被认为是好的?例如,是否应该合并它们并让应用程序使用按位运算? 一个补充:如果你在SQL Server的一个表中使用多个BIT列,它会将它们组合在一起。例如,如果您有一个 BIT 列,它将使用 1 个字节的存储空间。但是,如果您有 8 个 BIT 列,它仍将使用 1 个字节来存储两者。另一方面,Oracle 为每个分配 1 个字节。 @Rob:如果要搜索这些按位运算的结果,最好拆分字段,以便系统可以组合索引并使用位图访问表。在 Oracle 中,您还可以创建持久位图索引。 @Quassnoi - 在数据库中有混合字段用于应用程序域中的标志(即不需要人类可读)和一些用于是/没有需要人类可读的条目。可能有一个或两个字段会被查询,但现在要明确地说出一种或另一种方式还为时过早。 @Rob:我会避开那些1
和0
对于调试目的不够人类可读性的开发人员。【参考方案2】:
根据我自己的经验,我更喜欢 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 ''
)
【讨论】:
唯一的缺点是使用CHAR
类型允许可能的值包括“Y”和“N”,以及“1”和“0”。在您的代码中,您(或其他开发人员)可能会写 if (myTrueFalseFieldFromDatabase.equals("1")) ...
,当您打算与 "Y"
进行比较时。使用INT
或BIT
类型的好处是只允许使用1 和0,因此您不会犯此错误。但只要你小心,这不是什么大问题。
坦率地说,我不鼓励使用 ENUM('Y','N') 仅仅是因为它们在其他编程语言中都转换为 1/true。如果您使用 0 和 1,即使它只是 ENUM(1,0) 而不是 BIT/BOOLEAN,那么 0 将在 Perl、php 等“软”语言中转换为 false。0 和 1 是表示的经典方式布尔值,大多数编程语言都能准确解释。
@Tenner:一个 INT 通常可以存储比 0 或 1 更多的值。如果您在列上设置检查约束(假设 MySQL 支持它们),那么允许的值可以只是 'Y '和'N'(或'1'和'0')。我会选择 Y/N,但 1/0 没有大问题 - 在 CHAR(1) 字段中。
如果您必须滚动自己的布尔字段,我建议有一个类将语言(C++、Java 等)布尔转换为您想要的表示,并始终如一地使用它。这样可以避免有人在一个模块中使用 T/F 而不是 Y/N,或者使用小写而不是大写,或其他任何问题。
网站上的人几乎没有听说过约束。 @Jay 和@Tenner,乔纳森指出,对于混淆正确值应该是什么的担忧是没有根据的。检查约束将以零歧义解决 T/F 或上/下问题。【参考方案3】:
您可能需要考虑另一种数据模型来存储布尔值,而不是布尔数据类型,这在以下情况下可能特别合适:
当您将有许多是/否列时。 当您将来可能需要添加更多是/否列时。 当是/否值不经常更改时。定义用户权限可能是上述的一个典型例子。请考虑以下表格:
Table "Users": (user_id, name, surname, country)
Table "Permissions": (permission_id, permission_text)
Table "Users_Permissions": (user_id, permission_id)
在Permissions
表中,您将定义可能适用于用户的所有可能权限。您必须为每个是/否属性在Permissions
表中添加一行。您可能已经注意到,这使得将来添加新权限变得非常容易,而无需修改数据库架构。
使用上述模型,您可以通过在Users_Permissions
表中分配user_id
和permission_id
来指示一个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
优势
索引:您可以轻松地使用索引来查询特定事实。 空格:当您有许多错误值时,默认约定会节省空间。 规范化:事实在自己的表中定义(在Permissions
和Users_Permissions
表中。)您可以轻松地存储有关每个事实的更多信息。
缺点
查询:简单查询需要 JOIN。 设置为 False:要将值设置为 false,您必须删除一行(从Users_Permissions
表中)。否则,您可以在 Users_Permissions
表中使用“已删除”标志,这还允许您存储审计跟踪信息,例如权限何时被修改以及由谁修改。如果您删除该行,您将无法存储此信息。
【讨论】:
【参考方案4】:使用对您正在使用的特定数据库引擎有意义的任何内容。它是需要处理它的数据库的接口。如果数据库的代码端接口足够模块化,那么在底层数据库中处理不同的布尔类型将无非是简单的一行更改。
【讨论】:
虽然如此,但我认为问题不在于这个。我相信,就像在许多情况下一样,提问者隐藏了他查询的真正原因,即他正在编写一个支持多个数据库供应商的应用程序......所以他需要做出问题最少的数据类型选择。跨度> 【参考方案5】:我认为“Y/N”值比“1/0”更有意义。使用 Oracle,我将执行以下操作以使数据库引擎尽可能多地验证数据:
将列定义为 char(1) 添加可能的检查约束 值限制为“in ('Y', 'N') 如果符合业务规则, 使它们不可为空 - 这可以 隐含地防止出现问题 假设任何不是“Y”的东西 您的 SQL 中的值为“N”【讨论】:
【参考方案6】:如果您的 DBMS 支持布尔数据类型,例如 MySQL,请使用它。如果不是,我通常使用值为 Y 或 N 的 char(1)。在后一种情况下,最好编写几个函数来将 Java 或 C++ 或任何布尔类型转换为并且从 Y/N 开始,因此您可以避免使用冗余代码来执行此操作。这是一个非常简单的函数,但它必须处理诸如空值或 Y 或 N 以外的值之类的情况,并且您希望始终如一地这样做。
我绝对不会通过位操作将标志打包到单个变量中。是的,这会节省一些磁盘空间,但代价是更大的复杂性和出错的机会。如果您的 DBMS 不支持位操作——因为我从来没有想做这样的事情,我不知道该怎么做——如果有的话——那么你将拥有根据这样的标志进行选择或排序真的很困难。当然,您可以检索所有符合其他条件的记录,然后让调用代码清除具有正确标志值的记录。但是,如果只有一小部分记录具有所需的标志值,并且您有一个连接许多其他记录的查询,该怎么办?就像“select employee.name, sum(pay.amount) from employee join pay using (employee_id) where employee.executive=true and pay.bonus=true”一样。使用 where 子句,您可能会检索到非常少的记录。没有它,您将检索整个数据库。
现在磁盘空间很便宜,因此任何磁盘节省都可能不重要。如果你真的有大量的标志——比如每条记录有成百上千个标志——我想可能会有打包它们的情况。但这将在我的设计选择列表中。
编辑:让我详细说明编写一个类来将“SQL 布尔值”转换为“Java 布尔值”。这同样适用于任何语言,但我将使用 Java 作为示例。
如果您的 DBMS 具有内置布尔类型,那么使用 Java,您可以使用 ResultSet.getBoolean() 将其直接读入布尔变量。
但是,如果您必须将其存储为字符“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 或 Yes/No 或 On/Off 或其他什么,您可以添加备用 toString 变体 - toTFString 或 toString(value,truetext,falsetext) 或其他 - 而不是编写一遍又一遍的类似代码。
【讨论】:
【参考方案7】:我建议您创建另一个表,而不是添加列。听我说...
假设你有一个名为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... */
这很好也很简单。此线程上的许多人都在选择此列应为哪种数据类型提供了很好的建议,以便使语法在各种数据库中很好地发挥作用。
替代方案:创建单独的表
我将创建一个单独的表来存储每个可搜索客户的CustomerID
,而不是向Customer
添加另一列。
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 根据我的经验,这带来了极大的灵活性。
【讨论】:
正是因为这样的事情,优化器才不得不如此努力:CustomerID IN (SELECT CustomerID FROM SearchableCustomer) 嗯,我不太清楚为什么“customerid 在哪里(从可搜索的客户id 中选择)”是我更愿意写的东西,然后是“where searchable=true”。如果我有 3 个这样的字段,那么我有 4 个(“主”加 3 个“标志表”)而不是 1 个表。如果您担心布尔值的跨 DBMS 可移植性,可以,只需使用 char(1) 而不是将其声明为布尔值。 @Jay:很公平。在某些方面,我倾向于有点倒退。 :) 我倾向于在查询中设置操作,所以这对我来说似乎很自然。郑重声明,这种策略并非出于对跨平台布尔数据类型的需求。它实际上源于需要一种方法来集中“可搜索”客户的定义(请参阅“惊喜收益”)。如果我们的定义变成一个函数而不是列值,我们的系统中有几个地方需要重构。 @Stephanie:我喜欢现代优化器! :D 我可以用集合符号编写查询! +1 这种方法非常有用,如果我们需要了解有关 SearchableCustomers 的其他信息,而不是有关客户的信息;否则,我们必须在 Customer 表中有额外的 NULLable 列,加上约束以确保它们在不是 SearchableCustomer 时为 NULL,并确保它们在它们是 NOT NULL 时。另一方面,由于表连接所需的额外块读取,它可能会降低性能。【参考方案8】:位列通常用于表示 T/F 或 Y/N 类型的值,至少在 SQL Server 中是这样。尽管数据库纯粹主义者可能会告诉您,Bit 列在数据库中没有位置,因为它们“离硬件太近了”——Joe Celko。
【讨论】:
您是否引用了“太接近硬件”位?【参考方案9】:"选择 * 来自我的表 哪里 col1
,这在 SQL Server 和 Oracle 中是不可能的(你需要在那里有某种类型或谓词)。”
这只是说明 Oracle 和 SQL 服务器到底是多么可笑和可笑的可憎。
如果 col1 被声明为 BOOLEAN 类型,则表达式“col1”是谓词。
如果 WHERE 子句的语义要求其表达式仅计算为真值,并且如果某个列被声明为“真值”类型,则应允许并支持“WHERE that-column” .时期。任何不只是将其作者暴露给无能的平庸庸医的系统。
【讨论】:
哇,可笑和可憎,因为您必须输入整个 4 个额外字符 才能在列名后添加谓词?好的... 我认为有人有斧头要磨。首先,您不能在 Oracle 中定义布尔列。所以当然没有 WHERE boolcol。它不存在。 Erwin,请给我们您在 RDBMS 中的选择,我将挑选出 100 个 Oracle 具有但您最喜欢的功能没有的特性。【参考方案10】:我通常会在没有 BIT/BOOLEAN 值的情况下执行此操作。相反,我会有三张桌子。假设我们有一个项目管理系统,其中包含项目,这些项目具有一大堆属性。
然后我们有表格:
项目 - Project_ID (INT), - 名称(VARCHAR) 属性 - Attribute_ID (INT), - 名称(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 甚至不需要更改,因为属性本身是在数据库中定义的,而不是在代码中。
希望这会有所帮助。
【讨论】:
天啊,更多人推荐 EAV 结构。现在就开枪打死我。 但是,我们不能只是从结果集中的记录中提取字段,而是必须遍历结果以查找我们想要的字段。拼写错误的字段名称很难与具有空值的字段区分开来。根据定义,架构不是适用于任何给定表的属性的明确列表,而是在任何地方都没有明确的列表。 (有人可能会把它写在一张纸上,但谁来确保它是最新的。)我认为这是解决非常具体的一类问题的方法,而不是你一般想做的事情。 很抱歉,如果这显得有些粗鲁,但在我个人看来,拼写错误的字段名称很容易通过查看它们来发现。我不认为这是一个比“缺少半逗号”问题更多的真正问题。当然,简单地硬编码属性有一些好处,但我上次检查时,不鼓励硬编码这样的东西,因为那时它们是一成不变的。要获取属性列表,“SELECT Name FROM Attributes;” - 我看不到问题所在。请记住,极客的分歧似乎总是比实际更具敌意。 :) Helgi,你的建议太令人发指了,它有自己的名字。它被称为 EAV 模型。你说“不鼓励这样的事情”。你是对的,在编程世界中......在数据库世界中没有那么多。如果您按照您的描述设计 EAV,每个传统的 RDBMS 都会很糟糕。只需到这里***.com/questions/1940327/… 并从那里阅读链接。 我想我们只能不同意。也许我只是世界上见过的最伟大的程序员,但我从来没有遇到过这种做事方式的任何问题。我看不出拼写错误的字段名称和缺少半逗号之间的区别,我对其中任何一个问题都零尊重。我认为要求程序员甚至数据库设计人员能够阅读他们面前的代码并不过分。很抱歉,如果这听起来很苛刻或不体贴,但这就是我的感受。以上是关于在设计数据库时,存储多个真/假值的首选方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章