【问题标题】:Using a single SQL correlated sub-query to get two columns使用单个 SQL 相关子查询获取两列
【发布时间】:2011-12-23 04:47:03
【问题描述】:

我的问题由以下查询表示:

SELECT 
  b.row_id, b.x, b.y, b.something,
  (SELECT a.x FROM my_table a WHERE a.row_id = (b.row_id - 1), a.something != 42 ) AS source_x,
  (SELECT a.y FROM my_table a WHERE a.row_id = (b.row_id - 1), a.something != 42 ) AS source_y
FROM 
  my_table b

我使用相同的子查询语句两次,同时获取source_xsource_y。 这就是为什么我想知道是否可以只使用一个子查询来做到这一点?

因为一旦我对我的真实数据(数百万行)运行此查询,它似乎永远不会完成并且需要数小时甚至数天(我的连接在结束前挂断)。

我使用的是 PostgreSQL 8.4

【问题讨论】:

    标签: sql performance postgresql indexing correlated-subquery


    【解决方案1】:

    我认为你可以使用这种方法:

    SELECT b.row_id
         , b.x
         , b.y
         , b.something
         , a.x
         , a.y
      FROM my_table b
      left join my_table a on a.row_id = (b.row_id - 1)
                          and a.something != 42
    

    【讨论】:

      【解决方案2】:

      @DavidEG 发布了查询的最佳语法。

      但是,您的问题绝对不仅仅是查询技术JOIN 而不是两个子查询最多可以将速度提高两倍。很可能更少。这并不能解释“小时”。即使有数百万行,设置得当的 Postgres 也应该在几秒钟内完成简单的查询,而不是几小时。

      • 首先突出的是查询中的语法错误

        ... WHERE a.row_id = (b.row_id - 1), a.something != 42
        

        这里需要ANDOR,而不是逗号。

      • 接下来要检查的是索引。如果row_id 不是主键,你可能没有索引。为了获得此特定查询的最佳性能,请在 (row_id, something) 上创建一个 multi-column index,如下所示:

        CREATE INDEX my_table_row_id_something_idx ON my_table (row_id, something)
        
      • 如果过滤器每次都在something != 42中排除相同的值,您也可以使用partial index 来代替以提高速度:

        CREATE INDEX my_table_row_id_something_idx ON my_table (row_id)
        WHERE something != 42
        

        只有当42 是一个通用值时,这才会产生重大影响 something 是一个比整数更大的列。 (由于数据对齐,具有两个 integer 列的索引在磁盘上的大小通常与只有一个列的索引相同。请参阅:

      • 当性能成为问题时,check your settings 总是一个好主意。 Postgres 中的标准设置在许多发行版中使用最少的资源,并且无法处理“数百万行”。

      • 根据您的实际 Postgres 版本,upgrade to a current version(撰写本文时为 9.1)可能会有很大帮助。

      • 最终,hardware 也始终是一个因素。调优和优化只能让你走到这一步。

      【讨论】:

      • 我尝试了部分索引,然后@DavidEG 查询并很快得到了新表。非常感谢!
      • @JulieFen-Chong:酷。 :) 适合数百万行的索引是必不可少的。
      【解决方案3】:

      老式语法:

      SELECT 
        b.row_id, b.x, b.y, b.something
        , a.x AS source_x
        , a.y AS source
      FROM my_table b
          ,my_table a 
      WHERE a.row_id = b.row_id - 1
        AND a.something != 42
        ;
      

      连接语法:

      SELECT 
        b.row_id, b.x, b.y, b.something
        , a.x AS source_x
        , a.y AS source
      FROM my_table b
      JOIN my_table a 
        ON (a.row_id = b.row_id - 1)
      WHERE a.something != 42
        ;
      

      【讨论】:

      • 您需要LEFT JOIN 才能获得请求的结果。 DavidEG 做到了。
      • 是的,如果没有找到,子查询应该返回 NULL。但是太丑了,我想...
      【解决方案4】:
      SELECT b.row_id, b.x, b.y, b.something, a.x, a.y
        FROM my_table b
        LEFT JOIN (
          SELECT row_id + 1, x, y
            FROM my_table
            WHERE something != 42
        ) AS a ON a.row_id = b.row_id;
      

      【讨论】:

      • 这行得通,但可能很慢,因为你必须在连接之前用something != 42 增加每一行(可能是大多数“数百万行”),这阻碍了标准的使用连接的索引作为副作用。
      • @ErwinBrandstetter 我明白你的意思,我应该将a.row_id = b.row_id - 1 保留在连接条件中。我过于专注于将something != 42 移动到子查询中。
      【解决方案5】:

      Postgres:

          SELECT 
            b.row_id, b.x, b.y, b.something,
            source_x,
            source_y
          FROM 
            my_table b,
      LATERAL(SELECT a.x AS source_x, a.y AS source_y FROM my_table a WHERE a.row_id = (b.row_id - 1), a.something != 42 )
      

      MsSQL

      SELECT 
            b.row_id, b.x, b.y, b.something,
            source_x,
            source_y
          FROM 
            my_table b
      OUTER APPLY(SELECT a.x AS source_x, a.y AS source_y FROM my_table a WHERE a.row_id = (b.row_id - 1), a.something != 42 )
      

      【讨论】:

        猜你喜欢
        • 2020-12-21
        • 1970-01-01
        • 2022-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-10
        相关资源
        最近更新 更多