【发布时间】:2011-01-13 02:12:09
【问题描述】:
如标题中所述,在设计数据库时,处理具有多个列的表的首选方法是什么/1")?同样,在不同的数据库(例如 Oracle 和 SQL Server)之间是否可能会出现一些问题,这些问题可能会影响列的处理方式?
【问题讨论】:
标签: sql-server oracle database-design query-optimization
如标题中所述,在设计数据库时,处理具有多个列的表的首选方法是什么/1")?同样,在不同的数据库(例如 Oracle 和 SQL Server)之间是否可能会出现一些问题,这些问题可能会影响列的处理方式?
【问题讨论】:
标签: sql-server oracle database-design query-optimization
在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 中也是如此(就存储而言),但从逻辑上讲,它不能隐式转换为任何其他类型。
【讨论】:
1 和0 对于调试目的不够人类可读性的开发人员。
根据我自己的经验,我更喜欢 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,因此您不会犯此错误。但只要你小心,这不是什么大问题。
您可能需要考虑另一种数据模型来存储布尔值,而不是布尔数据类型,这在以下情况下可能特别合适:
定义用户权限可能是上述的一个典型例子。请考虑以下表格:
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 表中。)您可以轻松地存储有关每个事实的更多信息。缺点
Users_Permissions 表中)。否则,您可以在 Users_Permissions 表中使用“已删除”标志,这还允许您存储审计跟踪信息,例如权限何时被修改以及由谁修改。如果您删除该行,您将无法存储此信息。【讨论】:
使用对您正在使用的特定数据库引擎有意义的任何内容。它是需要处理它的数据库的接口。如果数据库的代码端接口足够模块化,那么在底层数据库中处理不同的布尔类型将无非是简单的一行更改。
【讨论】:
我认为“Y/N”值比“1/0”更有意义。使用 Oracle,我将执行以下操作以使数据库引擎尽可能多地验证数据:
【讨论】:
如果您的 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) 或其他 - 而不是编写一遍又一遍的类似代码。
【讨论】:
我建议您创建另一个表,而不是添加列。听我说...
假设你有一个名为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 根据我的经验,这带来了极大的灵活性。
【讨论】:
位列通常用于表示 T/F 或 Y/N 类型的值,至少在 SQL Server 中是这样。尽管数据库纯粹主义者可能会告诉您,Bit 列在数据库中没有位置,因为它们“离硬件太近了”——Joe Celko。
【讨论】:
"选择 * 来自我的表 哪里 col1
,这在 SQL Server 和 Oracle 中是不可能的(你需要在那里有某种类型或谓词)。”
这只是说明 Oracle 和 SQL 服务器到底是多么可笑和可笑的可憎。
如果 col1 被声明为 BOOLEAN 类型,则表达式“col1”是谓词。
如果 WHERE 子句的语义要求其表达式仅计算为真值,并且如果某个列被声明为“真值”类型,则应允许并支持“WHERE that-column” .时期。任何不只是将其作者暴露给无能的平庸庸医的系统。
【讨论】:
我通常会在没有 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 甚至不需要更改,因为属性本身是在数据库中定义的,而不是在代码中。
希望这会有所帮助。
【讨论】: