243

我们所有使用关系数据库的人都已经了解(或正在学习)SQL 是不同的。引出所需的结果并有效地完成这一过程涉及一个乏味的过程,其部分特征是学习不熟悉的范例,并发现我们最熟悉的一些编程模式在这里不起作用。您见过(或您自己承诺过)的常见反模式是什么?

4

39 回答 39

162

我一直对大多数程序员在数据访问层混合他们的 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 层紧密耦合。最重要的是,这种编程风格彻底阻止了存储过程的可重用。

于 2008-12-06T21:55:16.103 回答
120

这是我的前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 文本仅生成一个执行计划并将其保存在缓存中。无论参数值如何,都将使用该计划。这会导致间歇性较差的性能。编写两个查询(每个预期执行计划一个查询)要好得多。

于 2008-12-06T19:54:03.570 回答
74
  • 人类可读的密码字段,egad。不言自明。

  • 对索引列使用LIKE ,我几乎只想说 LIKE。

  • 回收 SQL 生成的 PK 值。

  • 令人惊讶的是还没有人提到神桌。没有什么比 100 列位标志、大字符串和整数更能说明“有机”的了。

  • 然后是“我想念 .ini 文件”模式:在大文本字段中存储 CSV、管道分隔的字符串或其他解析所需的数据。

  • 而对于 MS SQL 服务器来说,游标的使用完全没有。有更好的方法来完成任何给定的光标任务。

因为太多所以编辑!

于 2008-12-06T20:11:47.590 回答
64

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

于 2008-12-06T19:45:49.280 回答
58

使用无意义的表别名:

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

使阅读大型 SQL 语句比需要的困难得多

于 2008-12-06T19:59:54.120 回答
56
var query = "select COUNT(*) from Users where UserName = '" 
            + tbUser.Text 
            + "' and Password = '" 
            + tbPassword.Text +"'";
  1. 盲目相信用户输入
  2. 不使用参数化查询
  3. 明文密码
于 2008-12-06T23:49:50.427 回答
47

我的问题是由总经理最好的朋友狗美容师的 8 岁儿子整理的 450 列 Access 表,以及由于有人不知道如何正确规范化数据结构而存在的狡猾的查找表。

通常,此查找表如下所示:

ID INT,
名称 NVARCHAR(132),
IntValue1 整数,
IntValue2 整数,
CharValue1 NVARCHAR(255),
CharValue2 NVARCHAR(255),
日期 1 日期时间,
日期 2 日期时间

我已经数不清有多少客户拥有依赖于这种可憎之物的系统了。

于 2008-12-06T20:25:25.880 回答
28

我最不喜欢的是

  1. 在创建表格、存储过程等时使用空格。我可以使用 CamelCase 或 under_scores 以及单数或复数以及大写或小写,但必须引用 [带空格] 的表格或列,特别是如果 [它的间距很奇怪](是的,我遇到过这个)真的让我很生气。

  2. 非规范化数据。表格不必完全标准化,但是当我遇到一张包含有关他们当前评估分数或主要信息的员工表格时,它告诉我我可能需要在某个时候制作一个单独的表格并且然后尝试使它们保持同步。我将首先规范化数据,然后如果我看到非规范化有帮助的地方,我会考虑它。

  3. 过度使用视图或光标。视图是有目的的,但是当每个表都包含在视图中时,它就太多了。我不得不使用游标几次,但通常您可以为此使用其他机制。

  4. 使用权。程序可以是反模式吗?我的工作中有 SQL Server,但由于它的可用性、“易用性”和对非技术用户的“友好性”,许多人使用 access。这里有太多内容要介绍,但是如果您曾经处于类似的环境中,您就会知道。

于 2008-12-06T22:34:37.663 回答
26

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

于 2008-12-06T19:48:32.677 回答
25

过度使用临时表和游标。

于 2008-12-06T20:08:09.847 回答
25

对于存储时间值,仅应使用 UTC 时区。不应使用当地时间。

于 2009-04-20T22:14:10.167 回答
23

使用 @@IDENTITY而不是 SCOPE_IDENTITY()

从这个答案中引用:

  • @@IDENTITY返回为当前会话中的任何表在所有范围内生成的最后一个标识值。您需要在这里小心,因为它是跨范围的。您可以从触发器而不是当前语句中获取值。
  • SCOPE_IDENTITY返回为当前会话和当前范围中的任何表生成的最后一个标识值。一般你想用什么。
  • IDENT_CURRENT返回为任何会话和任何范围内的特定表生成的最后一个标识值。这使您可以指定要从哪个表中获取值,以防上述两个不是您所需要的(非常罕见)。如果您想获取尚未插入记录的表的当前 IDENTITY 值,则可以使用它。
于 2009-04-06T15:22:51.597 回答
23

将“死”字段重新用于它不打算用于的事情(例如将用户数据存储在“传真”字段中) - 作为快速修复非常诱人!

于 2010-02-08T17:16:34.617 回答
21
select some_column, ...
from some_table
group by some_column

并假设结果将按 some_column 排序。我在 Sybase 中看到了这一点,假设成立(目前)。

于 2008-12-06T22:25:41.080 回答
21
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

或者,将所有内容塞进一行。

于 2009-04-19T03:53:16.733 回答
17
  • JOINS的FROM TableA, TableB WHERE语法,而不是FROM TableA INNER JOIN TableB ON

  • 假设查询将以某种方式返回,而无需放入 ORDER BY 子句,因为这是在查询工具中测试期间显示的方式。

于 2008-12-06T23:03:39.710 回答
15

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

窗口函数与聚合函数一样,对定义的一组(一组)行执行聚合,但不是每个组返回一个值,窗口函数可以为每个组返回多个值。

请参阅O'Reilly SQL Cookbook 附录 A以获得对窗口函数的一个很好的概述。

于 2010-02-03T17:59:56.677 回答
13

我需要把我自己目前最喜欢的放在这里,只是为了使列表完整。我最喜欢的反模式不是测试您的查询

这适用于:

  1. 您的查询涉及多个表。
  2. 您认为您有一个查询的最佳设计,但不必费心测试您的假设。
  3. 您接受了第一个有效的查询,但不知道它是否接近优化。

并且针对非典型或不充分数据运行的任何测试都不算数。如果它是一个存储过程,请将测试语句放入注释中并与结果一起保存。否则,将其与结果一起放入代码中的注释中。

于 2008-12-07T02:57:51.407 回答
11

临时表滥用。

特别是这种事情:

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)

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

是的,我在生产数据库中看到过这种形式的代码页。

于 2009-04-20T22:05:53.150 回答
9

逆向观点:过度痴迷于正常化。

大多数 SQL/RBDB 系统都提供了许多非常有用的特性(事务、复制),即使对于未规范化的数据也是如此。磁盘空间很便宜,有时它可以更简单(更简单的代码,更快的开发时间)操作/过滤/搜索获取的数据,而不是编写 1NF 模式,并处理其中的所有麻烦(复杂的连接,讨厌的子选择, ETC)。

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

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

于 2008-12-12T17:55:35.857 回答
9

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

认为触发器之于数据库就像事件处理程序之于 OOP 一样,这是一种严重的反模式。有这样一种看法,即任何旧逻辑都可以放入触发器中,以便在表上发生事务(事件)时触发。

不对。最大的区别之一是触发器是同步的 - 复仇,因为它们在集合操作上是同步的,而不是在行操作上。在 OOP 方面,恰恰相反——事件是实现异步事务的有效方式。

于 2009-01-30T07:02:10.947 回答
8

没有任何注释的存储过程或函数...

于 2011-10-12T12:02:41.257 回答
7

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

MediaWiki 的表格“图像”中的一个示例:

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())

没有格式标识符

于 2008-12-06T20:05:19.133 回答
7

查询中的相同子查询。

于 2008-12-06T20:15:13.683 回答
7
  • 改变的视图 - 过于频繁地改变并且没有通知或理由的视图。更改要么在最不合适的时间被注意到,要么更糟的是错误的并且永远不会被注意到。也许您的应用程序会中断,因为有人为该列想了一个更好的名称。作为规则,视图应该扩展基表的有用性,同时保持与消费者的合同。修复问题但不要添加功能或更糟糕的更改行为,以便创建新视图。为缓解问题,请勿与其他项目共享视图,并在平台允许时使用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  
    
于 2008-12-08T01:46:21.050 回答
5

我发现最多并且在性能方面可能会产生重大成本的两个是:

  • 使用游标而不是基于集合的表达式。我想这在程序员程序化思考时经常发生。

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

于 2008-12-07T04:48:27.923 回答
5

将东西放在临时表中,尤其是从 SQL Server 切换到 Oracle 的人有过度使用临时表的习惯。只需使用嵌套的选择语句。

于 2009-01-01T13:11:28.373 回答
5

编写查询但不知道是什么让 SQL 应用程序(单个查询和多用户系统)变快或变慢的开发人员。这包括对以下方面的无知:

  • 物理 I/O 最小化策略,因为大多数查询的瓶颈是 I/O 而不是 CPU
  • 不同类型物理存储访问的性能影响(例如,大量顺序 I/O 将比大量小型随机 I/O 更快,尽管如果您的物理存储是 SSD 则更慢!)
  • 如果 DBMS 产生糟糕的查询计划,如何手动调整查询
  • 如何诊断较差的数据库性能,如何“调试”慢查询,以及如何读取查询计划(或 EXPLAIN,取决于您选择的 DBMS)
  • 锁定策略以优化吞吐量并避免多用户应用程序中的死锁
  • 批处理和其他技巧处理数据集的重要性
  • 表和索引设计以最佳平衡空间和性能(例如覆盖索引,尽可能保持索引小,将数据类型减少到所需的最小大小等)
于 2011-11-24T05:33:45.537 回答
4

使用主键作为记录地址的代理,使用外键作为嵌入在记录中的指针的代理。

于 2010-09-15T13:01:16.897 回答
3

使用 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 中值之间的中断,因为内部循环结束。这可能是控制中断报告中的一个因素。

此外,在应用程序中进行排序通常是禁忌。

于 2008-12-06T22:29:16.783 回答
3

我刚刚遇到这样的视图定义:

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

视图中有大约 50 列。一些开发人员以不提供列别名来折磨他人而感到自豪,因此必须计算两个位置的列偏移量,以便能够弄清楚视图中的列对应于哪一列。

于 2011-05-05T23:42:50.503 回答
3

我见过太多的人为了宝贵的生命IN (...)而坚持不懈,而完全忘记了EXISTS。一个很好的例子,请参阅 Symfony Propel ORM。

于 2011-08-10T07:35:47.357 回答
3

应用程序连接不仅仅是一个 SQL 问题,而是寻找问题的描述并找到这个问题,我很惊讶它没有被列出。

正如我听说过的那样,应用程序连接是指从两个或多个表中的每一个中提取一组行,然后使用一对嵌套的 for 循环将它们连接到您的 (Java) 代码中。这使系统(您的应用程序和数据库)不得不识别整个交叉产品,检索它并将其发送到应用程序。假设应用程序可以像数据库一样快地过滤叉积(可疑),只是更快地减少结果集意味着更少的数据传输。

于 2011-11-17T16:02:50.783 回答
2

将冗余表连接到这样的查询中:

select emp.empno, dept.deptno
from emp
join dept on dept.deptno = emp.deptno;
于 2009-01-14T17:17:16.433 回答
2

有1张桌子

code_1
value_1
code_2
value_2
...
code_10
value_10

而不是有 3 张桌子

代码、值和代码值

你永远不知道什么时候你可能需要超过 10 对情侣的代码,值。

如果您只需要一对,就不会浪费磁盘空间。

于 2009-05-12T20:13:47.083 回答
2

回复:使用@@IDENTITY 而不是 SCOPE_IDENTITY()

你不应该使用;改用输出

参看。https://connect.microsoft.com/SQLServer/feedback/details/328811/scope-identity-sometimes-returns-incorrect-value

于 2010-08-30T10:27:21.263 回答
2

我最喜欢的 SQL 反模式:

JOIN在非唯一列上并SELECT DISTINCT用于修剪结果。

创建一个连接多个表的视图,只是为了从一个表中选择几列。

 CREATE VIEW my_view AS 
     SELECT * FROM table1
     JOIN table2 ON (...)
     JOIN table3 ON (...);

 SELECT col1, col2 FROM my_view WHERE col3 = 123;
于 2011-04-04T18:39:28.473 回答
1

也许不是一种反模式,但让我烦恼的是,当某些 DB 的 DBA(好吧,我在这里谈论 Oracle)使用 Oracle 样式和代码约定编写 SQL Server 代码时,当它运行得如此糟糕时会抱怨。Oracle 人用光标就够了!SQL 是基于设置的。

于 2009-01-01T13:05:54.813 回答
0

不使用 With 子句或正确的连接并依赖子查询。

反模式:

select 
 ...
from data
where RECORD.STATE IN (
          SELECT STATEID
            FROM STATE
           WHERE NAME IN
                    ('Published to test',
                     'Approved for public',
                     'Published to public',
                     'Archived'
                    ))

更好:
我喜欢使用 with 子句使我的意图更具可读性。

with valid_states as (
          SELECT STATEID
            FROM STATE
           WHERE NAME IN
                    ('Published to test',
                     'Approved for public',
                     'Published to public',
                     'Archived'
                    )
select  ... from data, valid_states
where data.state = valid_states.state

最好的:

select 
  ... 
from data join states using (state)
where 
states.state in  ('Published to test',
                     'Approved for public',
                     'Published to public',
                     'Archived'
                    )
于 2010-02-03T18:19:20.453 回答