在 WHERE 子句中引用 SELECT 子查询值

bel*_*dan 1 sql-server subquery sql-server-2012 alias

完整查询

SET @vSelect = 
    '
    SELECT                       
        T1.BranchShortName AS BranchShortName,
        (
            SELECT TT1.EmployeeName
            FROM [dbo].[Tb_OPL_Employee] AS TT1
            JOIN [dbo].[Tb_MKT_SKD] AS TT2
                ON TT2.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
            JOIN [dbo].[Tb_SYS_Approval] AS TT3
                ON TT3.TaskId = TT2.TaskId
            WHERE
                TT3.IdTb_OPL_JobTitles = TT1.IdTb_OPL_JobTitles
                AND TT3.IdTb_OPL_JobTitles = 
                (
                    SELECT MAX(TTT3.IdTb_OPL_JobTitles) 
                    FROM [dbo].[Tb_SYS_Approval] AS TTT3 
                    WHERE 
                        TTT3.IsPassed = 1 
                        AND TTT3.ApprovalLevelCode = ''Approve'' 
                        AND TTT3.TaskId =TT2.TaskId
                )
                AND TT3.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                AND TT2.IdTb_MKT_SKD = T2.IdTb_MKT_SKD
        ) AS LastApproval,
        (
            SELECT TT1.EmployeeName 
            FROM [dbo].[Tb_OPL_Employee] AS TT1
            JOIN [dbo].[Tb_MKT_SKD] AS TT2
                ON TT2.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
            JOIN [dbo].[Tb_SYS_Approval] AS TT3
                ON TT3.TaskId = TT2.TaskId
            WHERE 
                TT1.IdTb_OPL_JobTitles = TT3.IdTb_OPL_JobTitles
                AND TT3.IdTb_OPL_JobTitles = 
                (
                    SELECT MIN(TTT3.IdTb_OPL_JobTitles) 
                    FROM [dbo].[Tb_SYS_Approval] AS TTT3 
                    WHERE
                        TTT3.IsPassed = 0 
                        AND TTT3.ApprovalLevelCode = ''Approve'' 
                        AND TTT3.TaskId =TT2.TaskId
                )
                AND TT3.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                AND TT2.IdTb_MKT_SKD = T2.IdTb_MKT_SKD
        ) AS NextApproval,
        T6.StatusDescription AS Status
    '
SET @vWhere = ' WHERE 1=1 '

IF (@ddlSearchCriteria = '1') 
BEGIN
    -- The condition of search textbox is not empty
    IF (@txtSearch <> '')
    BEGIN
        -- The Condition of StartDate is not empty and EndDate is empty
        IF (@txtStartDate <> '') AND (@txtEndDate = '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate >= CONVERT(SMALLDATETIME, ''' + @txtStartDate + ''', 101)'
        
        -- The Condition of StartDate is empty and EndDate is not empty
        IF (@txtStartDate = '') AND (@txtEndDate <> '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate <= CONVERT(SMALLDATETIME, ''' + @txtEndDate + ''', 101)'
                                      
        -- The Condition of StartDate is not empty and EndDate is not empty
        IF (@txtStartDate <> '') AND (@txtEndDate <> '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate >= CONVERT(SMALLDATETIME, ''' + @txtStartDate + ''', 101)
                                      AND T2.CreatedDate <= CONVERT(SMALLDATETIME, ''' + @txtEndDate + ''', 101)'
        
        IF (@txtStartDate = '') AND (@txtEndDate = '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'''
            
        PRINT @vSelect+@vWhere
    END
Run Code Online (Sandbox Code Playgroud)

如何获取 LastApproval 的子查询值以与 T1.BranchShortName 进行比较?

我尝试将其更改为:

... IF (@txtStartDate <> '') AND (@txtEndDate = '')
            SET @vWhere = @vWhere + ' AND **T1.BranchShortName --> LastApproval** LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%''  ...
Run Code Online (Sandbox Code Playgroud)

但这给了我一条消息,说我的 where 子句中有错误。

ype*_*eᵀᴹ 5

The problem arises because the aliases defined in the SELECT list are not visible by the same level WHERE clause - and the reason behind this is the order of logical execution of queries (which is roughly FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY, omitting some other clauses).

You can bypass this problem with several methods:

  • duplicating the expression code for the alias (the subqueries code here), so the code is both in the SELECT and in the WHERE clause (or in the SELECT and in the HAVING clause, if there is a GROUP BY and the expression uses aggregated values). This might result in a query which looks more complicated that it needs to be - and it may result in errors/inconsistencies later (if a developer changes the subquery/expression only in 1 of the 2 places).

  • move the expression code (subquery in our case) to a CTE. This may not be possible or easy to do in all cases - eg. if the expression is a correlated subquery, like in the specific issue.

  • wrap the whole query into a derived table (or CTE) and move the conditions for those (calculated, aliased) columns to the external WHERE clause. This should work in all cases. It may not be the most efficient though in every case. If the optimizer can "push down" the condition, the plan should be identical to solution 1 (having the code duplicated) as the optimizer can then apply further transformations and choose between different execution paths (in regard with checking the various WHERE conditions and the order of joining tables). If the condition is/can not pushed down, it will have to be evaluated after the derived table which may not be the most efficient in some cases.

  • 使用OUTER APPLY而不是复制代码。仅当表达式不使用聚合值时,这才是可能的。(如果它使用聚合,它将需要更复杂的重写,使用派生表和OUTER APPLY。)
    在我看来这是最好的选择,因为条件已经“下推”到与其余连接和条件相同的级别,我们是确保优化器有更多选项来计划执行。此外,代码仅在一处。

对于您的情况,解决方案 1(重复代码)、3(将整个查询包装在派生表中)和 4(OUTER APPLY)似乎是可行的。该APPLY会是什么样子:

-- the SELECT, with the subqueries removed
SET @vSelect = '
        SELECT    
             T1.BranchShortName AS BranchShortName,
             LA.LastApproval,
             NA.NextApproval,
             T6.StatusDescription AS Status
'
-- the FROM as it was (whatever you have there, doesn't change)
SET @vFrom = '
        FROM dbo.table_1 AS T1
          JOIN dbo.table_2 AS T2
          ON  T1.x = T2.y
'
Run Code Online (Sandbox Code Playgroud)

然后我们将APPLY相关的子查询添加到FROM子句中:

-- OUTER APPLY subqueries added
SET @vFrom = @vFrom + '
          OUTER APPLY
            ( SELECT TT1.EmployeeName AS LastApproval
              FROM [dbo].[Tb_OPL_Employee] AS TT1
                JOIN [dbo].[Tb_MKT_SKD] AS TT2
                ON TT2.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                JOIN [dbo].[Tb_SYS_Approval] AS TT3
                ON TT3.TaskId = TT2.TaskId
              WHERE TT3.IdTb_OPL_JobTitles = TT1.IdTb_OPL_JobTitles
                AND TT3.IdTb_OPL_JobTitles = 
                    ( SELECT MAX(TTT3.IdTb_OPL_JobTitles) 
                      FROM [dbo].[Tb_SYS_Approval] AS TTT3 
                      WHERE TTT3.IsPassed = 1 
                        AND TTT3.ApprovalLevelCode = ''Approve'' 
                        AND TTT3.TaskId = TT2.TaskId)
                AND TT3.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                AND TT2.IdTb_MKT_SKD = T2.IdTb_MKT_SKD
            ) AS LA (LastApproval)
          OUTER APPLY
            ( SELECT TT1.EmployeeName AS NextApproval
              FROM [dbo].[Tb_OPL_Employee] AS TT1
                JOIN [dbo].[Tb_MKT_SKD] AS TT2
                ON TT2.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                JOIN [dbo].[Tb_SYS_Approval] AS TT3
                ON TT3.TaskId = TT2.TaskId
              WHERE TT1.IdTb_OPL_JobTitles = TT3.IdTb_OPL_JobTitles
                AND TT3.IdTb_OPL_JobTitles = 
                    ( SELECT MIN(TTT3.IdTb_OPL_JobTitles) 
                      FROM [dbo].[Tb_SYS_Approval] AS TTT3 
                      WHERE TTT3.IsPassed = 0 
                        AND TTT3.ApprovalLevelCode = ''Approve'' 
                        AND TTT3.TaskId = TT2.TaskId)
                AND TT3.IdTb_OPL_Branch = TT1.IdTb_OPL_Branch
                AND TT2.IdTb_MKT_SKD = T2.IdTb_MKT_SKD
            ) AS NA (NextApproval)
'
Run Code Online (Sandbox Code Playgroud)

现在,我们可以在WHERE子句中使用子查询结果:

SET @vWhere = ' WHERE 1=1 '

IF (@ddlSearchCriteria = '1') 
BEGIN
    -- The condition of search textbox is not empty
    IF (@txtSearch <> '')
    BEGIN
        -- The Condition of StartDate is not empty and EndDate is empty
        IF (@txtStartDate <> '') AND (@txtEndDate = '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate >= CONVERT(SMALLDATETIME, ''' + @txtStartDate + ''', 101)'

        -- The Condition of StartDate is empty and EndDate is not empty
        IF (@txtStartDate = '') AND (@txtEndDate <> '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate <= CONVERT(SMALLDATETIME, ''' + @txtEndDate + ''', 101)'

        -- The Condition of StartDate is not empty and EndDate is not empty
        IF (@txtStartDate <> '') AND (@txtEndDate <> '')
            SET @vWhere = @vWhere + ' AND T1.BranchShortName LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'' 
                                      AND T2.CreatedDate >= CONVERT(SMALLDATETIME, ''' + @txtStartDate + ''', 101)
                                      AND T2.CreatedDate <= CONVERT(SMALLDATETIME, ''' + @txtEndDate + ''', 101)'

        -- we can use LA.LastApproval as any other column
        -- and the OUTER APPLY becomes CROSS APPLY
        IF (@txtStartDate = '') AND (@txtEndDate = '')
            SET @vWhere = @vWhere + ' AND LA.LastApproval LIKE ''%' + ltrim(rtrim(@txtSearch)) + '%'''

        PRINT @vSelect + @vFrom + @vWhere + ' ;'
    END
Run Code Online (Sandbox Code Playgroud)