2017-11-16 2 views
0

Ich habe ein Problem, dass es sehr einfach ist, in C# -Code zum Beispiel gelöst zu werden, aber ich habe keine Ahnung, wie man in einer SQL-Abfrage mit rekursiven CTE-s oder Sliding schreibt Windows-Funktionen HierSQL-Implementierung mit Sliding-Window-Funktionen oder rekursive CTEs

ist die Situation: sagen wir, ich habe eine Tabelle mit drei Spalten (ID, Date, Amount), und hier einige Daten:

ID Date   Amount 
----------------------- 
1 01.01.2016 -500 
2 01.02.2016 1000 
3 01.03.2016 -200 
4 01.04.2016  300 
5 01.05.2016  500 
6 01.06.2016 1000 
7 01.07.2016 -100 
8 01.08.2016  200 

Das Ergebnis, das ich vom Tisch bekommen wollen, ist dies (ID, Amount .... Order By Date):

ID Amount 
----------------------- 
2 300 
4 300 
5 500 
6 900 
8 200 

die Idee ist, die Beträge in Raten zu verteilen, separat für jeden Kunden, aber die Sache ist, wenn negative Menge Spiel kommt in Sie Betrag von der letzten Rate entfernen müssen. Ich weiß nicht, wie klar ich bin, also hier ein Beispiel:

Angenommen, ich habe 3 Rechnungen für einen Kunden mit den Beträgen 500, 200, -300.

Wenn ich anfange, diese Rechnungen zu verteilen, verteile ich zuerst den Betrag 500, dann 200. Aber wenn ich zum dritten -300 komme, dann muss ich von der letzten Rechnung entfernen. Mit anderen Worten 200 - 300 = -100, so dass der Betrag aus der zweiten Rechnung verschwindet, aber immer noch -100, die von der ersten Rechnung abgezogen werden müssen. Also 500 - 100 = 400. Das Ergebnis, das ich brauche, ist das Ergebnis mit einer Zeile (erste Rechnung mit Betrag 400)

Ein anderes Beispiel, wenn die erste Rechnung mit einem negativen Betrag (-500, 300, 500) ist. In diesem Fall wird die zweite (-500) Rechnung die zweite verschwinden lassen und weitere 200 werden von der dritten abgezogen. Also das Ergebnis wird: Dritte Rechnung mit Betrag 300.

Dies ist so etwas wie Stack-Implementierung in der Programmiersprache, aber ich muss es mit Sliding-Window-Funktionen in SQL Server machen.

Ich brauche eine Implementierung mit Gleitfunktion oder rekursive CTEs. Nicht mit Zyklen ...

Danke.

+1

... Es gibt keine Client-ID für Ihre Daten ...und ich verstehe es nicht, warum Sie eine Id von einer Reihe mit einer reduzierten Menge darauf wollen. ein siple sum() über die gruppierte by clientid reicht nicht aus? –

+0

Bei ID 2, die ich nehme, habe ich nur Eingänge von -500 und 1000, wie bekommen Sie 300 ??? Willst du nur die Gesamtsumme im Laufe der Zeit? Ihre Textbeispiele scheinen darauf hinzudeuten, dass Sie nur die Zahlen hinzufügen möchten. Wenn das wirklich für Rechnungen ist, müssen Sie Ihre Beispieldaten verbessern, um sie zu zeigen –

+0

Die reale Menge von 2 ist 1000, aber die tatsächliche sollte 1000 (2) -500 (1) -200 (3) = 300 sein Das Negative Beträge müssen von den Beträgen abgezogen werden, die vor ihnen liegen, aber in diesem Fall ist der erste Betrag negativ, und er muss vom ersten positiven Wert abgezogen werden, der danach kommt. –

Antwort

2

Ok, denke das ist was du willst. Es gibt zwei rekursive Abfragen. Eine für die Aufwärtsausbreitung und die zweite für die Abwärtsausbreitung.

with your_data_rn as 
(
    select *, row_number() over (order by date) rn 
    from your_data 
), rcte_up(id, date, ammount, rn, running) as 
(
    select *, ammount as running 
    from your_data_rn 
    union all 
    select d.*, 
      d.ammount + rcte_up.running 
    from your_data_rn d 
    join rcte_up on rcte_up.running < 0 and d.rn = rcte_up.rn - 1 
), data2 as 
(
    select id, date, min(running) ammount, 
      row_number() over (order by date) rn 
    from rcte_up 
    group by id, date, rn 
    having min(running) > 0 or rn = 1 
), rcte_down(id, date, ammount, rn, running) as 
(
    select *, ammount as running 
    from data2 
    union all 
    select d.*, d.ammount + rcte_down.running 
    from data2 d 
    join rcte_down on rcte_down.running < 0 and d.rn = rcte_down.rn + 1 
) 
select id, date, min(running) ammount 
from rcte_down 
group by id, date 
having min(running) > 0 

demo

Ich kann mir vorstellen, dass Sie nur die Aufwärtsausbreitung verwenden und die Abwärtsausbreitung der ersten Reihe wird in einigen prozeduralen Sprache getan. Die Abwärtsausbreitung ist eine Abtastung durch wenige erste Zeilen, daher kann die rekursive Abfrage ein Hammer auf einer Mücke sein.

+0

@Mike, danke für den interessanten Einblick in meine Lösung. Ich habe es benutzt. Beste. –

1

Ich füge Client-ID in Tabelle für allgemeinere Lösung. Dann habe ich den als XML gespeicherten Stack im Abfragefeld implementiert. Und emuliert einen Programmzyklus mit rekursiven CTE:

with Data as( -- Numbering rows for iteration on CTE 
    select Client, id, Amount, 
      cast(row_number() over(partition by Client order by Date) as int) n 
     from TabW 
), 
CTE(Client, n, stack) as(-- Recursive CTE 
    select Client, 1, cast(NULL as xml) from Data where n=1 
    UNION ALL 
    select D.Client, D.n+1, (
-- Stack operations to process current row (D) 
select row_number() over(order by n) n, 
     -- Use calculated amount in first positive and oldest stack cell 
     -- Else preserve value stored in stack 
     case when n=1 or (n=0 and last=1) then new else Amount end Amount, 
     -- Set ID in stack cell for positive and new data 
     case when n=1 and D.Amount>0 then D.id else id end id 
    from (
    select Y.Amount, Y.id, new, 
      -- Count positive stack entries 
      sum(case when new<=0 or (n=0 and Amount<0) then 0 else 1 end) over (order by n) n, 
      row_number() over(order by n desc) last -- mark oldest stack cell by 1 
     from (
     select X.*,sum(Amount) over(order by n) new 
      from (
       select case when C.stack.value('(/row/@Amount)[1]','int')<0 then -1 else 0 end n, 
         D.Amount, D.id -- Data from new record 
       union all -- And expand current stack in XML to table 
      select node.value('@n','int') n, node.value('@Amount','int'), node.value('@id','int') 
       from C.stack.nodes('//row') N(node) 
     ) X 
    ) Y where n>=0 -- Suppress new cell if the stack contained a negative amount 
) Z 
where n>0 or (n=0 and last=1) 
    for xml raw, type 

      ) 
     from Data D, CTE C 
    where D.n=C.n and D.Client=C.Client 
) -- Expand stack into result table 
select CTE.Client, node.value('@id','int') id, node.value('@Amount','int') 
    from CTE join (select Client, max(n) max_n from Data group by Client) X on CTE.Client=X.Client and CTE.n=X.max_n+1 
cross apply stack.nodes('//row') N(node) 
order by CTE.Client, node.value('@n','int') desc 

-Test auf sqlfiddle.com

Ich denke, diese Methode ist langsamer als @RadimBača. Und es wird gezeigt, dass die Möglichkeiten der Implementierung eines sequentiellen Algorithmus für SQL demonstriert werden.