我们所有使用关系数据库的人都已经知道(或正在学习)SQL是不同的。选出想要的结果并有效地进行,涉及到一个乏味的过程,其部分特征是学习不熟悉的范例,并发现我们最熟悉的编程模式在这里不起作用。您见过(或自己犯过)的常见反模式是什么?
我们所有使用关系数据库的人都已经知道(或正在学习)SQL是不同的。选出想要的结果并有效地进行,涉及到一个乏味的过程,其部分特征是学习不熟悉的范例,并发现我们最熟悉的编程模式在这里不起作用。您见过(或自己犯过)的常见反模式是什么?
Answers:
对于大多数程序员在数据访问层中混合使用其UI逻辑的趋势,我始终感到失望:
SELECT
FirstName + ' ' + LastName as "Full Name",
case UserRole
when 2 then "Admin"
when 1 then "Moderator"
else "User"
end as "User's Role",
case SignedIn
when 0 then "Logged in"
else "Logged out"
end as "User signed in?",
Convert(varchar(100), LastSignOn, 101) as "Last Sign On",
DateDiff('d', LastSignOn, getDate()) as "Days since last sign on",
AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' +
City + ', ' + State + ' ' + Zip as "Address",
'XXX-XX-' + Substring(
Convert(varchar(9), SSN), 6, 4) as "Social Security #"
FROM Users
通常,程序员之所以这样做,是因为他们打算将其数据集直接绑定到网格,并且使SQL Server格式的服务器端比客户端的格式更方便。
上面显示的查询非常脆弱,因为它们将数据层紧密耦合到UI层。最重要的是,这种编程风格彻底阻止了存储过程的重用。
这是我的前3名。
数字1.无法指定字段列表。(编辑:为避免混淆:这是一条生产代码规则。它不适用于一次性分析脚本-除非我是作者。)
SELECT *
Insert Into blah SELECT *
应该
SELECT fieldlist
Insert Into blah (fieldlist) SELECT fieldlist
数字2。使用游标和while循环,当使用带有循环变量的while循环时即可。
DECLARE @LoopVar int
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable)
WHILE @LoopVar is not null
BEGIN
-- Do Stuff with current value of @LoopVar
...
--Ok, done, now get the next value
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable
WHERE @LoopVar < TheKey)
END
数字3。通过字符串类型的DateLogic。
--Trim the time
Convert(Convert(theDate, varchar(10), 121), datetime)
应该
--Trim the time
DateAdd(dd, DateDiff(dd, 0, theDate), 0)
我最近看到过一个高峰,“一个查询比两个查询好吗?”
SELECT *
FROM blah
WHERE (blah.Name = @name OR @name is null)
AND (blah.Purpose = @Purpose OR @Purpose is null)
此查询需要两个或三个不同的执行计划,具体取决于参数的值。对于此sql文本,仅生成一个执行计划并将其粘贴到缓存中。无论参数值如何,都将使用该计划。这会导致间歇性的性能下降。最好编写两个查询(每个预期的执行计划一个查询)。
可读的密码字段,例如:adad。自我解释。
对索引 列使用LIKE,而我几乎很想一般地说一下LIKE。
回收SQL生成的PK值。
惊讶的是,没有人提到神桌。没有什么比100列位标志,大字符串和整数更像“有机”了。
然后是“我想念.ini文件”模式:在大型文本字段中存储CSV,管道分隔字符串或其他解析所需数据。
对于MS SQL Server来说,根本就不能使用游标。有一种更好的方法可以执行任何给定的游标任务。
编辑,因为有太多!
LIKE '%LIKE'
。
不必对此进行深入研究:不使用准备好的语句。
使用无意义的表别名:
from employee t1,
department t2,
job t3,
...
使读取大型SQL语句比需要的难得多
我的烦恼是450列Access表,这些表是由8岁的董事总经理最好的朋友狗美容师的儿子整理而成的,而狡猾的查找表由于某些人不知道如何正确地标准化数据结构而存在。
通常,此查找表如下所示:
ID INT, 名称NVARCHAR(132), IntValue1 INT, IntValue2 INT, CharValue1 NVARCHAR(255), CharValue2 NVARCHAR(255), 日期1 DATETIME, 日期2 DATETIME
我已经失去了我所见过的拥有这样的可恶系统的客户数量。
我最不喜欢的是
在创建表,存储过程等时使用空格。我对CamelCase或under_scores以及单数或复数以及大写或小写字母都很好,但是必须引用表或列[带空格],尤其是如果[奇数行距](是的,我碰到这个)真的让我很生气。
非规范化数据。一张表不一定要完全规范化,但是当我遇到一个雇员表时,该表具有有关其当前评估分数或主要指标的信息,它告诉我在某个时候我可能需要制作一个单独的表,并且然后尝试使其保持同步。我将首先对数据进行规范化,然后,如果我看到非规范化有帮助的地方,我会考虑的。
过度使用视图或光标。视图是有目的的,但是当每个表都包装在视图中时,它就太多了。我不得不使用游标几次,但是通常您可以使用其他机制。
访问。程序可以是反模式吗?我们正在使用SQL Server,但是由于非技术用户具有可用性,“易用性”和“友好性”,因此许多人都在使用访问权限。这里有太多要讨论的内容,但是如果您曾经在类似的环境中工作,您就会知道。
使用SP作为存储过程名称的前缀,因为它将首先在系统过程位置而不是自定义位置中搜索。
过度使用临时表和游标。
为了存储时间值,仅应使用UTC时区。不应使用当地时间。
使用@@ IDENTITY而不是SCOPE_IDENTITY()
引用此答案:
select some_column, ...
from some_table
group by some_column
并假设结果将按some_column排序。我已经在假设成立的Sybase身上看到了这一点。
SELECT FirstName + ' ' + LastName as "Full Name", case UserRole when 2 then "Admin" when 1 then "Moderator" else "User" end as "User's Role", case SignedIn when 0 then "Logged in" else "Logged out" end as "User signed in?", Convert(varchar(100), LastSignOn, 101) as "Last Sign On", DateDiff('d', LastSignOn, getDate()) as "Days since last sign on", AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' + City + ', ' + State + ' ' + Zip as "Address", 'XXX-XX-' + Substring(Convert(varchar(9), SSN), 6, 4) as "Social Security #" FROM Users
或者,将所有内容塞入一行。
FROM TableA, TableB WHERE
JOINS 的语法,而不是FROM TableA INNER JOIN TableB ON
假设查询将被返回将以某种方式排序,而无需放入ORDER BY子句,这仅仅是因为它是在查询工具中进行测试时所显示的方式。
在其职业生涯的前六个月学习SQL,在接下来的十年中再也没有学习过其他任何知识。特别是不学习或没有有效使用开窗/分析SQL功能。特别是over()和partition by的使用。
窗口函数与聚合函数一样,对一组已定义的行(一组)执行聚合,但是窗口函数可以为每个组返回多个值,而不是每组返回一个值。
有关窗口函数的详细概述,请参见《O'Reilly SQL Cookbook附录A》。
我需要在此处放入我自己当前的收藏夹,以使列表完整。我最喜欢的反模式不是测试您的查询。
在以下情况下适用:
并且针对非典型或不足数据进行的任何测试均不计算在内。如果是存储过程,则将测试语句放入注释中,并将其与结果一起保存。否则,将其放入带有结果的代码注释中。
临时表滥用。
具体来说就是这样的事情:
SELECT personid, firstname, lastname, age
INTO #tmpPeople
FROM People
WHERE lastname like 's%'
DELETE FROM #tmpPeople
WHERE firstname = 'John'
DELETE FROM #tmpPeople
WHERE firstname = 'Jon'
DELETE FROM #tmpPeople
WHERE age > 35
UPDATE People
SET firstname = 'Fred'
WHERE personid IN (SELECT personid from #tmpPeople)
不要从查询中构建临时表,而只是删除不需要的行。
是的,我已经在生产数据库中看到了这种形式的代码页。
相反的观点:过度迷恋标准化。
大多数SQL / RBDBs系统提供了很多功能(事务,复制),这些功能即使对非规范化数据也非常有用。磁盘空间便宜,与编写1NF模式并处理其中的所有麻烦(复杂的连接,讨厌的子选择)相比,操作/过滤/搜索获取的数据有时更简单(更轻松的代码,更快的开发时间)。等)。
我发现过度标准化的系统通常是过早的优化,尤其是在早期开发阶段。
(对此有更多的想法... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/)
1)我不知道这是一个“官方的”反模式,但我不喜欢并尝试避免将字符串文字作为数据库列中的魔术值使用。
MediaWiki表“ image”的一个示例:
img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO",
"MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
img_major_mime ENUM("unknown", "application", "audio", "image", "text",
"video", "message", "model", "multipart") NOT NULL default "unknown",
(我只是注意到不同的大小写,要避免的另一件事)
我设计这样的情况,例如使用int主键对表ImageMediaType和ImageMajorMime进行int查找。
2)取决于特定NLS设置的日期/字符串转换
CONVERT(NVARCHAR, GETDATE())
没有格式标识符
查询中的相同子查询。
更改视图-更改频率太高而没有通知或理由的视图。更改将在最不合适的时间被注意到,或者更糟的是错误并且永远不会被注意到。也许您的应用程序会中断,因为有人认为该列的名称更好。通常,视图应扩展基表的用途,同时保持与消费者的合同。解决问题,但不要添加功能或更糟的更改行为,因为那样会创建新视图。为了减轻不与其他项目共享视图的可能性,请在平台允许时使用CTE。如果您的商店有DBA,则您可能无法更改视图,但是在这种情况下,所有视图都将过时或无用。
!Paramed-查询是否可以有多个目的?可能是这样,但是下一个阅读它的人要等到深沉的冥想才能知道。即使您现在不需要它们,即使调试只是“公正”的,您也有机会。添加参数可以减少维护时间并使设备保持干燥。如果有where子句,则应该有参数。
没有案例的情况-
SELECT
CASE @problem
WHEN 'Need to replace column A with this medium to large collection of strings hanging out in my code.'
THEN 'Create a table for lookup and add to your from clause.'
WHEN 'Scrubbing values in the result set based on some business rules.'
THEN 'Fix the data in the database'
WHEN 'Formating dates or numbers.'
THEN 'Apply formating in the presentation layer.'
WHEN 'Createing a cross tab'
THEN 'Good, but in reporting you should probably be using cross tab, matrix or pivot templates'
ELSE 'You probably found another case for no CASE but now I have to edit my code instead of enriching the data...' END
我发现最多的两个,在性能方面可能会产生重大损失的是:
使用游标而不是基于集合的表达式。我猜想这种情况经常在程序员有步骤地思考时发生。
当关联到派生表时,可以使用相关子查询来完成这项工作。
编写查询而对如何使SQL应用程序(无论是单个查询还是多用户系统)的速度或速度不怎么了解的开发人员。这包括对以下方面的无知:
使用SQL作为美化的ISAM(索引顺序访问方法)包。特别是,嵌套游标而不是将SQL语句合并为单个(尽管较大)语句。这也算是“滥用优化器”,因为实际上优化器无能为力。可以将其与未准备好的语句结合使用以最大程度地降低效率:
DECLARE c1 CURSOR FOR SELECT Col1, Col2, Col3 FROM Table1
FOREACH c1 INTO a.col1, a.col2, a.col3
DECLARE c2 CURSOR FOR
SELECT Item1, Item2, Item3
FROM Table2
WHERE Table2.Item1 = a.col2
FOREACH c2 INTO b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
END FOREACH
正确的解决方案(几乎总是)是将两个SELECT语句组合为一个:
DECLARE c1 CURSOR FOR
SELECT Col1, Col2, Col3, Item1, Item2, Item3
FROM Table1, Table2
WHERE Table2.Item1 = Table1.Col2
-- ORDER BY Table1.Col1, Table2.Item1
FOREACH c1 INTO a.col1, a.col2, a.col3, b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
双循环版本的唯一优点是,由于内部循环结束,因此您可以轻松地在Table1中发现值之间的中断。这可能是控制中断报告中的一个因素。
同样,在应用程序中排序通常是禁止的。