在 SQL Server 2012 中使用别名列

Working with Aliased Columns in SQL Server 2012

好的,所以我从 this 问题中知道为什么我不能从 WHEREGROUP BYHAVING 语句中引用别名列。

我的问题是我有这个查询正在从 Teradata 数据库移动到 SQL Server 2012。

在 Teradata 中,在 where、group by、having 甚至 join 语句中引用别名列是完全有效的。

我的问题是,我如何才能在 SQL 服务器中执行此查询以及其他类似的查询,而不必首先从 table 填充临时 select . (这个例子只是包含 10 个独立交易的大型 tSQL 脚本的一部分,其中许多交易比提供的例子更复杂)

SELECT 
    MAX(CASE WHEN 
            Field_Name = 'Parent Brand Cd' AND 
            DATALENGTH(Field_Val)>1 
        THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
        ELSE NULL 
        END
        ) AS Parent_Brand_Cd,
    MAX(CASE WHEN 
            Field_Name = 'Brand Id' AND 
            DATALENGTH(Field_Val)>1 
        THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
        ELSE NULL 
        END
        ) AS Hotel_Cd,
    @src_sys_id AS Src_Sys_Id,
    MAX(CASE WHEN 
            Field_Name = 'Brand' AND 
            DATALENGTH(Field_Val)>1 
        THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
        ELSE NULL 
        END
        ) AS Temp1,
    CASE Temp1 
        WHEN 'Company1' 
            THEN 'c1'
        WHEN 'Company2' 
            THEN 'c2' 
        WHEN 'Company3' 
            THEN 'c3' 
        ELSE TEmp1 
    END AS Brand_Cd,
    @process_id AS Insert_Process_Id
 FROM dbo.Company -- STAGING
 GROUP BY Parent_Brand_Cd 
 HAVING 
    Parent_Brand_Cd IS NOT NULL AND 
    Hotel_Cd IS NOT NULL

此查询的第一个问题是它正在创建别名列 Temp1,然后立即尝试对其执行 CASE 语句。我可以通过这样做来纠正这个问题:

MAX(CASE WHEN 
            Field_Name = 'Brand' AND 
            DATALENGTH(Field_Val)>1 
        THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
        ELSE NULL 
        END
        ) AS Temp1,
    MAX(CASE WHEN 
            Field_Name = 'Brand' AND 
            DATALENGTH(Field_Val)>1 
        THEN 
            CASE 
                WHEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) = 'Company1' THEN 'c1'
                WHEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) = 'Company2' THEN 'c2'
                WHEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) = 'Company' THEN 'c3'
                ELSE SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1)
            END
        ELSE NULL 
        END
        ) AS Brand_Cd,

但这对脚本的其他部分没有帮助,其中别名列是计算,然后用于其他计算。此外,它没有解决 Group ByHaving 语句中别名列的问题。

有什么方法可以绕过 SQL 服务器中别名列的限制,而不必到处创建和填充临时 table?

编辑:GarethD

提出的可行解决方案
SELECT  
            Parent_Brand_Cd,
            Hotel_Cd,
            @src_sys_id AS Src_Sys_Id,
            CASE Temp1 
                WHEN 'Company1' THEN 'C1'
                WHEN 'Company2' THEN 'C2' 
                WHEN 'Company3' THEN 'C3' 
                ELSE TEmp1 
            END AS Brand_Cd,
            @process_id AS Insert_Process_Id,
            @process_id AS Update_Process_Id
        FROM    
            (   
                SELECT  
                    MAX(CASE WHEN Field_Name = 'Parent Brand Cd' THEN FieldValue END) AS Parent_Brand_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand Id' THEN FieldValue END) AS Hotel_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand' THEN FieldValue END) AS Temp1
                FROM    
                    (
                        SELECT  
                            Field_Name,
                            FieldValue = CASE WHEN DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END
                        FROM    DEV_STG_TB.dbo.Company_Attributes_3 -- STAGING
                    ) AS c

            ) AS sub
        WHERE   
            Parent_Brand_Cd IS NOT NULL AND 
            Hotel_Cd IS NOT NULL
        GROUP BY 
            Parent_Brand_Cd,
            Hotel_Cd,
            Temp1

您可以将查询移动到子查询中,并引用您的别名。 SQL 服务器足够聪明,能够以与 having 子句相同的方式对其进行优化(至少在我做过的测试中)。考虑以下两个查询:

SELECT  Name, [Count]
FROM    (   SELECT  name, [Count] = COUNT(*)
            FROM    sys.Columns
            GROUP BY name
        ) AS sub
WHERE   [Count] > 1;

SELECT  name, [Count] = COUNT(*)
FROM    sys.Columns
GROUP BY name
HAVING COUNT(*) > 1;

两个查询的执行计划完全一样:

所以你的查询最终会像这样:

SELECT  Parent_Brand_Cd,
        Hotel_Cd,
        @src_sys_id AS Src_Sys_Id,
        Temp1
        CASE Temp1 
            WHEN 'Company1' THEN 'c1'
            WHEN 'Company2' THEN 'c2' 
            WHEN 'Company3' THEN 'c3' 
            ELSE TEmp1 
        END AS Brand_Cd,
        @process_id AS Insert_Process_Id
FROM    (   SELECT  MAX(CASE WHEN Field_Name = 'Parent Brand Cd' AND DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END) AS Parent_Brand_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand Id' AND DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END) AS Hotel_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand' AND DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END) AS Temp1
            FROM    dbo.Company -- STAGING
            GROUP BY Parent_Brand_Cd 
        ) AS sub
WHERE   Parent_Brand_Cd IS NOT NULL 
AND     Hotel_Cd IS NOT NULL;

您甚至可以使用另一个子查询进一步减少重复的表达式:

SELECT  Parent_Brand_Cd,
        Hotel_Cd,
        @src_sys_id AS Src_Sys_Id,
        Temp1
        CASE Temp1 
            WHEN 'Company1' THEN 'c1'
            WHEN 'Company2' THEN 'c2' 
            WHEN 'Company3' THEN 'c3' 
            ELSE TEmp1 
        END AS Brand_Cd,
        @process_id AS Insert_Process_Id
FROM    (   SELECT  MAX(CASE WHEN Field_Name = 'Parent Brand Cd' THEN FieldValue END) AS Parent_Brand_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand Id' THEN FieldValue END) AS Hotel_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand' THEN FieldValue END) AS Temp1
            FROM    (   SELECT  Parent_Brand_Cd,
                                Field_Name,
                                FieldValue = CASE WHEN DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END
                        FROM    dbo.Company -- STAGING
                    ) AS c
            GROUP BY Parent_Brand_Cd 
        ) AS sub
WHERE   Parent_Brand_Cd IS NOT NULL 
AND     Hotel_Cd IS NOT NULL;

注意,我从 case 表达式中删除了 ELSE NULL,因为这是多余的。

我非常喜欢使用Common Table Expressions instead of subqueries to de-clutter my queries (this is entirely subjective), and also using PIVOT,所以我个人将上面的代码重写为:

WITH CompanyCTE AS
(   SELECT  Parent_Brand_Cd,
            Field_Name,
            FieldValue = CASE WHEN DATALENGTH(Field_Val) > 1 
                            THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
                        END
    FROM    dbo.Company 
)
SELECT  pvt.Parent_Brand_Cd
        Parent_Brand_Cd = pvt.[Parent Brand Cd],
        Hotel_Cd = pvt.[Brand Id],
        Temp1 = pvt.[Brand]
FROM    CompanyCTE AS c
        PIVOT
        (   MAX(FieldValue)
            FOR Field_Name IN ([Parent Brand Cd], [Brand Id], [Brand])
        ) AS pvt
WHERE   pvt.[Parent Brand Cd] IS NOT NULL
AND     pvt.[Brand Id] IS NOT NULL;

另一个优点是 PIVOT 使您可以直接访问聚合列。

当然,您的另一个选择是重复聚合函数:

HAVING MAX(CASE WHEN 
                Field_Name = 'Parent Brand Cd' AND 
                DATALENGTH(Field_Val)>1 
            THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
            ELSE NULL 
            END
            ) IS NOT NULL
AND    MAX(CASE WHEN 
                Field_Name = 'Parent Brand Cd' AND 
                DATALENGTH(Field_Val)>1 
            THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
            ELSE NULL 
            END
            ) IS NOT NULL;

附录

看到有效的解决方案后,您似乎根本不需要任何分组,所以我认为以下内容对您有用:

SELECT  Parent_Brand_Cd,
        Hotel_Cd,
        @src_sys_id AS Src_Sys_Id,
        Temp1
        CASE Temp1 
            WHEN 'Company1' THEN 'c1'
            WHEN 'Company2' THEN 'c2' 
            WHEN 'Company3' THEN 'c3' 
            ELSE TEmp1 
        END AS Brand_Cd,
        @process_id AS Insert_Process_Id
FROM    (   SELECT  MAX(CASE WHEN Field_Name = 'Parent Brand Cd' THEN FieldValue END) AS Parent_Brand_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand Id' THEN FieldValue END) AS Hotel_Cd,
                    MAX(CASE WHEN Field_Name = 'Brand' THEN FieldValue END) AS Temp1
            FROM    (   SELECT  Field_Name,
                                FieldValue = CASE WHEN DATALENGTH(Field_Val) > 1 THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) END
                        FROM    dbo.Company -- STAGING
                    ) AS c
        ) AS sub
WHERE   Parent_Brand_Cd IS NOT NULL 
AND     Hotel_Cd IS NOT NULL;

原因是因为 parent_brand_cd 根据定义是唯一的,因为它是从没有分组的聚合派生的,任何进一步的分组虽然无关紧要,但都是多余的。

或 PIVOT 解决方案。

WITH CompanyCTE AS
(   SELECT  Field_Name,
            FieldValue = CASE WHEN DATALENGTH(Field_Val) > 1 
                            THEN SUBSTRING(Field_Val, 1, DATALENGTH(Field_Val) - 1) 
                        END
    FROM    dbo.Company 
)
SELECT  Parent_Brand_Cd = pvt.[Parent Brand Cd],
        Hotel_Cd = pvt.[Brand Id],
        Src_Sys_Id = @src_sys_id,
        Temp1 = pvt.[Brand],
        Brand_Cd = CASE pvt.[Brand] 
                        WHEN 'Company1' THEN 'c1'
                        WHEN 'Company2' THEN 'c2' 
                        WHEN 'Company3' THEN 'c3' 
                        ELSE TEmp1 
                    END,
        Insert_Process_Id = @process_id 
FROM    CompanyCTE AS c
        PIVOT
        (   MAX(FieldValue)
            FOR Field_Name IN ([Parent Brand Cd], [Brand Id], [Brand])
        ) AS pvt
WHERE   pvt.[Parent Brand Cd] IS NOT NULL
AND     pvt.[Brand Id] IS NOT NULL;