以多种方式在 SQL 查询中多次使用复杂语句

Posted

技术标签:

【中文标题】以多种方式在 SQL 查询中多次使用复杂语句【英文标题】:Using complex statements in SQL queries multiple times in multiple ways 【发布时间】:2015-05-04 07:45:48 【问题描述】:

我正在设计一个 Microsoft SQL Server 2008 上的 View,它应该建立在相当多的业务逻辑之上,这意味着有很多 CASE WHEN THEN ELSE 语句。问题是,一个CASE 语句的结果通常在查询的其他地方需要,例如函数、连接和其他情况。这使得代码非常臃肿、难以阅读和维护。

以下是此类视图的示例(字段和功能仅是说明性的):

SELECT

    -- Random complicated case
    CASE
        WHEN A=B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
        THEN D-A
        WHEN A<>B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
        THEN D-A - 5
        WHEN A=B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
        THEN D-A - 10
        WHEN A<>B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
        THEN D-A - 15
    END AS ComplicatedCase

    -- Use of that same complicated case in another case
    CASE
        WHEN CASE
                WHEN A=B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A
                WHEN A<>B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 5
                WHEN A=B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 10
                WHEN A<>B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 15
             END > 300
        THEN CASE
                WHEN A=B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A
                WHEN A<>B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 5
                WHEN A=B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 10
                WHEN A<>B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 15
             END 
        ELSE NULL
    END AS AnotherCase

FROM SomeTable AS T
    -- Complicated case in join
    INNER JOIN AnotherTable AS AT
        ON  AT.ID = CASE
                        WHEN A=B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                        THEN D-A
                        WHEN A<>B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                        THEN D-A - 5
                        WHEN A=B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                        THEN D-A - 10
                        WHEN A<>B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                        THEN D-A - 15
                    END

代码很长,不可读,如果需要更改业务逻辑,我更有可能忘记更改。

我想到的明显解决方案是在子选择中添加案例,通过INNER JOINCROSS APPLY 加入,如下所示:

-- Solution by CROSS APPLY sub-select
SELECT
    T1.ComplicatedCase,
    CASE
        WHEN ComplicatedCase > 300
        THEN ComplicatedCase
        ELSE NULL
    END AS AnotherCase

FROM SomeTable AS T
    CROSS APPLY
        (
        SELECT
            CASE
                WHEN A=B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A
                WHEN A<>B AND CAST(C AS int)=D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 5
                WHEN A=B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 10
                WHEN A<>B AND CAST(C AS int)<>D AND DATEDIFF(DAY,E,F) > 5
                THEN D-A - 15
            END AS ComplicatedCase
        FROM SomeTable AS ST
        WHERE T.ID = ST.ID
        ) AS T1

    INNER JOIN AnotherTable AS AT ON AT.ID = T1.ID

...而且效果很好,但我不知道查询性能是否不会大规模受到影响(因为我不太了解引擎如何在内部处理这些东西)。

对于如何处理此类复杂语句,您有任何最佳实践吗?如果它在一个简单的连接子选择中,它甚至对 SQL 引擎有影响吗?

PS:我的case语句通常由几个函数组成,主要是datetime的处理和转换。

【问题讨论】:

【参考方案1】:

我认为你不必CROSS APPLY你的SomeTable两次。

这应该可行:

SELECT T1.ComplicatedCase
    , CASE
        WHEN ComplicatedCase > 300 THEN ComplicatedCase
        ELSE NULL
    END AS AnotherCase
FROM SomeTable AS T
CROSS APPLY (
    SELECT DATEDIFF(DAY, T.E, T.F)
    ) AS TT(DayDiff)
CROSS APPLY (
    SELECT CASE
            WHEN T.A = T.B  AND CAST(T.C AS INT) = T.D  AND TT.DayDiff > 5 THEN D - A
            WHEN T.A <> T.B AND CAST(T.C AS INT) = T.D  AND TT.DayDiff > 5 THEN D - A - 5
            WHEN T.A = T.B  AND CAST(T.C AS INT) <> T.D AND TT.DayDiff > 5 THEN D - A - 10
            WHEN T.A <> T.B AND CAST(T.C AS INT) <> T.D AND TT.DayDiff > 5 THEN D - A - 15
        END
    ) AS T1(ComplicatedCase)
INNER JOIN AnotherTable AS AT
    ON AT.ID = T1.ComplicatedCase;

CROSS APPLY 允许您创建计算值并在JOINSWHERE 语句等中使用它们。它使代码更具可读性,并且不应该为您花费额外的资源。

如果有什么不清楚或不符合您的标准 - 请告诉我。

【讨论】:

嗨@Evaldas,除了正确的表引用之外,我认为您的解决方案和我的解决方案没有太大区别。但我喜欢只为 DATEDIFF 使用 CROSS APPLY 的想法。这对 SQL 引擎有影响还是只是为了可读性?另外,将别名分配给带括号的子选择是什么意思?像这样:AS TT(DayDiff)...我以前没见过这个。谢谢! 嘿@MarekStejskal,我看到了不同之处,您在不需要时在SomeTable 之间进行了两次引用(至少对我来说看起来是这样),这是额外费用,请参见此处:prntscr.com/71csl1。它不应该对 SQL Server 引擎产生任何显着影响,它只是保持可读性。对于您的第二个问题 - 这只是分配别名的另一种方式。我习惯于为CROSS APPLYOUTER APPLY 这样做。

以上是关于以多种方式在 SQL 查询中多次使用复杂语句的主要内容,如果未能解决你的问题,请参考以下文章

php pdo预处理语句与存储过程

Laravel操作数据库 - 原生SQL语句

SQL基础教程(第2版)第5章 复杂查询:5-1 视图和表

数据库语句

sql语句 分次(多次)获取不重复记录,请高手赐教!

在 C#/.NET3.5 中构造动态 sql 查询的最佳方式?