【问题标题】:SQL SELECT statement expression value reuse for other expressionSQL SELECT 语句表达式值重用于其他表达式
【发布时间】:2012-08-19 11:21:56
【问题描述】:

我在 mysql 中有一个包含大量行数的表(尽管我正在寻找通用 SQL 解决方案)

very_big_table(INT a, INT b, INT c, ...)

我想要 SELECT 语句

SELECT a, 
    (b + c) as expression1,
    (b + c + a) AS expression2 -- basically (expression1 + a)
FROM very_big_table
WHERE ...
GROUP BY a
ORDER BY a DESC

只要表达式 1 简单,这看起来不错且易于阅读。
但是当 CASE-WHEN/IFNULL()/SUM()/MIN()/STRCAT() 或某些运算符在这些表达式中发挥作用时,它很难阅读和调试。

我已经解决了一些已经提出的问题
assigning mysql value to variable inline
Use value of a column for another column (SQL Server)?
How to use conditional columns values in the same select statement?

但如果我使用描述的方法

SELECT a, 
    expression1,
    (expression1 + a) AS expression2
FROM 
    (SELECT a,
        (b + c) AS expression1
    FROM very_big_table
    WHERE ...
    GROUP BY a) as inner_table
ORDER BY a DESC

这可以正常工作,但此查询的执行时间要多 70 倍。至少当我解雇它时,虽然只有一次。
如果我在输出列中有多个级别的 表达式 怎么办?

有什么优雅的方法来处理这个问题,而不影响可读性?

顺便说一句,为什么 SQL 标准或供应商不支持 select 语句中的这个 表达式重用别名引用? (假设在单个 SELECT 语句表达式中没有循环求值。在这种情况下编译器会失败)

【问题讨论】:

  • 为什么您认为能够重用表达式别名会导致执行时间减少 70 倍?你能用真实的查询和 .sqlplan 文件来展示一个真实的例子吗?我几乎可以保证,执行时间的这种差异不是由于添加了子查询——其他的东西肯定是不同的。
  • @Quail 仍然无法设想这样一种情况,即这是唯一的区别,它会导致 70 倍的性能差异。
  • 在 Aaron 编辑他的评论后,我删除了我的评论。对于后代,它说:“我想他正在比较它,明确指定每一列。”
  • 这个非常大的表有没有主键?它是什么?

标签: mysql sql sql-server


【解决方案1】:

您可以使用用户定义的变量来解决您的问题。您的 SQL 可以重写为:

SELECT a, 
    @expr1 := (b + c) as expression1,
    (@expr1 + a) AS expression2
FROM very_big_table
WHERE ...
GROUP BY a
ORDER BY a DESC

您可以参考this post

【讨论】:

    【解决方案2】:

    我有两个选择..

    使用您的实际查询作为子查询

     select a,exp1,exp1+a as exp2
     from (SELECT a 
                 ,(b + c) as exp1
           FROM very_big_table
           WHERE ...
           GROUP BY a
     )V
     ORDER BY a DESC
    

    或为您的查询添加外部应用语句...

    SELECT a 
          ,OA.exp1 as expression1
          ,(OA.exp1 + a) AS expression2 -- basically (expression1 + a)
    FROM very_big_table
    outer apply (select (b + c) as exp1) OA
    WHERE ...
    GROUP BY a
    ORDER BY a DESC
    

    我猜第二个选项,使用外部应用,更适合阅读...

    请记住,每行都运行外部应用程序

    因此,如果 exp1 必须从表中访问大量数据,这可能是个坏主意。

    无论如何,只需使用您在实际查询中已经获得的字段 添加它不会有很大的成本。

    那么..你会选择哪种方式?

    【讨论】:

    • 为什么是outer apply 而不是cross apply
    【解决方案3】:

    解决方法假设表达式不是 sql select 子句,而是嵌套的运算符/函数,纯粹作用于在原始“select from”子句中检索到的数据,因此手头的任务纯粹是为了让代码“看起来漂亮”: 创建用户定义函数

    那么您的查询将如下所示

    select a
    , myfunction( b, c)
    , myfunction( b, c) + a
    ...
    

    优势 - 这会清理您既定目标的“选择代码”,您也可以集中管理更复杂的逻辑

    缺点 - 这不是将移植到其他系统的“通用 sql”,如果函数很少被重用,可能不值得花时间投资

    【讨论】:

      【解决方案4】:

      对于 Postgresql 用户

      如果您想最大限度地提高复杂计算和/或逻辑的可读性,请将计算封装在自己的函数中

      create table x
      (
          a int not null,
          b int not null,
          c int not null
      );
      
      create table y
      (
          a int not null,
          z int not null
      );
      
      
      select * from x;
      
      insert into x VALUES
      (1,2,3), (4,5,6);
      
      insert into y values
      (1, 100);
      

      具有复杂计算和/或逻辑的函数:

      create or replace function computer
      (
          rx x, -- this emphasizes that the source of data come from table x
          ry y, -- this emphasizes that the source of data come from table x
      
          out expression1 int, out expression2 int, out expression3 int
      )
      as $$
      begin
          expression1 := rx.b + rx.c;
          expression2 := expression1 + rx.a;
          expression3 := expression2 + ry.z;
      end;
      $$ language 'plpgsql';
      

      测试查询:

      select x.a, x.b, x.c, y.z, answer.*
      from x
      join y on x.a = y.a
      cross join lateral computer(x,y) answer 
      

      输出:

      如果只需要快速测试函数的返回值,可以使用括号-星号语法进行扩展:

      select x.a, x.b, x.c, y.z, (computer(x, y)).*
      from x
      join y on x.a = y.a
      

      不要在生产环境中使用它,如果函数有 50 个返回列,则该函数将被调用 50 次。在生产中使用cross join lateral。见:How can you expand a "condensed" PostgreSQL row into separate columns?

      如果您的函数不需要关心数据源来自哪里,只需在函数参数上使用record 类型

      create or replace function computer_b
      (
          anon record,
          out expression1 int, out expression2 int, out expression3 int
      )
      as $$
      begin
          expression1 := anon.b + anon.c;
          expression2 := expression1 + anon.a;
          expression3 := expression2 + anon.z;
      end;
      $$ language 'plpgsql';  
      

      测试查询:

      with data_source as
      (
          select x.*, y.z
          from x
          join y on x.a = y.a
      )
      select ds.*, answer.*
      from data_source ds
      cross join lateral computer_b(ds) answer
      

      【讨论】:

        猜你喜欢
        • 2011-05-18
        • 2016-10-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-28
        • 2021-12-27
        • 2016-12-19
        相关资源
        最近更新 更多