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

时间:2008-12-06 19:42:15

标签: sql anti-patterns

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

40 个答案:

答案 0 :(得分:147)

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

答案 1 :(得分:113)

这是我的前三名。

编号1.未指定字段列表。 (编辑:为了防止混淆:这是一个生产代码规则。它不适用于一次性分析脚本 - 除非我是作者。)

SELECT *
Insert Into blah SELECT *

应该是

SELECT fieldlist
Insert Into blah (fieldlist) SELECT fieldlist

Number 2.使用cursor和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

Number 3. 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文本的缓存中。无论参数的值如何,都将使用该计划。这导致间歇性的不良性能。编写两个查询(每个预期的执行计划一个查询)要好得多。

答案 2 :(得分:67)

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

  • 使用 LIKE对索引 列,我几乎想要 就像一般说来一样。

  • 回收SQL生成的PK值。

  • 惊喜没人提到 上帝之表。什么都没说 像100列一样的“有机” 标志,大字符串和整数。

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

  • 并为MS SQL服务器使用 游标在所有。有一个更好的 做任何给定游标任务的方法。

编辑,因为有这么多!

答案 3 :(得分:60)

不必深入挖掘它:不使用准备好的陈述。

答案 4 :(得分:55)

使用无意义的表别名:

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

使读取大型SQL语句的难度远远超过它需要的

答案 5 :(得分:50)

var query = "select COUNT(*) from Users where UserName = '" 
            + tbUser.Text 
            + "' and Password = '" 
            + tbPassword.Text +"'";
  1. 盲目信任用户输入
  2. 未使用parameterized queries
  3. Cleartext passwords

答案 6 :(得分:45)

我的bugbears是由管理总监最好的朋友狗美容师的8岁儿子和仅仅存在的狡猾的查找表组成的450列访问表,因为有人不知道如何正确地规范化数据结构

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

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

我已经失去了我见过的拥有依赖这种可憎行为的系统的客户数量。

答案 7 :(得分:28)

我最不喜欢的是

  1. 在创建表,sprocs等时使用空格。我可以使用CamelCase或under_scores和单数或复数以及大写或小写,但必须引用表或列[带空格],特别是如果[它是奇怪的间隔](是的,我遇到了这个)真的让我感到恼火。

  2. 非规范化数据。表格不一定要完全正常化,但是当我遇到一个员工表,其中包含有关当前评估分数或主要内容的信息时,它告诉我,我可能需要在某个时刻制作一个单独的表格,然后尝试让他们同步。我将首先规范化数据然后如果我看到非规范化有帮助的地方,我会考虑它。

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

  4. 访问。程序可以反模式吗?我的工作中有SQL Server,但很多人都使用访问权限,因为它对非技术用户来说具有可用性,“易用性”和“友好性”。这里有太多的内容,但如果你曾经在类似的环境中,你知道。

答案 8 :(得分:26)

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

答案 9 :(得分:25)

过度使用临时表和游标。

答案 10 :(得分:23)

使用@@ IDENTITY 代替SCOPE_IDENTITY()

引自this answer

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

答案 11 :(得分:23)

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

答案 12 :(得分:23)

将“死”字段重新用于不适合的内容(例如,将用户数据存储在“传真”字段中) - 尽管可以快速修复!

答案 13 :(得分:21)

select some_column, ...
from some_table
group by some_column

并假设结果将按some_column排序。我已经在Sybase看到了这个假设(暂时)。

答案 14 :(得分:20)

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

或者,把所有东西塞进一行。

答案 15 :(得分:17)

  • JOINS的FROM TableA, TableB WHERE语法而不是FROM TableA INNER JOIN TableB ON

  • 假设查询将以某种方式返回,而不会放入ORDER BY子句,只是因为这是在查询工具中测试时出现的方式。

答案 16 :(得分:14)

在职业生涯的前六个月学习SQL,并且在接下来的十年中从不学习任何其他知识。特别是不学习或有效使用窗口/分析SQL功能。特别是使用over()和partition by。

  

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

有关窗口函数的详细概述,请参阅O'Reilly SQL Cookbook Appendix A

答案 17 :(得分:12)

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

这适用于:

  1. 您的查询涉及多个表格。
  2. 您认为自己拥有查询的最佳设计,但不必费心去测试您的假设。
  3. 您接受第一个有效的查询,但不知道它是否接近优化。
  4. 任何针对非典型或不充分数据的测试都不计算在内。如果它是存储过程,请将测试语句放入注释中并将其保存,并显示结果。否则,将其放入带有结果的代码中的注释中。

答案 18 :(得分:10)

临时表滥用。

特别是这类事:

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)

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

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

答案 19 :(得分:9)

反对观点:过度迷恋正常化。

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

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

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

答案 20 :(得分:9)

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

认为触发器是数据库,因为事件处理程序是OOP,这是一个严肃的反模式。有这种看法,任何旧的逻辑都可以放入触发器中,当事务(事件)发生在表上时被触发。

不正确。其中一个最大的区别是触发器是同步的 - 具有复仇性,因为它们在设置操作上是同步的,而不是在行操作上。在OOP方面,恰恰相反 - 事件是实现异步事务的有效方式。

答案 21 :(得分:8)

没有任何评论的存储过程或函数...

答案 22 :(得分:7)

  • “已更改的视图” - 一种经常更改且无需通知或理由的视图。这种变化要么在最不合适的时候被注意到,要么更糟糕,并且从未被注意到。也许你的应用程序会破坏,因为有人想到该列的更好名称。通常,视图应该在保持与消费者的合同的同时扩展基表的有用性。修复问题但不添加功能或更糟糕的更改行为,因为这会创建新视图。要缓解不与其他项目共享视图,请在平台允许时使用CTEs。如果您的商店有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  
    

答案 23 :(得分:7)

查询中的相同子查询。

答案 24 :(得分:7)

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

没有格式标识符

答案 25 :(得分:5)

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

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

答案 26 :(得分:5)

将内容放在临时表中,特别是从SQL Server切换到Oracle的人有过度使用临时表的习惯。只需使用嵌套的select语句。

答案 27 :(得分:5)

我发现最多的两个,并且在性能方面可能会有很大的成本:

  • 使用游标而不是基于集合 表达。我猜这个程序员经常在程序上思考这个问题。

  • 使用相关子查询时,a 连接到派生表可以做到 工作

答案 28 :(得分:3)

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

答案 29 :(得分: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个左右的列。一些开发人员通过不提供列别名来贬低其他人,因此必须在两个位置计算列偏移量,以便能够确定视图中的哪个列对应。

答案 30 :(得分:3)

我看到有太多人为IN (...)而坚持不懈地生活,而完全忘记EXISTS。有关一个很好的示例,请参阅Symfony Propel ORM。

答案 31 :(得分:3)

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

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

答案 32 :(得分: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中值之间的中断,因为内部循环结束。这可能是控制中断报告中的一个因素。

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

答案 33 :(得分:2)

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

select emp.empno, dept.deptno
from emp
join dept on dept.deptno = emp.deptno;

答案 34 :(得分:2)

答案 35 :(得分:2)

有一张桌子

code_1
value_1
code_2
value_2
...
code_10
value_10

而不是有3个表

代码,值和code_value

你永远不知道什么时候你可能需要10对以上的情侣代码。

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

答案 36 :(得分:2)

re:使用@@ IDENTITY而不是SCOPE_IDENTITY()

你不应该使用;改用输出

比照https://connect.microsoft.com/SQLServer/feedback/details/328811/scope-identity-sometimes-returns-incorrect-value

答案 37 :(得分: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;

答案 38 :(得分:1)

也许不是一种反模式,但令我很生气的是某些DB的DBA(好的,我在这里谈论Oracle)使用Oracle风格和代码约定编写SQL Server代码,并在运行如此糟糕时抱怨。足够甲骨文的游标! SQL意味着基于。

答案 39 :(得分: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'
                    )