Использовать столбец запроса в подзапросе - PullRequest
0 голосов
/ 21 мая 2018

У меня есть две таблицы:

INVOICES
ID | DISCOUNT_PRC
1  |   NULL
2  |   0.10
3  |   0.70
...

INVOICE_ITEMS
ID | INVOICE_ID | PRICE | ALT_PRICE
1  |     1      |  100  |  0
2  |     1      |  200  |  150
3  |     2      |  400  |  300
4  |     2      |  200  |  0
5  |     2      |  100  |  NULL
6  |     3      |  200  |  40
7  |     3      |  100  |  NULL
...

ПРИМЕЧАНИЕ. База данных используется другим приложением, мне не разрешено изменять нули на значения NULL или наоборот.

Мне нужно вывести сумму товаров для каждого счета-фактуры, при этом PRICE каждого товара умножается на скидку (1 - DISCOUNT_PRC), за исключением случаев, когда ALT_PRICE не равно NULL и больше нуля.В этом случае просто возьмите ALT_PRICE.Итак, желаемый результат будет выглядеть так:

INVOICES
ID | OVERALL_PRICE
1  |    250     (100*1) + (150)
2  |    570       (300) + (200*0.9) + (100*0.9)
3  |     70        (40) + (100*0.3)

Что у меня есть до сих пор:

select I.ID,
     case when ISNULL( IT.ALT_PRICE, 0) > 0
          then IT.ALT_PRICE
          else IT.PRICE * (1 - ISNULL( I.DISCOUNT_PRC, 0))
          end AS OVERALL_PRICE
from INVOICES I
join INVOICE_ITEMS IT on IT.INVOICE_ID = I.ID

Результат

ID  OVERALL_PRICE
1   100
1   150
2   300
2   180
2   90
3   40
3   30

Результат действителен длякаждый элемент, теперь мне нужно SUM их в один ряд для каждого счета.Я пробовал это с использованием LEFT JOIN, а затем с OUTER_APPLY:

select I.ID, items.OVERALL_PRICE FROM INVOICES I
OUTER APPLY ( select sum (
     case when ISNULL( IT.ALT_PRICE, 0) > 0
          then IT.ALT_PRICE
          else IT.PRICE * (1 - ISNULL( I.DISCOUNT_PRC, 0))
          end) AS OVERALL_PRICE
from INVOICE_ITEMS IT
where IT.INVOICE_ID = I.ID
group by IT.INVOICE_ID ) as items

Но я получаю ту же ошибку:

Multiple columns are specified in an aggregated expression containing an outer reference. If an expression being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.

РЕДАКТИРОВАТЬ: Я также хотел бы избежать использования SUM (или любого другого агрегированного выражения) в столбцах моего основного запроса, поскольку для этого потребуется сгруппировать все остальные столбцы.Фактическая таблица содержит более 50 столбцов и некоторые другие подзапросы, поэтому я хочу этого избежать.

РЕДАКТИРОВАТЬ 2: Хорошо, я провел сравнительный анализ с решениями из B3S (объединение в подзапросе), iSR5 (над) и Гордон Линофф (подать заявку).Я вставил счета-фактуры 50 КБ и элементы счета-фактуры 500 КБ и использовал MSSQL STATISTICS, он должен показывать достаточно приличные результаты в зависимости от размера базы данных.Вот результаты:

Join within subquery:
- CPU time = 1170 ms,  elapsed time = 335 ms.
- CPU time = 1202 ms,  elapsed time = 344 ms.
- CPU time = 1153 ms,  elapsed time = 348 ms.

OVER:
- CPU time = 3089 ms,  elapsed time = 1361 ms.
- CPU time = 3010 ms,  elapsed time = 1075 ms.
- CPU time = 3010 ms,  elapsed time = 1070 ms.

APPLY:
- CPU time = 2496 ms,  elapsed time = 2320 ms.
- CPU time = 2433 ms,  elapsed time = 2171 ms.
- CPU time = 2496 ms,  elapsed time = 2179 ms.

Заключение: Я ожидал, что объединение в подзапросе будет оптимизировано SQL, но я все еще ожидал увидеть лучшие результаты с двумя другимипредложения.Это довольно неожиданно для меня, но я должен отдать должное (и принятый ответ) B3S.Я был уверен, что внутреннее соединение нанесет удар по спектаклю, я не стал его пробовать.В любом случае, не стесняйтесь присоединиться к внешней таблице из подзапроса - если необходимо, конечно.

Ответы [ 4 ]

0 голосов
/ 21 мая 2018

Другой подход заключается в использовании преимущества OVER(), которое позволит вам избежать использования GROUP BY

. Вы можете сделать что-то вроде этого:

SELECT DISTINCT
    Inv.ID,
    SUM(CASE
            WHEN items.ALT_Price IS NOT NULL AND items.ALT_Price > 0 
            THEN items.ALT_Price
            ELSE items.Price * (1 - ISNULL(DISCOUNT_PRC, 0 ))
    END) OVER(PARTITION BY Inv.ID ORDER BY Inv.ID) AS OVERALL_PRICE
FROM #Invoices Inv
JOIN #Invoices_Items items ON items.Inovice_ID = Inv.ID

, затем вы можете добавлять столбцы беззаключив их внутрь GROUP BY

, вы также можете использовать его как подзапрос:

SELECT *
FROM(
SELECT DISTINCT
    Inv.ID,
    SUM(CASE
            WHEN items.ALT_Price IS NOT NULL AND items.ALT_Price > 0 
            THEN items.ALT_Price
            ELSE items.Price * (1 - ISNULL(DISCOUNT_PRC, 0 ))
    END) OVER(PARTITION BY Inv.ID ORDER BY Inv.ID) AS OVERALL_PRICE
FROM #Invoices Inv
JOIN #Invoices_Items items ON items.Inovice_ID = Inv.ID
) D 
-- extend it with more filters, JOINs ..etc
0 голосов
/ 21 мая 2018

Вот метод, который я бы выбрал (ожидающий анализ производительности по сравнению с другими методами):

select i.ID
     , sum(coalesce(alt_price,price*(1-isnull(discount_prc,0)))) OVERALL_PRICE
  from invoices i
  join (select id
             , invoice_id
             , price
             , case alt_price when 0 then null else alt_price end alt_price
          from invoice_items) ii
    on i.id = ii.invoice_id
 group by i.id

В этом запросе alt_price с нуля сначала преобразуются в NULL, затем coalesce используется внутриsum, чтобы выбрать либо alt_price, либо price.

со скидкой.
0 голосов
/ 21 мая 2018

Ваш apply метод должен работать без group by, на мой взгляд.

select I.ID, items.OVERALL_PRICE
FROM INVOICES I OUTER APPLY
     (select sum(case when IT.ALT_PRICE > 0
                      then IT.ALT_PRICE
                      else IT.PRICE * (1 - coalesce( I.DISCOUNT_PRC, 0))
                 end) AS OVERALL_PRICE
      from INVOICE_ITEMS IT
      where IT.INVOICE_ID = I.ID
     ) items;

Обратите внимание, что начальное условие не требует NULL проверки , because NULL will fails almost all comparisons. If ALT_PRICE is never negative or zero, you can simply use COALESCE () `:

select I.ID, items.OVERALL_PRICE
FROM INVOICES I OUTER APPLY
     (select sum(coalesce(IT.ALT_PRICE,
                          IT.PRICE * (1 - coalesce( I.DISCOUNT_PRC, 0))
                         )
                ) AS OVERALL_PRICE
      from INVOICE_ITEMS IT
      where IT.INVOICE_ID = I.ID
     ) as items;

Но это не так в SQL Server.Я не уверен, почему SQL Server имеет это ограничение на внешние ссылки.Это кажется странным.

В этом случае вы можете переписать логику следующим образом:

select I.ID, items.OVERALL_PRICE
from INVOICES I outer apply
     (select (sum(case when IT.ALT_PRICE > 0 then IT.ALT_PRICE else 0 END) +
              sum(case when IT.ALT_PRICE = 0 OR IT.ALT_PRICE IS NULL
                       then IT.PRICE else 0
                  end) * (1 - coalesce( I.DISCOUNT_PRC, 0))
             ) AS OVERALL_PRICE
      from INVOICE_ITEMS IT
      where IT.INVOICE_ID = I.ID
     ) items;

Это не так удовлетворительно, но вы можете использовать apply.

0 голосов
/ 21 мая 2018

Вы были очень близки:

select I.ID,
     sum(case when ISNULL( IT.ALT_PRICE, 0) > 0
          then IT.ALT_PRICE
          else IT.PRICE * (1 - ISNULL( I.DISCOUNT, 0))
          end) AS OVERALL_PRICE
from INVOICES I
join INVOICE_ITEMS IT on IT.INVOICE_ID = I.ID
GROUP BY I.ID

Редактировать на основе вашего комментария:

select DISTINCT
     I.TESTFIELD1,
     I.TESTFIELD2,
     I.ID,
     (SELECT SUM(case when ISNULL( IT2.ALT_PRICE, 0) > 0
          then IT2.ALT_PRICE
          else IT2.PRICE * (1 - ISNULL( I2.DISCOUNT_PRC, 0))
          end)
     FROM INVOICES I2
     LEFT JOIN INVOICE_ITEMS IT2 ON IT2.INVOICE_ID = I2.ID
     WHERE I2.ID = I.ID
     GROUP BY I2.ID) AS OVERALL_PRICE
from INVOICES I
join INVOICE_ITEMS IT on IT.INVOICE_ID = I.ID

Я добавил несколько тестовых полей, чтобы показать вам, как вы можете использовать sumи избегайте group by каждого поля таблицы.

SQL Fiddle Here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...