什么是最常见的SQL反模式?

我们所有使用关系数据库的人都学习(或正在学习)SQL是不同的。 获得期望的结果,并有效地这样做,涉及到一个乏味的过程,部分特点是学习不熟悉的范例,并发现我们最熟悉的一些编程模式在这里不起作用。 什么是你见过的(或者你自己犯的)常见的反模式?

我一直对大多数程序员倾向于在数据访问层中混合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循环,当一个循环variableswhile循环会做。

 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.通过stringtypes的DateLogic。

 --Trim the time Convert(Convert(theDate, varchar(10), 121), datetime) 

应该

 --Trim the time DateAdd(dd, DateDiff(dd, 0, theDate), 0) 

我见过最近的一个“一个问题比两个好,amiright?

 SELECT * FROM blah WHERE (blah.Name = @name OR @name is null) AND (blah.Purpose = @Purpose OR @Purpose is null) 

此查询需要两个或三个不同的执行计划,具体取决于参数的值。 只有一个执行计划被生成,并插入到这个sql文本的caching中。 无论参数的值如何,该计划都将被使用。 这导致间歇性差的性能。 编写两个查询(每个预期的执行计划一个查询)会好得多。

  • 人类可读的密码字段 ,egad。 自我解释。

  • 使用LIKE对索引列,我几乎试图说一般LIKE。

  • 回收SQL生成的PK值。

  • 惊喜没人提到神桌呢。 什么都没有说“有机”就像100列位标志,大string和整数。

  • 然后是“我错过.ini文件”模式:在大型文本字段中存储CSV,pipe道分隔string或其他parsing所需的数据。

  • 而对于MS SQL服务器,完全可以使用游标。 有一个更好的方法来做任何给定的光标任务。

编辑,因为有这么多!

不必深入挖掘:不使用预准备语句。

使用无意义的表别名:

 from employee t1, department t2, job t3, ... 

使阅读大型SQL语句比阅读更困难

 var query = "select COUNT(*) from Users where UserName = '" + tbUser.Text + "' and Password = '" + tbPassword.Text +"'"; 
  1. 盲目地相信用户input
  2. 不使用参数化查询
  3. 明文密码

我的烦恼是由董事总经理的最好的朋友狗美容师的8岁的儿子已经把450列访问表放在一起,只有存在,因为有人不知道如何规范一个数据结构正常。

通常,这个查找表如下所示:

 ID INT,
名称NVARCHAR(132),
 IntValue1 INT,
 IntValue2 INT,
 CharValue1 NVARCHAR(255),
 CharValue2 NVARCHAR(255),
 Date1 DATETIME,
 Date2 DATETIME

我已经失去了我见过的那些依赖这种可憎的系统的客户数量。

我最不喜欢的是

  1. 在创build表格,sprocs等时使用空格我很好,驼峰或under_scores和单数或复数和大写或小写,但不得不引用表格或列[与空格],尤其是如果[这是奇怪间隔](是的,我遇到了这个)真的让我很不爽。

  2. 非规范化的数据。 一个表格不一定是完全标准化的,但是当我碰到一张有关其当前评估分数或主要任何信息的员工表格时,它告诉我,我可能需要在某个时候制作一个单独的表格,然后尝试保持它们同步。 我会先对数据进行标准化,然后如果我看到一个非规范化的地方,我会考虑的。

  3. 过度使用视图或游标。 视图有一个目的,但是当每个表被包装在视图中时,它太多了。 我不得不使用游标几次,但通常你可以使用其他机制。

  4. 访问。 程序可以是反模式吗? 在我的工作中,我们拥有SQL Server,但由于非技术用户的“易用性”和“友善性”,许多人使用访问。 这里有太多可以进入的地方,但是如果你曾经在类似的环境中,你知道。

过度使用临时表和游标。

使用SP作为存储过程名称的前缀,因为它将首先在系统过程位置而不是自定义过程中进行search。

为了存储时间值,只能使用UTC时区。 当地时间不应该使用。

 select some_column, ... from some_table group by some_column 

并假设结果将按some_column进行sorting。 我已经看到了Sybase的这个假设(现在)。

使用@@ IDENTITY而不是SCOPE_IDENTITY()

从这个答案引用:

  • @@ IDENTITY返回当前会话中任何表中为所有作用域生成的最后一个标识值。 你需要小心,因为它跨越了范围。 您可以从触发器中获取值,而不是当前的语句。
  • SCOPE_IDENTITY返回为当前会话中的任何表和当前范围生成的最后一个标识值。 一般来说你想用什么。
  • IDENT_CURRENT返回在任何会话和任何作用域中为特定表生成的最后一个标识值。 这可以让你指定你想从哪个表中获得值,以防上面的两个不是你所需要的(非常less见)。 如果你想为没有插入logging的表获取当前的IDENTITY值,你可以使用它。

重复使用一个'死'字段的东西是不打算(例如存储在“传真”字段中的用户数据) – 非常诱人的一个快速修复,但!

 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 JOIN的FROM TableA, TableB WHERE语法而不是FROM TableA INNER JOIN TableB ON

  • 假定一个查询将被返回sorting,而不用放入一个ORDER BY子句,只是因为它是在查询工具中testing期间显示的。

我需要在这里把自己目前最喜欢的,只是为了完成清单。 我最喜欢的反模式不是testing你的查询

这适用于:

  1. 您的查询涉及多个表。
  2. 你认为你有一个查询的最佳devise,但不费心testing你的假设。
  3. 你接受第一个查询是有效的,而不知道是否接近优化。

而任何针对非典型或不足数据的testing都不计算在内。 如果是存储过程,则将testing语句放入注释中,并将结果保存。 否则,把它放在代码中的注释与结果。

在他们职业生涯的头六个月里学习SQL,在未来的10年里从来没有学过任何东西。 特别是没有学习或有效地使用窗口/分析SQLfunction。 特别是使用over()和partition by。

窗口函数(如聚合函数)在定义的行(组)上执行聚合,而不是每个组返回一个值,而窗口函数可以为每个组返回多个值。

请参阅O'Reilly SQL Cookbook附录A ,了解窗口函数的一个很好的概述。

相反的观点:过分迷恋正常化。

大多数SQL / RBDB系统提供了非常有用的一大堆function(事务,复制),即使是非标准化的数据。 磁盘空间是便宜的,有时它可以更简单(更简单的代码,更快的开发时间)来操作/筛选/search获取的数据,而不是编写1NF模式,并处理其中的所有麻烦(复杂的连接,讨厌的子查询等等)。

我发现过度规范化的系统往往是过早的优化,尤其是在开发的早期阶段。

(更多的想法… … http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/

我只是把这一个放在一起,根据这里的一些SQL响应。

这是一个严重的反模式,认为触发器是数据库作为事件处理程序是面向对象。 有这样的看法,只要任何旧的逻辑可以被放入触发器,当一个事务(事件)发生在桌子上时被触发。

不对。 其中一个很大的区别是触发器是同步的 – 带有复仇,因为它们在一个集合操作上是同步的,而不是在一个行操作上。 在OOP方面,完全相反的事件是实现asynchronous事务的有效方式。

临时表滥用。

特别是这样的事情:

 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) 

不要从查询构build临时表,只能删除不需要的行。

是的,我已经在生产数据库中看到了这种forms的代码页。

1)我不知道这是一个“官方”反模式,但我不喜欢,并尝试避免string文字作为数据库列中的魔法值。

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", 

(我只是注意到不同的shell,另一个要避免的事情)

我devise这样的例子int整数查find表格ImageMediaType和ImageMajorMime int主键。

2)依赖于特定NLS设置的date/string转换

 CONVERT(NVARCHAR, GETDATE()) 

没有格式标识符

查询中的相同子查询。

  • 改变视图 – 一个经常改变的视图,没有任何通知或理由。 改变将在最不适当的时候被注意到,或者更糟的是从未被注意到。 也许你的应用程序会中断,因为有人想到这个专栏的更好的名字。 作为一个规则,意见应扩大基表的用处,同时保持与消费者的合同。 修复问题,但不要添加function或更糟糕的更改行为,为此创build一个新的视图。 减轻不要与其他项目共享意见,并在平台允许时使用CTE 。 如果您的商店有DBA,那么您可能无法更改视图,但在这种情况下,您的所有视图都将过时或无用。

  • !Paramed – 一个查询可以有多个目的吗? 也许下一个读它的人直到深刻的冥想才会知道。 即使你现在不需要它们,即使是“正义”的debugging,你也是可能的。 添加参数可以缩短维护时间并保持干燥。 如果你有一个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 Server切换到Oracle的人有一个习惯,就是过度使用临时表。 只需使用嵌套的select语句。

我发现的最多的两个,并且在性能方面可能具有显着的成本是:

  • 使用游标而不是基于集合的expression式。 我猜这个程序员在程序上思考的时候会频繁发生。

  • 使用相关的子查询时,到派生表的连接可以完成这项工作。

Developers who write queries without having a good idea about what makes SQL applications (both individual queries and multi-user systems) fast or slow. This includes ignorance about:

  • physical I/O minimization strategies, given that most queries' bottleneck is I/O not CPU
  • perf impact of different kinds of physical storage access (eg lots of sequential I/O will be faster than lots of small random I/O, although less so if your physical storage is an SSD!)
  • how to hand-tune a query if the DBMS produces a poor query plan
  • how to diagnose poor database performance, how to "debug" a slow query, and how to read a query plan (or EXPLAIN, depending on your DBMS of choice)
  • locking strategies to optimize throughput and avoid deadlocks in multi-user applications
  • importance of batching and other tricks to handle processing of data sets
  • table and index design to best balance space and performance (eg covering indexes, keeping indexes small where possible, reducing data types to minimum size needed, etc.)

Using SQL as a glorified ISAM (Indexed Sequential Access Method) package. In particular, nesting cursors instead of combining SQL statements into a single, albeit larger, statement. This also counts as 'abuse of the optimizer' since in fact there isn't much the optimizer can do. This can be combined with non-prepared statements for maximum inefficiency:

 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 

The correct solution (almost always) is to combine the two SELECT statements into one:

 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 

The only advantage to the double loop version is that you can easily spot the breaks between values in Table1 because the inner loop ends. This can be a factor in control-break reports.

Also, sorting in the application is usually a no-no.

I just came across view definition like this:

 CREATE OR REPLACE FORCE VIEW PRICE (PART_NUMBER, PRICE_LIST, LIST_VERSION ...) AS SELECT sp.MKT_PART_NUMBER, sp.PRICE_LIST, sp.LIST_VERSION, sp.MIN_PRICE, sp.UNIT_PRICE, sp.MAX_PRICE, ... 

There are 50 or so columns in the view. Some developers take a small pride torturing others by not providing column aliases, so one have to count column offset in both places in order to be able to figure out what column in a view corresponds to.