Накопительный расчет с использованием SQL - PullRequest
0 голосов
/ 06 июня 2019

У меня есть таблица спроса и предложения, как показано ниже,

Supply
Center1  1-Sep  500
Center1  1-Dec  1000

Demand
Center1  Req-1  1-Aug   300
Center1  Req-2  15-Aug  250
Center1  Req-3  1-Sep   1100

написать код SQL, чтобы получить вывод, как показано ниже,

Ожидаемый результат,

                 Req_Dt   Supply_Dt    Units supplied
Center1  Req-1   1-Aug      1-Sep       300
Center1  Req-2   15-Aug     1-Sep       200
Center1  Req-2   15-Aug     1-Dec       50
Center1  Req-3   1-Sep      1-Dec       950
Center1  Req-3   1-Sep      NULL        150

Для центра1, если есть запрос-1, который пришел с запросом 300 единиц на 1 августа, но по состоянию на 1 августа поставки отсутствуют, поэтому мы не можем выполнить запрос, пока не получим его. Далее поставки поступили только 1 сентября с 500 ед. Таким образом, для запроса-1 поставка произойдет 1 сентября, и будет поставлено 300 единиц, поэтому 200 все еще остается.

Теперь, следующий запрос-2 15 августа требует 250 единиц, и, так как 1 сентября может выполнить еще 200 единиц запаса, оставшихся после использования для req-1, мы разделили выполнение запроса, заявив, что Req-2 может будет выполнено 200 единицами 1-го сентября, а оставшиеся 50 - только 1-го декабря, когда поступит следующая поставка и т. д.

Пробовал использовать цикл while в хранимой процедуре, но это занимает больше времени при обработке миллионов строк. поэтому нужно написать либо функцию, либо sql

Ожидаемый результат,

Center   Request    Req_Dt    Supply_Dt    Units supplied
Center1  Req-1      1-Aug      1-Sep       300
Center1  Req-2      15-Aug     1-Sep       200
Center1  Req-2      15-Aug     1-Dec       50
Center1  Req-3      1-Sep      1-Dec       950
Center1  Req-3      1-Sep      NULL        150

Ответы [ 2 ]

1 голос
/ 10 июня 2019
DROP TABLE IF EXISTS #Supply
DROP TABLE IF EXISTS #Demand

SELECT * INTO #Supply FROM
(
SELECT CAST('09-01-2019' AS Date) AS dt, 'DC1' as DC, 500 AS KW
UNION
SELECT CAST('12-01-2019' AS Date), 'DC1', 1000
UNION
select CAST('10-01-2019' AS Date), 'DC2', 700
UNION
select CAST('10-01-2019' AS Date), 'DC3', 300
) Supply


SELECT * INTO #demand FROM 
(
SELECT 'DC1' as DC, 'D1' as DemandId, 300 as KW, CAST('01-08-2019' AS Date) AS dt
UNION
SELECT 'DC1', 'D2', 250, CAST('08-15-2019' AS Date)
UNION
SELECT 'DC1', 'D3', 1100, CAST('08-15-2019' AS Date)
UNION
SELECT 'DC2', 'D1', 500, CAST('09-01-2019' AS Date)
UNION
SELECT 'DC2', 'D2', 250, CAST('09-15-2019' AS Date)
UNION
SELECT 'DC3', 'D1', 100, CAST('10-01-2019' AS Date)
UNION
SELECT 'DC3', 'D2', 200, CAST('11-01-2019' AS Date)
) Demand

DROP TABLE IF EXISTS #AllSupply
DROP TABLE IF EXISTS #AllDemand
DROP TABLE IF EXISTS #Final


SELECT RowID, DT, DC, KW, RunningTotalCurrent,
LAG(RunningTotalCurrent, 1,0) OVER(partition by DC Order by RowId) AS PriorRunning, 
LEAD(RunningTotalCurrent, 1,0) OVER(partition by DC Order by RowId)  AS NextRunning
into #AllSupply
FROM (SELECT S1.RowID, S1.dt, s1.KW, S1.DC , SUM(S1.KW) over (PARTITION BY s1.dc ORDER BY s1.dt rows UNBOUNDED preceding) AS RunningTotalCurrent 
FROM (SELECT *, ROW_NUMBER() OVER(Partition By DC Order By dt ) AS RowID FROM #Supply) S1
) t


SELECT RowID, DemandId, DT, DC, KW, RunningTotalCurrent,LAG(RunningTotalCurrent, 1,0) OVER(partition by DC Order by RowId) AS PriorRunning, 
LEAD(RunningTotalCurrent, 1,0) OVER( partition by DC Order by RowId) AS NextRunning
into #AllDemand
FROM (SELECT d1.RowID, d1.dt, d1.KW, d1.DC , d1.demandId, SUM(d1.KW) over (PARTITION BY d1.dc ORDER BY d1.dt rows UNBOUNDED preceding) AS RunningTotalCurrent 
FROM (SELECT *, ROW_NUMBER() OVER(Partition By DC Order By dt, DemandId ) AS RowID FROM #Demand) d1) t


SELECT s.DC, D.DemandId, D.dt AS DemandDate, CASE WHEN s.dt < d.dt THEN d.dt ELSE ISNULL(S.dt,'11/11/2222') END AS SupplyDate, s.RunningTotalCurrent as SupplyRunningTotal,
 d.RunningTotalCurrent as DemandRunningTotal, s.PriorRunning as SupplyPreviousTotal, d.PriorRunning as DemandPreviousTotal, s.kw as SupplyKW, d.kw as DemandKW, 
CASE WHEN S.RunningTotalCurrent >= d.RunningTotalCurrent and s.PriorRunning = d.PriorRunning THEN d.kw
WHEN S.RunningTotalCurrent >= d.RunningTotalCurrent and s.PriorRunning > d.PriorRunning THEN d.kw - (s.PriorRunning - d.PriorRunning)
WHEN S.RunningTotalCurrent < d.RunningTotalCurrent AND s.PriorRunning = d.PriorRunning THEN s.kw 
WHEN S.RunningTotalCurrent < d.RunningTotalCurrent AND s.PriorRunning > d.PriorRunning THEN d.kw - (s.PriorRunning - d.PriorRunning)
WHEN S.RunningTotalCurrent < d.RunningTotalCurrent AND s.PriorRunning < d.PriorRunning THEN S.RunningTotalCurrent - d.PriorRunning 
WHEN S.RunningTotalCurrent = d.RunningTotalCurrent THEN d.kw 
END  AS Supply 
INTO #Final
FROM #AllDemand D
LEFT JOIN #AllSupply S ON S.DC = D.DC
AND D.PriorRunning <= S.RunningTotalCurrent 
AND D.RunningTotalCurrent >= S.PriorRunning


SELECT DC, DemandId, DemandDate, SupplyDate, Supply 
FROM #Final
UNION 
SELECT d.DC, d.DemandId, d.dt, '11/11/2222', d.kw - a.Supply
FROM (SELECT Dc, DemandId, SUM(Supply) AS Supply FROM #Final GROUP BY DC, DemandId) a 
JOIN #demand d on a.DC= d.DC and a.DemandId = d.DemandId and d.kw > a.Supply
1 голос
/ 06 июня 2019

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

По сути, я построил серию таблиц обработки.Один для фильтрации каждой строки спроса и предложения за один раз, а другой для фактического манипулирования количеством вещей, поставляемых / в строках спроса, это делается для того, чтобы сами данные можно было оставить в покое.Наконец, существует таблица, созданная на лету для генерации самого вывода.

Цикл в основном продолжается до тех пор, пока не останется ничего для фильтрации, и если что-либо еще не выполнено ни из строки предложения, ни из строки спроса, то этотакже вводится в выходную таблицу.На каждом проходе запрашиваются текущие требования и таблицы обработчиков поставщика, чтобы узнать, нужно ли добавлять что-то новое, а затем в конце цикла после того, как сгенерирована выходная строка, все, что находится в 0 или меньше, удаляется из таблиц обработчиков, чтобы гарантировать толькоодна строка спроса или предложения рассматривается одновременно.

РЕДАКТИРОВАТЬ: я добавил цикл для центра.Это означает, что набор данных с несколькими центрами теперь будет работать успешно.

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

Пожалуйста, посмотрите пример запроса и данные, используя данные Center1 и Center 2, предоставленные в вашем комментарии инабор результатов вывода вставлен ниже запроса.

declare @Supply table (
    Center nvarchar(20),
    [Date] nvarchar(10),
    Supply int
);

declare @Demand table (
    Center nvarchar(20),
    Requirement nvarchar(10),
    [Date] nvarchar(10),
    Demand int
);

insert @Supply (Center, Date, Supply)
values
('Center1', '1-Sep', 500),
('Center1', '1-Dec', 1000),
('Center2', '1-Oct', 700);


insert @Demand (Center, Requirement, Date, Demand)
values
('Center1', 'Req-1', '1-Aug', 300),
('Center1', 'Req-2', '15-Aug', 250),
('Center1', 'Req-3', '1-Sep', 1100),
('Center2', 'Req-1', '1-Sep', 500), 
('Center2', 'Req-2', '1-Oct', 250);

declare @output table
( Center nvarchar(20),
  Requirement nvarchar(10),
  Req_Dt nvarchar(10),
  Supply_Dt nvarchar(10),
  Units_supplied int
);

declare @reqfilter table (
    Requirement nvarchar(10),
    Center nvarchar(20)
);

declare @supfilter table (
    Date nvarchar(10),
    Center nvarchar(20)
);

declare @req table (
    Center nvarchar(20),
    Requirement nvarchar(10),
    Date nvarchar(10),
    Demand int
);

declare @sup table (
    Center nvarchar(20),
    Date nvarchar(10),
    Supply int
);

Declare @Centerfilter table (Center nvarchar(20));

insert @Centerfilter
select distinct Center from @Supply
union
select distinct Center from @Demand;

--select count(*) from @Supply as supply
--select count(*) from @Demand as demand

while exists (select 1 from @Centerfilter)
begin
    insert @reqfilter 
    select requirement, c.Center from @Demand d inner join 
    (select top 1 Center from @Centerfilter order by Center) c
    on d.Center=c.Center;

    insert @supfilter
    select date, s.Center from @Supply s inner join
    (select top 1 Center from @Centerfilter order by Center) c
    on s.Center=c.Center;

    while exists (select 1 from @reqfilter outer apply @supfilter)
    begin 
        if not exists (select 1 from @req)
        begin
                insert @req
                select top 1 d.Center, d.Requirement, date, demand from
                @Demand d inner join @reqfilter r on d.center=r.Center where d.Requirement in (select top 1 Requirement from @reqfilter order by Center, Requirement)
                order by Center, parse(date as date);
                delete @reqfilter from @reqfilter rq inner join @req r on rq.Requirement=r.Requirement and rq.Center=r.Center;
        end
        if not exists (select 1 from @sup)
        begin
            insert @sup
            select top 1 s.Center, date, supply from
            @Supply s inner join @Centerfilter c on s.Center=c.Center where date in (select top 1 Date from @supfilter order by Center, parse(date as date))
            order by center, parse(date as date);
            delete @supfilter from @supfilter s inner join @sup su on s.Date=su.date and s.Center=su.Center;
        end
        insert @output
        select coalesce(d.center, s.center), requirement, d.date, s.date, case when supply>Demand then demand else supply end
        from @req d inner join @sup s on d.Center=s.Center

        update @req set demand=demand-o.Units_supplied 
        from @req r inner join (select top 1 Center, Requirement, Units_supplied from @output order by Center desc, parse(Req_Dt as date) desc, parse(Supply_Dt as date) desc) o on r.Center=o.Center and r.Requirement=o.Requirement;

        update @sup set Supply=supply-units_supplied
        from @sup s inner join @req r on s.Center=r.Center 
        inner join (select top 1 center, requirement, units_supplied from @output order by Center desc, parse(Req_Dt as date) desc, parse(Supply_Dt as date) desc) 
        o on s.Center=o.Center and r.Requirement=o.Requirement;
        delete from @req where Demand<=0;
        delete from @sup where Supply<=0;
    end

    if exists (select 1 from @req)
    begin
        insert @output
        select center, requirement, Date, null, demand from @req;
    end

    if exists (select 1 from @sup)
    begin
        insert @output
        select center, null, null, date, supply from @sup;
    end
    delete @Centerfilter where Center in (select top 1 Center from @Centerfilter order by Center);
    delete from @reqfilter;
    delete from @supfilter;
    delete from @req;
    delete from @sup;
end

    select * from @output;

enter image description here

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