Как использовать конкретную таблицу в соединении при условии?

#sql #sql-server #tsql

Вопрос:

Я хочу написать хранимую процедуру, включающую входные параметры, временные таблицы и объединения.

У меня есть AllocationDetails временная таблица в этой процедуре:

 ;WITH AllocationDetails AS
(
    SELECT
        lastPaymentAllocationDetails.*
    FROM   
        (SELECT 
             *, 
             ROW_NUMBER() OVER (PARTITION BY [PaymentAllocationGuid] ORDER BY [Year] DESC, [WorkingPeriodTitle] DESC) rowNumber
         FROM 
             ret_vwPaymentAllocationDetails) lastPaymentAllocationDetails
WHERE lastPaymentAllocationDetails.rowNumber = 1
),
 

Тогда я хотел бы иметь еще одну временную таблицу — вот так:

 ValidPaymentAllocations AS
(
    SELECT
        paymentAllocationDetails.[PaymentAllocationGuid],
        paymentAllocation.[RetiredPersonnelGuid],
        MAX(paymentAllocationDetails.ID) AS MaxPaymentAllocationDetailID,
        MAX(paymentAllocationDetails.[Year] * 100   paymentAllocationDetails.[WorkingPeriodTitle]) AS [WorkingPeriodKey]
    FROM
        ret_PaymentAllocation AS paymentAllocation 
    INNER JOIN
        CASE when(@PaymentCommandType = 1) THEN ret_PaymentAllocationDetails  ELSE AllocationDetails END AS paymentAllocationDetails ON paymentAllocation.[Guid] = paymentAllocationDetails.[PaymentAllocationGuid] 
    INNER JOIN
        ret_PaymentTypes AS paymentTypes ON paymentAllocation.[PaymentTypesGuid] = paymentTypes.[Guid] 
    INNER JOIN
        (SELECT MAX(ID) ID FROM ret_PaymentAllocationDetails GROUP BY PaymentAllocationGuid,WorkingPeriodTitle,Year) lastRecord ON LastRecord.Id = paymentAllocationDetails.ID
    WHERE
        ISNULL(paymentAllocation.[Deleted],0) = 0 AND
        paymentAllocation.[RetiredPersonnelGuid] = @SalariedGuid AND
        (paymentAllocationDetails.[Year]  * 100   paymentAllocationDetails.[WorkingPeriodTitle] <= @Year * 100   @WorkingPeriodID AND paymentAllocationDetails.[Status] = 1 AND paymentTypes.[PaymentTypeFlag] <> 3 OR
         paymentAllocationDetails.[Year]  * 100   paymentAllocationDetails.[WorkingPeriodTitle] = @Year * 100   @WorkingPeriodID AND paymentTypes.[PaymentTypeFlag] = 3) AND
        (paymentAllocation.[PaymentCommandType] = @PaymentCommandType OR paymentAllocation.[PaymentTypesGuid] IN (SELECT * FROM com_udfSplit(@PaymentTypeGuids,','))) AND
        ((paymentTypes.[AffectOnAmountNature] = 1) OR (paymentTypes.[AffectOnAmountNature] = 2 AND paymentAllocation.[Amount] > 0))
    GROUP BY paymentAllocation.[RetiredPersonnelGuid], paymentAllocationDetails.[PaymentAllocationGuid]
),
 

Но в разделе «Присоединиться» при одном условии @PaymentCommandType = 1 я хочу использовать ret_PaymentAllocationDetails вместо "AllocationDetails" этого (временная таблица выше):

 CASE WHEN(@PaymentCommandType = 1) THEN ret_PaymentAllocationDetails  ELSE AllocationDetails END AS paymentAllocationDetails ON paymentAllocation.[Guid] = paymentAllocationDetails.[PaymentAllocationGuid]
 

Я использовал СЛУЧАЙ, КОГДА, но это не сработало. Как решить эту проблему?

В этом и заключается вся процедура:

 CREATE PROCEDURE [dbo].[ret_PayrollCalculations_GetPayrollItems]
(
    @SalariedGuid uniqueidentifier,
    @Year int,
    @WorkingPeriodID int,
    @PaymentCommandType int,
    @PaymentTypeGuids nvarchar(max),
    @PayrollItemGuid uniqueidentifier
)
 AS
IF(ISNULL(@PayrollItemGuid, '00000000-0000-0000-0000-000000000000') = '00000000-0000-0000-0000-000000000000' AND @PaymentCommandType = 1)
    SELECT @PayrollItemGuid = [Guid] FROM ret_PayrollItemPatterns WHERE [PaymentCommandType] = 1 AND ISNULL([Deleted], 0) = 0 AND [Active] = 1
;WITH AllocationDetails
AS
(
    SELECT
        lastPaymentAllocationDetails.*
    FROM (
    SELECT *, 
    ROW_NUMBER() OVER (PARTITION BY [PaymentAllocationGuid] ORDER BY [Year] DESC, [WorkingPeriodTitle] DESC) rowNumber
    FROM ret_vwPaymentAllocationDetails
) lastPaymentAllocationDetails
WHERE lastPaymentAllocationDetails.rowNumber = 1
), ValidPaymentAllocations AS(
    SELECT
        paymentAllocationDetails.[PaymentAllocationGuid],
        paymentAllocation.[RetiredPersonnelGuid],
        MAX(paymentAllocationDetails.ID) AS MaxPaymentAllocationDetailID,
        MAX(paymentAllocationDetails.[Year] * 100   paymentAllocationDetails.[WorkingPeriodTitle]) AS [WorkingPeriodKey]
    FROM
        ret_PaymentAllocation AS paymentAllocation INNER JOIN
        CASE when(@PaymentCommandType = 1) THEN ret_PaymentAllocationDetails  ELSE AllocationDetails END AS paymentAllocationDetails ON paymentAllocation.[Guid] = paymentAllocationDetails.[PaymentAllocationGuid] INNER JOIN
        ret_PaymentTypes AS paymentTypes ON paymentAllocation.[PaymentTypesGuid] = paymentTypes.[Guid] INNER JOIN
        (SELECT MAX(ID) ID FROM ret_PaymentAllocationDetails GROUP BY PaymentAllocationGuid,WorkingPeriodTitle,Year) lastRecord ON LastRecord.Id     = paymentAllocationDetails.ID
    WHERE
        ISNULL(paymentAllocation.[Deleted],0) = 0 AND
        paymentAllocation.[RetiredPersonnelGuid] = @SalariedGuid AND
        (paymentAllocationDetails.[Year]  * 100   paymentAllocationDetails.[WorkingPeriodTitle] <= @Year * 100   @WorkingPeriodID AND paymentAllocationDetails.[Status] = 1 AND paymentTypes.[PaymentTypeFlag] <> 3 OR
         paymentAllocationDetails.[Year]  * 100   paymentAllocationDetails.[WorkingPeriodTitle] = @Year * 100   @WorkingPeriodID AND paymentTypes.[PaymentTypeFlag] = 3) AND
        (paymentAllocation.[PaymentCommandType] = @PaymentCommandType OR paymentAllocation.[PaymentTypesGuid] IN (SELECT * FROM com_udfSplit(@PaymentTypeGuids,','))) AND
        ((paymentTypes.[AffectOnAmountNature] = 1) OR (paymentTypes.[AffectOnAmountNature] = 2 AND paymentAllocation.[Amount] > 0))
    GROUP BY paymentAllocation.[RetiredPersonnelGuid], paymentAllocationDetails.[PaymentAllocationGuid]
), LastAmounts AS (
SELECT
    payrollCalculationDetails.[ItemGuid],
    SUM(payrollCalculationDetails.[Amount]) AS [Amount]
FROM
    ret_PayrollCalculationCommands payrollCalculationCommands INNER JOIN
    ret_PayrollCalculations payrollCalculations ON payrollCalculationCommands.[Guid] = payrollCalculations.[CalculationCommandGuid] INNER JOIN
    ret_PayrollCalculationDetails payrollCalculationDetails ON payrollCalculations.Guid = payrollCalculationDetails.[CalculationGuid]
WHERE 
    payrollCalculationCommands.[Hidden] = 0 AND
    payrollCalculationCommands.[Approved] = 1 AND
    payrollCalculations.[SalariedGuid] = @SalariedGuid AND
    payrollCalculations.[WorkingPeriodID] = @WorkingPeriodID AND
    payrollCalculations.[Year] = @Year
GROUP BY 
    payrollCalculationDetails.[ItemGuid]
)
SELECT
    paymentAllocation.[Guid],
    1 AS ItemType,
    paymentAllocation.[PaymentCommandType],
    '' AS ParameterName,
    paymentAllocationDetails.[Status],
    paymentAllocationDetails.[Amount],
    paymentAllocation.[Coffer],
    paymentTypes.[AffectOnSlipNature] AS [AffectOnSlip],
    paymentTypes.[AffectOnAmountNature] AS [AffectOnAmount],
    paymentTypes.[EffectiveOnSlip],
    paymentTypes.[BackPayable],
    0 AS [Reversible],
    lastAmounts.Amount AS LastAmount
FROM
    ret_PaymentAllocation AS paymentAllocation INNER JOIN
    ret_PaymentAllocationDetails AS paymentAllocationDetails ON paymentAllocation.[Guid] = paymentAllocationDetails.[PaymentAllocationGuid] INNER JOIN
    ValidPaymentAllocations AS validPaymentAllocations ON 
    paymentAllocationDetails.[PaymentAllocationGuid] = validPaymentAllocations.[PaymentAllocationGuid] AND 
    paymentAllocationDetails.[Year] * 100   paymentAllocationDetails.WorkingPeriodTitle = validPaymentAllocations.[WorkingPeriodKey] AND 
    paymentAllocationDetails.ID = validPaymentAllocations.MaxPaymentAllocationDetailID INNER JOIN
    ret_PaymentTypes AS paymentTypes ON paymentAllocation.[PaymentTypesGuid] = paymentTypes.[Guid] LEFT JOIN 
    LastAmounts lastAmounts ON lastAmounts.ItemGuid = paymentAllocation.Guid
WHERE
    ISNULL(paymentAllocation.Active,0) = 1 AND
    paymentAllocation.[RetiredPersonnelGuid] = @SalariedGuid AND
    paymentAllocationDetails.[Status] = 1 /* Status.Save */
UNION ALL
SELECT
    payrollItems.[Guid],
    2 AS [ItemType],
    payrollItemPatterns.[PaymentCommandType],
    payrollItems.[ParameterName],
    0 AS [Status],
    0 AS [Amount],
    0 AS [Coffer],
    payrollItems.[Nature] AS [AffectOnSlip],
    0 AS [AffectOnAmount],
    payrollItems.[EffectiveOnSlip],
    payrollItems.[BackPayable],
    payrollItems.[Reversible],
    lastAmounts.Amount AS LastAmount
FROM
    ret_PayrollItemPatterns AS payrollItemPatterns INNER JOIN
    ret_PayrollItemPatternDetails AS PID ON payrollItemPatterns.[Guid] = PID.[PayrollItemPatternGuid] INNER JOIN
    ret_PayrollItems AS payrollItems ON PID.[PayrollItem] = payrollItems.ID LEFT JOIN 
    LastAmounts lastAmounts ON lastAmounts.ItemGuid = payrollItems.Guid
WHERE
    (payrollItemPatterns.[PaymentCommandType] = @PaymentCommandType AND payrollItemPatterns.[Guid] IN (@PayrollItemGuid))
 

Комментарии:

1. Вы не можете «параметризовать» объединение таблицы, вам придется объединить обе таблицы, а затем использовать ОБЪЕДИНЕНИЕ в выборе, чтобы выбрать нужные значения в соответствии с тем, что @PaymentCommandType установлено

2. Вы говорите о временных таблицах, но в вашем SQL их нет. Мы что-то здесь упускаем?

3. Pet peeve также ; является терминатором утверждений, он находится в конце всех ваших утверждений, а не в начале тех, для которых требуется правильное завершение предыдущего утверждения.

4. Большая проблема возникнет, если одно соединение завершится неудачно, так как это приведет к удалению строки для другого соединения. В этом случае используйте левое соединение

5. «Я изложил всю процедуру в вопросе». Однако временных таблиц по- прежнему нет. Возможно, у вас сложилось впечатление, что Общее табличное выражение (CTE) и Временная таблица-это одно и то же? Это не так; они очень разные. CTE, как следует из его названий, — это выражение. Временная таблица-это фактический физический объект, который будет храниться в tempdb течение всего срока его существования. То, как они работают «под колпаком», и то, как они ведут себя, очень разные.

Ответ №1:

Вот объяснение теории с использованием надуманного примера (потому что это проще, чем пытаться понять вашу конкретную ситуацию и написать запрос, который использует ваши точные таблицы — надеюсь, вы сможете перевести этот совет в то, что хотите)

Представьте, что у вас есть 3 таблицы: Человек, рабочий адрес, домашний адрес. Мы проигнорируем замечания о том, что два разных адреса должны находиться в одной таблице с дискриминатором для этого надуманного примера

Вы хотите передать параметр, определяющий, какие данные будут возвращены-рабочие или домашние

Вы не можете этого сделать:

 SELECT * 
FROM
  Person p
  JOIN CASE WHEN @what = 'work' THEN WorkAddress ELSE HomeAddress END x
    ON p.Id = x.PersonId
 

Хорошая попытка, но вы не можете «вернуть всю таблицу» из случая, когда

Вы должны присоединиться к обоим, а затем выбрать в разделе «Выбор» то, что вы хотите:

 SELECT 
  p.*,
  CASE WHEN @what = 'work' THEN w.Building ELSE h.Building END as Building,
  CASE WHEN @what = 'work' THEN w.Street ELSE h.Street END as Street,
  ...
FROM
  Person p
  JOIN WorkAddress w ON p.Id = w.PersonId
  JOIN HomeAddress h ON p.Id = h.PersonId
 

Если адрес отсутствует, вам нужно присоединиться к нему СЛЕВА, в противном случае отсутствующий (рабочий) адрес приведет к тому, что весь человек и (доступный) домашний адрес исчезнут из результатов.

Ларну обоснованно указывает, что объединение СЛЕВА позволит вам поместить параметр в предложение ON, что может позволить вашей базе данных оптимизировать его (никогда не выполняйте объединение, если сравнение параметров ложно).

 SELECT 
  p.*,
  COALESCE(w.Building, h.Building) as Building,
  COALESCE(w.Street, h.Street) as Street,
  ...
FROM
  Person p
  LEFT JOIN WorkAddress w ON p.Id = w.PersonId AND @what = 'work'
  LEFT JOIN HomeAddress h ON p.Id = h.PersonId AND @what = 'home'
 

Charlieface alo обоснованно считает, что вам вообще не следует этого делать; сделайте «ЕСЛИ/ИНАЧЕ», если вы пишете хранимую процедуру, поскольку это самый надежный способ быть уверенным в планировании запросов и оптимизации производительности, которые будут иметь место. Вы можете использовать CTE для частей, общих для обоих запросов, чтобы упростить чтение всего текста

Комментарии:

1. Хм, это сработало бы для отношений один к одному, но не один ко многим. Если бы у кого-то, например, было 2 домашних адреса, и вы запросили Рабочий адрес, вы получили бы 2 (идентичные) строки. Если бы у кого-то было 2 домашних и 2 рабочих адреса, то у вас получилось бы 4 строки, о которых вы когда-либо просили.

2. @Larnu это именно моя проблема, несмотря на то, что я выбрал Max(Id) для решения этой проблемы, он возвращает две строки и все испортил.

3. @mehrabhabibi для легкой жизни убедитесь, что «таблицы», к которым вы присоединяетесь, имеют одну строку. Я уверен, что вы знаете, как это сделать, потому что ваш вопрос уже содержит ROW_NUMBER() OVER ... WHERE ronwumber=1 CTE

4. @Larnu Если вы проверите вопрос и комментарии, он определяет объем ответа: я прокомментировал «вы не можете этого сделать, сделайте это», ОП сказал «можете ли вы объяснить больше», я написал ответ, расширяющий «сделайте это». Если OP последовательно сталкивается с декартовым взрывом, с которым (необъяснимо, учитывая, что они демонстрируют способность справляться) они не могут справиться, это отдельный вопрос. Этот вопрос касается исключительно того, почему JOIN CASE WHEN x THEN t1 ELSE t2 END это не работает и как это может быть; мы не должны позволять вопросам становиться набором постоянно меняющихся целей, пока мы не напишем всю систему управления аэропортом OP.

5. @mehrabhabibi (но если вы не можете этого сделать; задайте, пожалуйста, новый вопрос.. это отдельная проблема по сравнению с тем, о чем вы спрашиваете здесь, которая является синтаксической ошибкой, которая вообще не позволяет вашему SQL работать)