Как улучшить это заявление T SQL с помощью CTE - PullRequest
0 голосов
/ 14 января 2020

У меня возникли проблемы с тем, чтобы обернуть голову вокруг запроса. Он использует подзапрос и дополнительное объединение, и я уверен, что есть способ построить его лучше, более читабельным и более производительным, вероятно, используя CTE, но я не уверен, как.

Здесь идет: у меня есть одна таблица с историей складских запасов и другая таблица с историей цен на товары. Мне нужно рассчитать стоимость инвентаря для каждой записи в истории c, используя самую последнюю цену на тот момент.

Для целей этого поста я немного упростил таблицы, используя только один элемент из один запас. Таблица инвентаризации выглядит так:

enter image description here

select * 
from Temp.dbo.InvHist 
order by Date

И исторические c цены выглядят так:

enter image description here

select *
from Temp.dbo.PriceHist
order by Date

Чтобы получить самую последнюю цену на каждую дату инвентарного запаса, мне нужно сначала получить правильную дату из таблицы цен:

enter image description here

select 
    InvHist.Date
    ,InvHist.Item
    ,InvHist.Amount
    ,max(PriceHist1.Date) DatePrice
from Temp.dbo.InvHist 
left join (
    select PriceHist.Item, PriceHist.Date
    from Temp.dbo.PriceHist
    group by PriceHist.Item, PriceHist.Date
) PriceHist1 on InvHist.Item = PriceHist1.Item and PriceHist1.Date <= InvHist.Date
group by 
    InvHist.Date
    ,InvHist.Item
    ,InvHist.Amount
order by
    InvHist.Date

И, наконец, я снова присоединяюсь к таблице цен, чтобы получить правильную цену, по которой я могу рассчитать стоимость акций:

enter image description here

select 
    a.*
    ,b.Price
    ,a.Amount * b.Price InvValue
from (
    select 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
        ,max(PriceHist1.Date) DatePrice
    from Temp.dbo.InvHist 
    left join (
        select PriceHist.Item, PriceHist.Date
        from Temp.dbo.PriceHist
        group by PriceHist.Item, PriceHist.Date
    ) PriceHist1 on InvHist.Item = PriceHist1.Item and PriceHist1.Date <= InvHist.Date
    group by 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
) a
left join Temp.dbo.PriceHist b on a.Item = b.Item and a.DatePrice = b.Date

Итак, есть ли у кого-нибудь идеи о том, как добиться этого результата более эффективным и элегантным образом?

Ответы [ 4 ]

1 голос
/ 14 января 2020

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

Обратите внимание на псевдонимы таблиц, я использовал временные таблицы, поэтому я просто присвоил псевдониму то же имя, чтобы оно совпадало с вашим исходным запросом. :

CREATE TABLE #InvHist ([date] date, Item varchar(20),Amount int)

INSERT INTO #InvHist VALUES('01-31-16','NDS.09011012',12)
INSERT INTO #InvHist VALUES('02-29-16','NDS.09011012',11)
INSERT INTO #InvHist VALUES('03-31-16','NDS.09011012',8)
INSERT INTO #InvHist VALUES('04-30-16','NDS.09011012',6)
INSERT INTO #InvHist VALUES('05-31-16','NDS.09011012',6)
INSERT INTO #InvHist VALUES('06-30-16','NDS.09011012',32)
INSERT INTO #InvHist VALUES('07-31-16','NDS.09011012',32)
INSERT INTO #InvHist VALUES('08-31-16','NDS.09011012',28)
INSERT INTO #InvHist VALUES('09-30-16','NDS.09011012',26)
INSERT INTO #InvHist VALUES('10-31-16','NDS.09011012',26)
INSERT INTO #InvHist VALUES('11-30-16','NDS.09011012',23)
INSERT INTO #InvHist VALUES('12-31-16','NDS.09011012',21)


CREATE TABLE #PriceHist([date] date, Item varchar(20), Price int)
INSERT INTO #PriceHist VALUES('07-26-06','NDS.09011012',93894)
INSERT INTO #PriceHist VALUES('10-25-06','NDS.09011012',98119)
INSERT INTO #PriceHist VALUES('04-26-07','NDS.09011012',102828)
INSERT INTO #PriceHist VALUES('06-23-07','NDS.09011012',102599)
INSERT INTO #PriceHist VALUES('05-27-08','NDS.09011012',10701)
INSERT INTO #PriceHist VALUES('05-26-09','NDS.09011012',89649)
INSERT INTO #PriceHist VALUES('10-20-10','NDS.09011012',90783)
INSERT INTO #PriceHist VALUES('01-26-12','NDS.09011012',89991)
INSERT INTO #PriceHist VALUES('05-24-14','NDS.09011012',131496)
INSERT INTO #PriceHist VALUES('03-28-15','NDS.09011012',141873)
INSERT INTO #PriceHist VALUES('05-14-16','NDS.09011012',149738)
INSERT INTO #PriceHist VALUES('06-25-16','NDS.09011012',15318)
INSERT INTO #PriceHist VALUES('03-25-17','NDS.09011012',15459)
INSERT INTO #PriceHist VALUES('10-21-17','NDS.09011012',156352)
INSERT INTO #PriceHist VALUES('03-30-18','NDS.09011012',154869)
INSERT INTO #PriceHist VALUES('03-29-19','NDS.09011012',155154)


    SELECT 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
        ,PH.PriceDate
        ,PH.Price
        ,InvHist.Amount * PH.Price InvValue
        FROM #InvHist InvHist
    CROSS APPLY (SELECT TOP(1) MAX(date) PriceDate, Price 
            FROM #PriceHist PriceHist 
            WHERE InvHist.Item = PriceHist.Item and PriceHist.date <=  InvHist.date
            GROUP BY Price ORDER by PriceDate desc) PH
1 голос
/ 14 января 2020

Добрый день,

Поскольку вы не предоставили DDL + DML, я не могу проверить запросы. Более того, любое обсуждение производительности совершенно бессмысленно.

Следующие запросы не были протестированы и просто представляют основную c идею использования CTE вместо подзапроса в вашем конкретном c дело. Этот ответ ПРЕДСТАВЛЯЕТСЯ ТОЛЬКО ДЛЯ ОБУЧЕНИЯ, а не по производственной причине.

Преобразование основного подзапроса в один CTE:

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

;With MyCTE as(
    select 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
        ,max(PriceHist1.Date) DatePrice
    from Temp.dbo.InvHist 
    left join (
        select PriceHist.Item, PriceHist.Date
        from Temp.dbo.PriceHist
        group by PriceHist.Item, PriceHist.Date
    ) PriceHist1 on InvHist.Item = PriceHist1.Item and PriceHist1.Date <= InvHist.Date
    group by 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
)
select a.*,b.Price,a.Amount * b.Price InvValue
from MyCTE a
left join Temp.dbo.PriceHist b on a.Item = b.Item and a.DatePrice = b.Date
GO

Теперь давайте перейдем на следующий уровень и разбим подзапросы внутри основного подзапроса на отдельный CTE

Этот шаг немного сложнее (и я не могу быть уверен, что не допустил синтаксическую ошибку, поскольку не смог ее проверить).

;With MyCTE01 as(
    select 
        InvHist.Date
        ,InvHist.Item
        ,InvHist.Amount
        ,max(PriceHist1.Date) DatePrice
    from Temp.dbo.InvHist 
),
MyCTE02 as (
    select PriceHist.Item, PriceHist.Date
    from Temp.dbo.PriceHist
    group by PriceHist.Item, PriceHist.Date
),
MyCTE03 as (
    select Date,Item,Amount,DatePrice
    from MyCTE01
    left join MyCTE02 on MyCTE01.Item = MyCTE02.Item and MyCTE02.Date <= MyCTE01.Date
    group by MyCTE01.Date,MyCTE01.Item,MyCTE01.Amount
)
select a.*,b.Price,a.Amount * b.Price InvValue
from MyCTE03 a
left join MyCTE02 b on a.Item = b.Item and a.DatePrice = b.Date
0 голосов
/ 16 января 2020

попробуйте заменить внутренний левый запрос на соединение временной таблицей #temp

left join (
        select PriceHist.Item, PriceHist.Date
        from Temp.dbo.PriceHist
        group by PriceHist.Item, PriceHist.Date
    ) PriceHist1 on InvHist.Item = PriceHist1.Item and PriceHist1.Date <= InvHist.Date

на

select PriceHist.Item, PriceHist.Date
         INTO #temp
        from Temp.dbo.PriceHist
        group by PriceHist.Item, PriceHist.Date

, а затем используйте #temp в левом соединении. Необходимо проверить логическое читает с Set Statistics IO или profiler этого запроса, где логическое чтение тяжелое.

0 голосов
/ 15 января 2020

Итак, настоящие таблицы - это (на немецком языке) LagerbestandHistor ie с 3,6 миллионами строк и HerstellkostenHistor ie с 61,9 миллионами строк с датами от 2006 до 2020 года. Я добавил кластерное хранилище столбцов индекс для них обоих.

Я сравнил только два запроса, мой начальный с подзапросом и один с перекрестным применением.

Первый выглядит так:

select 
    a.*
    ,b.HerstellkostenkomponenteNr
    ,b.Betrag
from (
    select 
        lbh.Datum
        ,lbh.ArtikelNr
        ,lbh.LagerNr
        ,lbh.LagerplatzNr
        ,lbh.Menge
        ,max(hkh1.Datum) PreisDatum
    from LagerbestandHistorie lbh 
    left join (
        select hkh.Datum, hkh.ArtikelNr
        from HerstellkostenHistorie hkh
        group by hkh.Datum, hkh.ArtikelNr
    ) hkh1 on lbh.ArtikelNr = hkh1.ArtikelNr and hkh1.Datum <= lbh.Datum
    group by 
        lbh.Datum
        ,lbh.ArtikelNr
        ,lbh.LagerNr
        ,lbh.LagerplatzNr
        ,lbh.Menge
) a
left join HerstellkostenHistorie b on a.ArtikelNr = b.ArtikelNr and a.PreisDatum = b.Datum
order by
    a.Datum
    ,a.ArtikelNr
    ,a.LagerNr
    ,a.LagerplatzNr
    ,b.HerstellkostenkomponenteNr

Он вернул результат (12 миллионов строк) за 26 минут.

Другой выглядит так:

select 
    lbh.Datum
    ,lbh.ArtikelNr
    ,lbh.LagerNr
    ,lbh.LagerplatzNr
    ,lbh.Menge
    ,hk1.PreisDatum
    ,hkh2.HerstellkostenkomponenteNr
    ,hkh2.Betrag
from LagerbestandHistorie lbh
cross apply (
    select 
        max(Datum) PreisDatum
    from HerstellkostenHistorie hkh 
    where 
        lbh.ArtikelNr = hkh.ArtikelNr and hkh.Datum <= lbh.Datum
    ) hkh1
left join HerstellkostenHistorie hkh2 on lbh.ArtikelNr = hkh2.ArtikelNr and hkh1.PreisDatum = hkh2.Datum
order by
    lbh.Datum
    ,lbh.ArtikelNr
    ,lbh.LagerNr
    ,lbh.LagerplatzNr
    ,hk2.HerstellkostenkomponenteNr

Потребовалось 44 минуты, чтобы завершить sh!

Планы выполнения выглядят так:

enter image description here

enter image description here

Пока я буду придерживаться первого запроса. Спасибо за все ваши ответы.

...