了解具有许多子查询的大型复杂 SQL 查询的最佳方法

Best way to understand big and complex SQL queries with many subqueries

我刚开始一个新项目,在一家新公司。

我得到了一个大而复杂的 SQL,大约有 1000 行和许多子查询、连接、求和、分组等

此 SQL 用于报告生成(它没有插入或更新)。

SQL 有一些缺陷,我在公司的第一份工作是识别并纠正这些缺陷,以便报告显示正确的值(我通过访问一个写在Cobol...)

如何让我更容易理解查询,以便识别缺陷?

作为一名经验丰富的 Java 程序员,我知道如何用小块代码将复杂的、糟糕的、单一的 Java 代码重构为更容易理解的代码。但我不知道如何用 SQL.

做到这一点

SQL 看起来像这样:

SELECT columns
FROM
    (SELECT columns
    FROM
        (SELECT DISTINCT columns
              FROM table000 alias000
              INNER JOIN                                          
                      table000 alias000             
               ON column000 = table000.column000

              LEFT JOIN
                 (SELECT columns
                    FROM (
                    SELECT DISTINCT columns  
                      FROM columns        
                     WHERE conditions) AS alias000
                        GROUP BY columns ) alias000
                   ON
                    conditions
             WHERE conditions
            ) AS alias000
                 LEFT JOIN
                  (SELECT
                    columns  
                    FROM many_tables                
     WHERE many_conditions 
            ) )
        ) AS alias000
     ON condition               
       LEFT JOIN (      
    SELECT columns
    FROM            
    (SELECT
      columns
    FROM                                                   
       many_tables       
     WHERE many_conditions
            ) ) ) AS alias001  
        ,
         (SELECT
           many_columns 
         FROM                                                   
            many_tables            
           WHERE many_conditions) AS alias001
            ) AS alias001
        ON condition
    LEFT JOIN 
        (SELECT                                     
         many_columns
       FROM many_tables               
       WHERE many_conditions
          ) AS alias001
        ON condition
        ,    
         (SELECT  DISTINCT columns
           FROM table001 alias001
           INNER JOIN                                          
                 table001 alias001              
           ON condition
            LEFT JOIN
            (SELECT columns 
            FROM (                                           
         SELECT  DISTINCT columns
          FROM tables        
          WHERE conditions
         ) AS alias001
        GROUP BY                                                  
             columns ) alias001
            ON
             condition
             WHERE                                                
                 conditions
         ) AS alias001
         LEFT JOIN
         (SELECT columns
            FROM tables            
            WHERE conditions
              ) AS alias001
            ON condition
            LEFT JOIN ( 
                 SELECT columns
             FROM
             (SELECT columns
              FROM tables               
              WHERE conditions ) AS alias001
                    ,
                    (SELECT
                        columns
                 FROM                                                   
                   tables
                 WHERE conditions ) AS alias001
                ) AS alias001
                ON condition
                 LEFT JOIN 
                                (SELECT                                     
                   columns
                 FROM                                                   
                   tables
                 WHERE conditions
                     ) AS alias001
                   ON condition
    WHERE 
    condition
    ) AS alias001
    order by column001

如何让我更容易理解查询,以便识别缺陷?

编辑:我对这个答案投了反对票,可能是因为他们认为我提议将此作为构建最终查询的方式。我应该澄清一下,这纯粹是为了尝试了解正在发生的事情。一旦您理解了子查询以及它们如何 link 在一起,您就可以使用这些知识对查询进行必要的更改并以有效的方式重建它。

我已经使用中间温度技术 tables 对复杂查询进行了相当多的故障排除。它们将逻辑分解成更小的块,如果原始查询需要很长时间,它们也很有用。您可以测试如何组合这些中间 tables 而无需重新运行整个查询的开销。有时我会使用临时视图而不是 temp tables,因为查询优化器可以继续使用基础 tables 上的索引。临时视图将在您完成后删除。

我会从最里面的子查询开始,一直到外面。 您正在寻找以略有不同的形式多次出现的子查询,并为它们提供简明的描述 - 它们的设计目的是什么?

例如,替换

        from (
            select x1.y1, x1.y2, x1.y3 ...
            from tb1, tb2, tb3, tb4, tb5 ...
            left join ...
            where ...
            group by ...
            ) as a1

from daniel_view1 as a1

其中 daniel_view1

create view daniel_view as
            select x1.y1, x1.y2, x1.y3 ...
            from tb1, tb2, tb3, tb4, tb5 ...
            left join ...
            where ...
            group by ...

这已经使它看起来更干净了。然后比较意见。可以合并在一起吗?您不一定会在最终产品中保留视图,但它们将有助于看到更广泛的模式,而不会淹没细节。

或者,您可以将子查询插入临时 table

insert #daniel_work1 
select x1.y1, x1.y2, x1.y3 ...
from tb1, tb2, tb3, tb4, tb5 ...
left join ...
where ...
group by ...

然后将子查询替换为

select ... from #daniel_work1 as a1

您可以做的另一件事是查看是否可以将其分解为连续的步骤。 如果你看到

select ... from ...
union all
select ... from ...

这可能会变成

insert #steps
select 'step1', ...#1...
insert #steps
select 'step2', ...#2...

union 比较棘手,因为 set union 会删除重复的行(所有列都与另一行相同的行)。

通过将中间结果存储在临时 tables 中,您可以在查询展开时查看其内部,并重播困难的步骤。我将 'step_id' 作为我所有调试温度 table 的第一列,因此如果它分阶段填充,那么您会看到哪些数据适用于哪个阶段。

有一些技巧可以提供有关正在发生的事情的线索。如果您看到 table 像这样连接到自身:

select ... from mytable t1 inner join mytable t2 on t2.id < t.id

这通常意味着他们想要 table 与自身的叉积,但没有重复项。您将获得密钥 1 和 2,但不会获得密钥 2 和 1。

从中间开始工作在 SQL 中很常见,将 sql 的基于集合的逻辑转换为顺序逻辑可能会导致性能问题。尽量避免这种情况,尽管我知道这样做很诱人。

我要做的第一件事就是质疑连接语法。这真的是现在写的方式吗?

            select
            from tb1, tb2, tb3, tb4, tb5 ...
            left join ...

from 子句应如下所示

 From tb1
 Inner join tb2 on .....
 Inner join tb3 on .....
   ....
 Left join

http://www-03.ibm.com/software/products/en/data-studio

IBM 提供了一个基于 Eclipse 的分析工具,该工具能够为复杂查询生成可视化 EXPLAIN 图。它显示了索引的使用方式、生成和组合的内部结果集等。

示例: SELECT * FROM EMPLOYEE, DEPARTMENT WHERE WORKDEPT=DEPTNO

我每天都在处理这样的代码,因为我们在这里进行大量的报告和复杂数据的导出。

第一步是理解你所做的事情的意义。如果你不理解意义,你就无法评价你是否得到了正确的结果。因此,准确了解您要完成的任务,看看您是否可以在用户界面中看到一条记录应该看到的结果。有一些东西可以比较确实很有帮助,这样您就可以在查询过程中看到添加新事物如何改变结果。如果您的查询使用了单个字母或其他对派生的 table 别名无意义的东西,那么当您弄清楚派生的 table 应该做的事情的含义时,然后用更多的东西替换别名像 Employees 而不是 A 一样有意义。这将使下一个处理它的人以后更容易对其进行解码。

然后你要做的是从最内层的派生 table 开始(或者如果你愿意,可以从子查询开始,但是当它被用作 table 时,术语派生 table 更多准确的)。首先弄清楚它应该做什么。例如,也许它正在让所有绩效评估不尽如人意的员工。

运行 然后根据您所做的事情检查结果,看它们是否正确。例如,如果您正在查看不令人满意的评估并且您有 10,000 名员工,5617 似乎是该数据块的合理结果集吗?查找重复的记录。如果同一个人在那里三次,那么当你只想要一个时,你可能会遇到问题,你要加入一对多并取回许多。这可以通过使用聚合函数和分组依据或放入另一个派生 table 来替换问题连接来解决。

一旦你清除了最里面的部分,然后开始检查其他派生的 table 的结果,重新添加代码并检查结果,直到你找到不应该丢失的记录有(嘿,我在这个阶段有 137 名员工,现在只有 116 名。是什么原因造成的?)请记住,这只是了解为什么会发生这种情况的线索。当您构建复杂的查询时,有时基本结果会发生变化,有时它们不应该发生变化,这就是理解数据的含义至关重要的原因。

一般需要注意的一些事项:

  • 空值的处理方式会影响结果
  • 混合使用隐式连接和显式连接可能会在某些情况下导致不正确的结果 数据库。
  • 无论如何,您应该始终将所有隐式连接替换为 明确的。这使得代码更清晰并且不太可能有 错误。
  • 如果您有隐式联接,请查找意外交叉联接。他们是 即使在简短的查询中也很容易引入,在复杂的查询中,它们 更有可能,这就是为什么永远不应该使用隐式连接 用过。
  • 如果您离开了加入,请注意他们得到的地方 通过放置 where 子句意外转换为内部连接 左连接 table (除了 id 是否为空)。所以这 结构有问题:

       FROM table1 t1
       LEFT JOIN Table2 t2 ON t1.t1id = T2.t1id
       WHERE t2.somefield = 'test'
    

    应该是

       FROM table1 t1
       LEFT JOIN Table2 t2 ON t1.t1id = T2.t1id
          AND t2.somefield = 'test'
    

解决方案是使用 COMMON TABLE EXPRESSIONS 来简化查询。

这使我能够将大而复杂的 SQL 查询分解为许多小且易于理解的查询。

常见TABLE 表达式:

  • 可用于分解复杂的查询,尤其是复杂的连接和子查询
  • 是一种封装查询定义的方式。
  • 只坚持到下一个查询是 运行。
  • 正确使用可以提高代码 quality/maintainability 和速度。
  • 可用于在同一语句中多次引用结果 table(消除 SQL 中的重复)。
  • 当不需要视图的一般用途时,可以替代视图;也就是说,您不必将定义存储在元数据中。

示例:

WITH cte (Column1, Column2, Column3)
AS
(
    SELECT Column1, Column2, Column3
    FROM SomeTable
)

SELECT * FROM cte

我的新 SQL 看起来像这样:

------------------------------------------
--COMMON TABLE EXPRESSION 001--
------------------------------------------
WITH alias001 (column001, column002) AS (
    SELECT column005, column006
    FROM table001
    WHERE condition001
    GROUP by column008
)

--------------------------------------------
--COMMON TABLE EXPRESSION 002 --
--------------------------------------------
, alias002 (column009) as (
    select distinct column009 from table002
)

--------------------------------------------
--COMMON TABLE EXPRESSION 003 --
--------------------------------------------
, alias003 (column1, column2, column3) as (
    SELECT '1' AS column1, '1' as column2, 'name001' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '1' AS column1, '1.1' as column2, 'name002' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '1' AS column1, '1.2' as column2, 'name003' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '2' AS column1, '2' as column2, 'name004' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '2' AS column1, '2.1' as column2, 'name005' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '2' AS column1, '2.2' as column2, 'name006' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '3' AS column1, '3' as column2, 'name007' AS column3 FROM SYSIBM.SYSDUMMY1
    UNION ALL
    SELECT '3' AS column1, '3.1' as column2, 'name008' AS column3 FROM SYSIBM.SYSDUMMY1
)
--------------------------------------------
--COMMON TABLE EXPRESSION 004 --
--------------------------------------------
, alias004 (column1) as (
    select distinct column1 from table003
)

------------------------------------------------------
--COMMON TABLE EXPRESSION 005 --
------------------------------------------------------
, alias005 (column1, column2) as (
    select column1, column2 from alias002, alias004
)

------------------------------------------------------
--COMMON TABLE EXPRESSION 006 --
------------------------------------------------------
, alias006 (column1, column2, column3, column4) as (
    SELECT column1, column2, column3, sum(column0) as column4
    FROM table004
    LEFT JOIN table005 ON column01 = column02
    group by column1, column2, column3
)

------------------------------------------------------
--COMMON TABLE EXPRESSION 007 --
------------------------------------------------------
, alias007 (column1, column2, column3, column4) as (
    SELECT column1, column2, column3, sum(column0) as column4
    FROM table006
    LEFT JOIN table007 ON column01 = column02
    group by column1, column2, column3
)

------------------------------------------------------
--COMMON TABLE EXPRESSION 008 --
------------------------------------------------------
, alias008 (column1, column2, column3, column4) as (
    select column1, column2, column3, column4 from alias007 where column5 = 123
)

----------------------------------------------------------
--COMMON TABLE EXPRESSION 009 --
----------------------------------------------------------
, alias009 (column1, column2, column3, column4) as (
    select column1, column2, 
    CASE WHEN column3 IS NOT NULL THEN column3 ELSE 0 END as column3, 
    CASE WHEN column4 IS NOT NULL THEN column4 ELSE 0 END as column4
    from table007
)

----------------------------------------------------------
--COMMON TABLE EXPRESSION 010 --
----------------------------------------------------------
, alias010 (column1, column2, column3) as (
    select column1, sum(column4), sum(column5) 
    from alias009 
    where column6 < 2005 
    group by column1
)

--------------------------------------------
--             MAIN QUERY            --
--------------------------------------------

select j.column1, n.column2, column3, column4, column5, column6, 
column3 + column5 AS column7,
column4 + column6 AS column8
from alias010 j
left join alias006 m ON (m.column1 = j.column1)
left join alias008 n ON (n.column1 = j.column1)