Рекурсивный выбор с использованием T-SQL CTE - PullRequest
0 голосов
/ 05 мая 2018

У меня есть проблема рекурсивного выбора, которую я не могу заставить работать, такое ощущение, что почти там, но не обязательно.

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

Таблица 1 - Список продуктов

List    ProductCode    SortNumber
------------------------------------
1       A              1  
1       B              2  
1       F              3  
1       G              4  
1       K              5  
2       C              1  
2       A              2  
3       B              1  
3       K              2  
3       G              3  

Таблица 2 - Цена продукта

ProductType     Client      ProductCode     Price
---------------------------------------------------
Type 1          1               A           100  
Type 1          1               A           150  
Type 1          1               B           200  
Type 1          1               B           120  
Type 1          1               F           150  
Type 2          1               A           200  
Type 2          1               A           300  
Type 2          1               B           300  
Type 2          1               F           400  
Type 2          1               G           125  
Type 2          1               G           75  
Type 1          2               A           190  
Type 1          2               A           130  
Type 1          2               A           200  
Type 1          2               B           270  
Type 1          2               B           180  
Type 1          2               F           130  
Type 2          2               A           210    
Type 2          2               A           100  
Type 2          2               B           350  
Type 2          2               F           200  
Type 2          2               G           175  
Type 2          2               G           95  
Type 2          2               K           65

То, чего я пытаюсь достичь, это когда я запрашиваю Список продуктов из 1, он получит все максимальные цены, связанные с кодом продукта. Если цены для Типа продукта 1 отсутствуют, он проверит максимальные цены для Типа продукта 2. Если нет соответствующей цены Типа 2, значение равно 0.

Итак, желаемым выводом является то, что когда я запрашиваю прайс-лист 1 для всех клиентов (скажем, здесь для клиентов 1 и 2), желаемый вывод должен быть

Client  ProductCode     SortNumber  PriceToDisplay
---------------------------------------------------
1       A               1           150  
1       B               2           200
1       F               3           150
1       G               4           125     (No Type 1, so we get type 2 price)
1       K               5           0       (No Type 1, 2 Price for Product K) 
2       A               1           200
2       B               2           270
2       F               3           130
2       G               4           175
2       K               5           65

Испытанное решение: подход CTE

Поскольку у меня было впечатление, что это может быть рекурсивный выбор, я подумал об использовании CTE. У меня есть этот запрос -

( обратите внимание на прокомментированный код - - И pp1.ProductCode = pp2.ProductCode )

Declare @List int = 1

;With ClientCTE As (
Select Distinct Client From ClientsTable
),
RecursiveSelect As (
Select p1.Client
, l.ProductCode
, l.SortOrder
, p1.Price As P1Price
, p2.Price As P2Price
, Case when p1.Price Is Null Then Case When p2.Price Is Null Then 0 Else p2.Price End
    Else p1.Price End As PriceToDisplay
From ProductList l

Left Join (
    Select Distinct pp.Client, pp.ProductCode, Max(pp.Price) As ItemPrice From ProductPrice pp
    Left Join ClientCTE c On c.Client = pp.Client
    Where pp.ProductType = 1
    Group By pp.Client, pp.ProductCode) p1 On p1.ProductCode = l.ProductCode

Left Join (
    Select Distinct pp.Client, pp.ProductCode, Max(pp.Price) As ItemPrice From ProductPrice pp
    Left Join ClientCTE c On c.Client = pp.Client
    Where pp.ProductType = 2
    Group By pp.Client, pp.ProductCode) p2 On p2.Client = p1.Client

Where pp1.Client = pp2.Client
-- And pp1.ProductCode = pp2.ProductCode **this commented code**
And l.List = 1

)

Select Distinct Client, ProductCode, SortOrder, Max(P1Price), Max(P2Price)   
From RecursiveSelect  
Group By Client, ProductCode, SortOrder

Выводы - CTE

Если код прокомментирован, он будет:

  • Получите цены правильно
  • Однако, поскольку в таблице «Цена продукта» цены на код продукта G и K для типа 1 отсутствуют, он не будет отображаться правильно

Результаты

Client  ProductCode     SortNumber  PriceToDisplay
---------------------------------------------------
1       A               1           150  
1       B               2           200
1       F               3           150
(missing G and K product codes)
2       A               1           200
2       B               2           270
2       F               3           130
2       G               4           175
2       K               5           65

Если код не прокомментирован, он будет:

  • Появятся все коды продуктов, но цены неверны
  • Отображаемая цена является максимальной ценой в таблице Price1 независимо от клиента, следовательно

Результаты

Client  ProductCode     SortNumber  PriceToDisplay
---------------------------------------------------
1       A               1           WRONG PRICE  
1       B               2           WRONG PRICE
1       F               3           WRONG PRICE
1       G               4           WRONG PRICE
1       K               5           WRONG PRICE 
2       A               1           WRONG PRICE
2       B               2           WRONG PRICE
2       F               3           WRONG PRICE
2       G               4           WRONG PRICE
2       K               5           WRONG PRICE

Я предполагаю, что есть только одна настройка, которую я должен сделать в своем запросе, но я не могу точно определить, где именно. Некомментированный код возвращает правильные значения, но почему он не возвращает отсутствующие левые табличные значения, учитывая, что это левое соединение?

Ответы [ 4 ]

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

Поэтому я сижу с другом и работаю над этим. Решение, которое сработало (подкорректировано из исходного имени таблицы и столбцов БД), выглядит следующим образом:

Declare @List int = 1

;With ClientCTE (BuyGroup, CustomerPriceGroup, ProductListHeaderId) As (
Select Distinct Client From ClientsTable
), 

RecursiveSelect As (
Select x.Client, x.ProductCode
, Case When Max(Type1Price) Is Null Then 
    Case When Max(Type2Price) Is Null Then 0 
    Else Max(Type2Price) End 
    Else Max(Type1Price) End 
As PriceToDisplay

From 
    (Select h.Client, h.ProductCode,
        Case When h.ProductType = 'Type1' 
            Then 
                Max(h.UnitPrice) 
            Else 
                Null 
            End As Type1Price
        , Case When h.ProductType = 'Type2' 
            Then 
                Max(h.UnitPrice) 
            Else 
                Null 
            End As Type2Price
            , h.BuyGroup, h.ConditionScale          
        From ProductPrice h
             Inner Join ClientCTE c On c.Client = h.Client
        Where           
            h.ProductType In ('Type1', 'Type2')
        And h.ProductCode In (Select ProductCode From ProductList where List = @List)           
        Group by h.Client, h.ProductCode
        ) x
        Group by Client, ProductCode
        )

select * from RecursiveSelect

Мы оба выяснили, что в исходном запросе два левых объединения отличаются только по одному критерию (то есть ProductType). Как только мы получили исходные данные из запроса, мы сгруппировали и выбрали цену, которая будет отображаться.

Причина CTE состоит в том, что первоначальное намерение запроса было получить цены для N клиентов. Поэтому якорный запрос получает список клиентов и затем передается на второй. Более того, если вы также работаете с числом списков X, вы можете включить его в запрос привязки и заменить "@List" на столбец первого.

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

Вы можете сделать это с помощью объединения и группировки по

declare @PL table (client int, ProductCode char(1), Sort int);
insert into @pl values
       (1, 'A', 1),
       (1, 'B', 2), 
       (1, 'F', 3), 
       (1, 'G', 4), 
       (1, 'K', 5), 
       (2, 'C', 1), 
       (2, 'A', 2), 
       (3, 'B', 1), 
       (3, 'K', 2), 
       (3, 'G', 3);
--select * from @PL;
declare @PP table (ProductType varchar(10), Client int, ProductCode char(1), Price int);
insert into @PP values
       ('Type 1', 1, 'A', 100), 
       ('Type 1', 1, 'A', 150), 
       ('Type 1', 1, 'B', 200), 
       ('Type 1', 1, 'B', 120), 
       ('Type 1', 1, 'F', 150), 
       ('Type 2', 1, 'A', 200), 
       ('Type 2', 1, 'A', 300), 
       ('Type 2', 1, 'B', 300), 
       ('Type 2', 1, 'F', 400), 
       ('Type 2', 1, 'G', 125), 
       ('Type 2', 1, 'G',  75), 
       ('Type 1', 2, 'A', 190), 
       ('Type 1', 2, 'A', 130), 
       ('Type 1', 2, 'A', 200), 
       ('Type 1', 2, 'B', 270), 
       ('Type 1', 2, 'B', 180), 
       ('Type 1', 2, 'F', 130), 
       ('Type 2', 2, 'A', 210), 
       ('Type 2', 2, 'A', 100), 
       ('Type 2', 2, 'B', 350), 
       ('Type 2', 2, 'F', 200), 
       ('Type 2', 2, 'G', 175), 
       ('Type 2', 2, 'G',  95), 
       ('Type 2', 2, 'K',  65);
--select * from @pp;
select pl.client, pl.ProductCode
   --, pp1.Price as Price1, pp2.Price as Price2
     , max(isnull(pp1.Price, isnull(pp2.Price, 0))) as Price12
from @PL pl 
left join @PP pp1
  on pp1.ProductCode = pl.ProductCode 
 and pp1.Client      = pl.client
 and pp1.ProductType = 'Type 1'
 left join @PP pp2
  on pp2.ProductCode = pl.ProductCode 
 and pp2.Client      = pl.client
 and pp2.ProductType = 'Type 2'
 where pl.client in (1, 2) 
 group by pl.client, pl.ProductCode 
 order by pl.client, pl.ProductCode;
0 голосов
/ 05 мая 2018

исходя из требований, вот что я бы сделал.

  1. подзапрос или просмотр, например vw_product_max_price:

    select producttype, client, productcode, max(price) as maxprice group by producttype, client, productcode

  2. запрос на запрос списка продуктов

select MP.client, MP.productcode, PL.sortnumber, case MP.maxprice when NULL then 0 else MP.maxprice end as PriceToDisplay from productlist PL left outer join vw_product_max_price MP on PL.productcode=MP.productcode where MP.client = ? and MP.productcode = ?

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

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

Вы можете использовать OUTER APPLY, чтобы получить максимальную цену, вам не нужен рекурсивный CTE

select  c.Client, l.ProductCode, l.SortNumber, Price = isnull(p.Price, 0)
from    ProductList l
        cross join ClientCTE c
        outer apply
        (
            -- Top 1 to return only one row per client + Pproduct Code
            select  top 1 p.Price 
            from    ProductPrice p
            where   p.Client    = c.Client
            and     p.ProductCode   = l.ProductCode
            -- Type 1 will comes before Type 2, and higher price first
            -- If there isn't any rows for Type 1, Type 2 will be selected
            -- no rows return if no rows are found
            order by p.ProductType, p.Price desc
        ) p
where   l.List  = 1
order by Client, ProductCode
...