如何在 Oracle SQL 语句中重用动态列?

Posted

技术标签:

【中文标题】如何在 Oracle SQL 语句中重用动态列?【英文标题】:How to reuse dynamic columns in an Oracle SQL statement? 【发布时间】:2009-04-13 12:35:33 【问题描述】:

我尝试重用一些我在 Oracle SQL 中动态计算的列,例如

SELECT
    A*2 AS P,
    P+5 AS Q
FROM tablename

“tablename”有一个名为“A”的列,但没有其他列。这给了我一个

ORA-00904: "P": invalid identifier

我知道如何使用子查询来解决这个问题

SELECT P, P+5 AS Q
FROM ( SELECT A*2 AS P FROM tablename )

但我觉得这有点丑。此外,我想让查询更复杂一些,例如也重用“Q”,我不想再创建另一个子查询。

更新:我想存储'P'的计算的原因是我想让它更复杂,并多次重复使用'P'。所以我不想明确地说“A*2+5 AS Q”,因为随着“P”变得越来越复杂,这很快就会变得很麻烦。

一定有一个好方法可以做到这一点,有什么想法吗?

更新:我应该注意我不是数据库管理员 :(.


更新:一个真实世界的例子,有一个更具体的查询。我想做的是:
SELECT 
    SL/SQRT(AB) AS ALPHA,
    5*LOG(10,ALPHA) AS B,
    2.5*LOG(10,1-EXP(-5/ALPHA)*(5/ALPHA+1)) AS D
    BS -2.74 + B + D AS BSA
FROM tablename

现在,我已经把它写出来了,它有效,但是很丑:

SELECT
    SL/SQRT(AB) AS ALPHA,
    5*LOG(10,SL/SQRT(AB)) AS B,
    2.5*LOG(10,1-EXP(-5/(SL/SQRT(AB)))*(5/(SL/SQRT(AB))+1)) AS D
    BS -2.74 + 5*LOG(10,SL/SQRT(AB)) + 2.5*LOG(10,1-EXP(-5/(SL/SQRT(AB)))*((5/(SL/SQRT(AB)))+1)) AS BSA
FROM tablename

收到数据后我可以做所有这些,但我想,让我们看看我可以让数据库做多少。另外,我也想选择“BSA”(我现在可以将此查询作为子查询/with-clause 来完成)。


更新:好的,我想现在我完成了 Cade Roux 和 Dave Costa 的解决方案。尽管 Pax 和 Jens Schauder 的解决方案看起来会更好,但我不能使用它们,因为我不是 DBA。现在我不知道该将谁标记为最佳答案:)。

WITH 
  A1 AS ( 
    SELECT A0.*, 
    SL/SQRT(AB) AS ALPHA
    FROM tablename A0
  ),
  A2 AS (
    SELECT A1.*, 
    5*LOG(10,ALPHA) AS B,
    2.5*LOG(10,1-EXP(-5/ALPHA)*((5/ALPHA)+1)) AS D
    FROM A1
  )
SELECT
  ALPHA, B, D, BS,
  BS -2.74 + B + D AS BSA
FROM A2

顺便说一句,如果有人感兴趣,SB 是星系的“表面亮度”,B 和 D 是修正项。

【问题讨论】:

【参考方案1】:

我们在 SQL Server 中遇到了同样的问题(这是一个 ANSI 问题)。我相信这是为了避免混淆混叠效果:

SELECT A * 2 AS A
    ,A * 3 AS B -- This is the original A, not the new A
FROM whatever

我们通过堆叠公用表表达式来解决这个问题:

WITH A1 AS (
    SELECT A * 2 AS A
    FROM whatever
)
,A2 AS (
    SELECT A1.*
        ,A * 3 AS B
    FROM A1
)
,A3 AS (
    SELECT A2.*
        ,A + B AS X
    FROM A2
)
SELECT *
FROM A3

这是最易读、最易维护、最易遵循的版本。

对于更新,有一个使用 column_name = 表示法的已弃用 SQL Server 解决方法,您可以在其中引用列表中先前已更新的列。但这不能在 SELECT 中使用。

我希望将来某个时候将一些堆叠表达式的功能(不使用标量 UDF)添加到 ANSI SQL。

【讨论】:

哈哈,不过确实有副作用!因为我第一次使用“T”和“S”作为我的新列,它们确实存在!所以没有错误,只有完全不正确的值。 (200 多列,全部 2-5 个字符...) 结果在列顺序变化下是不变的。希望 ANSI 能够以某种内联堆栈指令的形式提供一些缓解。【参考方案2】:

我不确定您是否可以做到这一点(我从未见过这样做过),但您可以通过以下方式解决它:

SELECT
    A*2   AS P,
    A*2+5 AS Q
FROM tablename

这肯定比引入子查询要好。

我建议的唯一其他方法是创建一个视图,为您提供 P/Q 类型的列(使用上面的公式),这至少可以简化查询的文本。然后你可以:

SELECT P, Q FROM viewintotablename

【讨论】:

是的,我把示例中的查询做得很简单,实际上我多次使用值 P 来计算其他列,而 P 稍微复杂一些。我会更新问题。 我不完全确定您为什么如此关注查询。它们往往只写一次然后就不管了,所以它们有多“丑陋”并不重要(假设真正丑陋的有正确的记录:-)。 我知道 DBA 所关心的唯一 是原始速度,我认为如果不进行非规范化,您将无法轻松修复(即,在 DB2 中引入生成的列)。 我关心的是可读性和正确性,而不是速度。但我的主要目标是学习,我想看看我是否可以将一些简单的计算卸载到数据库中。 视图会这样做,存储过程也会这样做。对于简单的情况,您会发现视图可能更容易。如果您按 A 排序,则可以优化存储过程以使用某些列中的前行数据(假设 A 相同)。我在 DB2 中做过这个,Oracle 我不知道。【参考方案3】:

在 sql 中没有直接的方法可以做到这一点。

但是您可以使用 PL/SQL 定义一个函数。所以你的选择看起来像这样

select 
    P(A), 
    Q(P(A)) 
from tablename

对于 P 和 Q,这并不(很多)比原来的好,但如果函数很复杂,并且不需要太多参数,它可能会使您的语句更具可读性。

它还允许您独立于 sql 语句和任何数据来测试您的函数。

【讨论】:

我以前从未在 Oracle 中创建过函数,显然我没有足够的权限这样做。 (我不会经常需要这些查询来保证 DB 范围的功能。)【参考方案4】:

你可能会比你给出的内联视图示例更喜欢这个:

WITH inner_view AS ( SELECT A*2 AS P FROM tablename )
SELECT P, P+5 AS Q
FROM inner_view

我认为它相当于同一件事,但阅读起来更清晰一些。

如果计算列是您将在多个列中使用的东西,那么创建一个永久视图可能是有意义的。

Oracle 11(我还没用过)有一个虚拟列功能,可能对你有用。

【讨论】:

确实更具可读性。我相信它是一个 11g 的数据库,但我不是 DBA,我似乎需要为虚拟列。【参考方案5】:

你不能。

如果您不希望重新评估子查询,请为子查询添加NO_MERGE 提示:

这个子查询将在嵌套循环中重新计算(使用MERGE 提示):

SELECT  /*+ LEADING(g) USE_NL(g, r) MERGE(g) */
        *
FROM    (
        SELECT  1
        FROM    dual
        UNION ALL
        SELECT  2
        FROM    dual
        ) r, 
        (
        SELECT  SYS_GUID() AS guid
        FROM    dual d
        ) g

---
33CA48C1AB6B4403808FB0219302CE43
711BB04F9AFC406ABAEF8A8F4CFA1266

这个子查询不会在嵌套循环中重新计算(使用NO_MERGE 提示):

SELECT  /*+ LEADING(g) USE_NL(g, r) NO_MERGE(g) */
        *
FROM    (
        SELECT  1
        FROM    dual
        UNION ALL
        SELECT  2
        FROM    dual
        ) r, 
        (
        SELECT  SYS_GUID() AS guid
        FROM    dual d
        ) g

------
7715C69698A243C0B379E68ABB55C088
7715C69698A243C0B379E68ABB55C088

在你的情况下,只需写:

SELECT  BS - 2.74 + d
FROM    (
        SELECT  t2.*, 2.5 * LOG(10, 1 - EXP(-5 / b)) * ((5 / A) + 1) AS d
        FROM    (
                SELECT  t1.*, 5 * LOG(10, alpha) AS b
                FROM    (
                        SELECT  /*+ NO_MERGE */ t.*,
                                SL/SQRT(AB) AS alpha
                        FROM    tablename t
                        ) t1
                ) t2
        ) t3

,效率更高(EXPLOG 成本高)并且更容易调试。

【讨论】:

我不确定这如何适用,但我会记住这一点。 (是否重新评估只是速度问题。)【参考方案6】:

别名重用在 Teradata 中很常见,但有时可能会造成混淆,主要是当您使用表/子查询中存在的名称为列名取别名并尝试重用它时,数据库将使用原始列而不是您的列别名。

【讨论】:

以上是关于如何在 Oracle SQL 语句中重用动态列?的主要内容,如果未能解决你的问题,请参考以下文章

如何用oracle动态查询一张表里面的某些列?

如何在oracle存储过程中执行动态sql语句

如何在Select语句中声明SQL oracle中的null列

在 Oracle 的动态 SQL 中使用 BLOB 类型

如何在VB中用SQL语句动态的增加字段.

如何从 oracle pl sql 中的包主体内的 select 语句中捕获特定列