优化多个嵌套选择的 SQL 查询

Posted

技术标签:

【中文标题】优化多个嵌套选择的 SQL 查询【英文标题】:Optimizing SQL query of multiple nested selects 【发布时间】:2015-05-28 07:05:16 【问题描述】:

我需要将表中的一列拆分为新视图或表中的不同列,其中逗号分隔值。 目前对我来说最好的运行解决方案是

CREATE VIEW clearTable as select ID,Timestamp,s1,s3,s4,s5,s6,s7,s8,substr(s8r,1,instr(s8r,",")-1) as s9 from
    (select ID,Timestamp,s1,s3,s4,s5,s6,s7,substr(s7r,1,instr(s7r,",")-1) as s8,substr(s7r,instr(s7r,",")+1) as s8r from
        (select ID,Timestamp,s1,s3,s4,s5,s6,substr(s6r,1,instr(s6r,",")-1) as s7,substr(s6r,instr(s6r,",")+1) as s7r from
            (select ID,Timestamp,s1,s3,s4,s5,substr(s5r,1,instr(s5r,",")-1) as s6,substr(s5r,instr(s5r,",")+1) as s6r from
                (select ID,Timestamp,s1,s3,s4,substr(s4r,1,instr(s4r,",")-1) as s5,substr(s4r,instr(s4r,",")+1) as s5r from
                    (select ID,Timestamp,s1,s3,substr(s3r,1,instr(s3r,",")-1) as s4,substr(s3r,instr(s3r,",")+1) as s4r from
                        (select ID,Timestamp,s1,substr(s2r,1,instr(s2r,",")-1) as s3,substr(s2r,instr(s2r,",")+1) as s3r from
                            (select ID,Timestamp,s1,substr(s1r,1,instr(s1r,",")-1) as s2,substr(s1r,instr(s1r,",")+1) as s2r from
                                (select ID,Timestamp,cast(substr(payload,1,instr(payload,",")-1) as TIME) as s1,substr(payload,instr(payload,",")+1) as s1r from thebasetable))))))))

如您所见 - 每个分隔字符都有一个新的子查询级别。 结果是,我不会这样做,但我正在寻找更好的方法来到达那里 - 也许是一个更有效的解决方案。 作为一个工作示例,您可以使用this SQL Fiddle。 此外,我想提一下,目前数据存储在SQLite 中,但这可能会发生变化,因此无需仅针对SQLite 进行优化。 欢迎所有提示。

【问题讨论】:

SQLite 旨在嵌入到另一种语言中。 【参考方案1】:

让我从您当前解决方案中的一个错误开始(除了是否有效):对于第三行,它在s1 列中返回0。据我了解您的意图,您想从有效负载中返回第一个元素,第 3 行是A,而不是0

它也不会返回s2 - 我不知道这是不是故意的。我的解决方案确实返回了它。

现在,回答您的问题我制定了一个运行速度更快的查询(在我的本地 sqlite 上进行测试时,它给了我 3 毫秒,而运行原始查询平均需要 11 毫秒)并且不嵌套选择这么多。这有点复杂,所以我会在后面解释。这是查询:

SELECT id,
       timestamp,
       max(CASE WHEN col = 1 THEN item ELSE '' END) AS s1,
       max(CASE WHEN col = 2 THEN item ELSE '' END) AS s2,
       max(CASE WHEN col = 3 THEN item ELSE '' END) AS s3,
       max(CASE WHEN col = 4 THEN item ELSE '' END) AS s4,
       max(CASE WHEN col = 5 THEN item ELSE '' END) AS s5,
       max(CASE WHEN col = 6 THEN item ELSE '' END) AS s6,
       max(CASE WHEN col = 7 THEN item ELSE '' END) AS s7,
       max(CASE WHEN col = 8 THEN item ELSE '' END) AS s8,
       max(CASE WHEN col = 9 THEN item ELSE '' END) AS s9
  FROM (
       WITH RECURSIVE tmp (
               id,
               timestamp,
               item,
               data,
               col
           )
           AS (
               SELECT id,
                      timestamp,
                      substr(payload, 1, instr(payload, ',') - 1),
                      payload,
                      1
                 FROM thebasetable
               UNION ALL
               SELECT id,
                      timestamp,
                      substr(substr(data, instr(data, ',') + 1), 1, instr(substr(data, instr(data, ',') + 1), ',') - 1),
                      substr(data, instr(data, ',') + 1),
                      col + 1
                 FROM tmp
                WHERE instr(data, ',') > 0 AND 
                      col < 9
                ORDER BY 1
           )
           SELECT id,
                  timestamp,
                  item,
                  col
             FROM tmp
       )
 GROUP BY id,
          timestamp;

查询使用公用表表达式 (CTE)。您可以在 SQLite 的 SQL 语法文档中阅读更多相关信息(查找 WITH 语句)。

CTE 部分是这个:

   WITH RECURSIVE tmp (
           id,
           timestamp,
           item,
           data,
           col
       )
       AS (
           SELECT id,
                  timestamp,
                  substr(payload, 1, instr(payload, ',') - 1),
                  payload,
                  1
             FROM thebasetable
           UNION ALL
           SELECT id,
                  timestamp,
                  substr(substr(data, instr(data, ',') + 1), 1, instr(substr(data, instr(data, ',') + 1), ',') - 1),
                  substr(data, instr(data, ',') + 1),
                  col + 1
             FROM tmp
            WHERE instr(data, ',') > 0 AND 
                  col < 9
            ORDER BY 1
       )
       SELECT id,
              timestamp,
              item,
              col
         FROM tmp

它的作用是读取具有初始负载的所有行,从负载中获取“第一个”元素并为其添加等于1col 值。然后它将有效载荷传递给 CTE 的下一次迭代,但它从有效载荷中截断了第一个元素,因此下一次迭代将第二个元素视为第一个元素。它还会为每次下一次迭代增加初始 1 值。

它反复遍历整个有效负载,每次迭代移动第一个元素,直到到达有效负载的末尾 (WHERE instr(data, ',') &gt; 0)。

我还在WHERE 中添加了第二个条件:col &lt; 9 - 这个条件控制将从有效负载中提取多少列。该数字应等于您将从中读取的列数。如果将其设置为较小的数字,则结果中剩余的列将为空。如果将它设置为更大的数字,它不会造成任何伤害,只是查询会慢一点,不必要。

最后,CTE 包含在SELECT 中,它按IDTimestamp 将CTE 的结果分组,然后通过检测该行是否有任何值来从其余列中获取值。很难解释。如果你自己执行CTE部分会更好,看看它返回什么,然后你就会明白外面的SELECT是做什么的。

注意 - 此解决方案需要 SQLite 3.8.3,因为这是将 CTE 引入 SQLite 时的版本。

CTE 是数据库中的常见功能。大多数流行的数据库都支持它(我刚刚查了一下,它存在于 mysql、MS SQL、Oracle、PostgreSQL 中,所以看起来很不错)。

【讨论】:

谢谢,非常好的解决方案。

以上是关于优化多个嵌套选择的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

使用嵌套查询从表列表/多个表中选择 - MS SQL

使用算术运算优化 MySQL 嵌套选择

使用嵌套选择优化查询

在 C# 中优化多个 SQL 选择

sqlwhere后可以有多个条件后再嵌套吗

使用嵌套选择语句优化 MySQL 查询?