T-SQL Vermijd cursor casus

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
Hallo

Even een achtergrond verhaal, ik ben aan het wisselen van software pakket dat wordt gebruikt om gegevens van huurders bij te houden.

Ik ben nu bezig om de transacties om te zetten tussen de systemen, daarbij moet ik rekening houden met welke kosten wel en niet zijn betaald.

Voorbeeld:
Jan heeft in februari als kosten:
  • 400 euro huur
  • 50 euro service kosten
  • 100 euro incidentele kosten (bijvoorbeeld omdat Jan rommel in de portiek heeft achtergelaten)
Jan betaald de huur en de service kosten, de incidentele kosten betaald hij niet.

Jan heeft in maart de volgende kosten
  • 400 euro huur
  • 50 euro service kosten
Jan komt wat geld te kort en betaald 370 huur en 50 service kosten.

Het totaal aan kosten in deze periode is 1000 euro. het totaal betaald bedrag door jan is 870, verdeeld over 3 betalingen (1x 450, 1x 370 1x 50)

Ik moet nu deze transacties overzetten in het nieuwe systeem, maar ik moet dus rekening houden met welke rekeningen wel en niet zijn betaald. Ik kan dus niet alle rekeningen en betalingen op een hoop gooien (dat zou bijvoorbeeld betekenen dat de incidentele kosten worden betaald terwijl dit niet het geval is)
Dit is het punt waar ik een beetje moeite mee heb om dit goed SET gebaseerd te doen.

Ik heb de onderstaande query die werkt, helaas wel met een cursor.
De tabel #Transactie en #Betaling zijn onderdeel van het nieuwe systeem. #AlBetaald heb ik zelf afgeleid uit het oude systeem (dat heel onduidelijk is in welke betalingen nu bij welke rekeningen horen, maar dat terzijde)

Kan iemand helpen om deze cusor weg te werken ?
SQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
CREATE TABLE #Transactie
(TransactieID INT IDENTITY(1,1),
BatchID INT,
TransactieNo INT,
EigenaarID INT,
ContractID INT,
TransactieType VARCHAR(3), -- CHG = Kosten, PMT = Betaling
TransactieDatum DATE,
ToepassenID INT,
BetalingID INT,
Bedrag DECIMAL(16,2) 
)

CREATE TABLE #Betaling
(BetalingID INT IDENTITY(1,1),
Bedrag DECIMAL(16,2),
ContractID INT
)

CREATE TABLE #AlBetaald
(
TransactieID INT,
BedragBetaald DECIMAL(16,2),
ContractID INT
)


-- Deze tabel bevat kosten en welke kosten zijn betaald.
INSERT INTO #Transactie
(BatchID,
TransactieNo,
EigenaarID,
ContractID,
TransactieType,
TransactieDatum,
ToepassenID,
BetalingID,
Bedrag)
VALUES
(1,1,344557,854123,'CHG','2017-01-01',1,NULL,400), -- 400 kosten voor contract 854123
(1,2,344557,854123,'CHG','2017-01-02',2,NULL,50), -- 50 kosten voor contract 854123
(1,3,344557,854123,'CHG','2017-01-02',3,NULL,100), -- 100 kosten voor contract 854123
(1,4,344557,854123,'CHG','2017-01-01',4,NULL,400), -- 400 kosten voor contract 854123
(1,5,344557,854123,'CHG','2017-01-02',5,NULL,50) -- 50 kosten voor contract 854123


INSERT INTO #Betaling
(Bedrag, ContractID)
VALUES
(-450,854123),
(-370,854123),
(-50,854123) -- totaal aantal betalingen voor contract 854123 = 870


-- deze tabel houdt bij 
INSERT INTO #AlBetaald
(TransactieID,BedragBetaald,ContractID)
VALUES
(1,400,854123), -- rekening van 400 volledig betaald
(2,50,854123), -- rekening van 50 volledig betaald
(4,370,854123), -- rekening van 400 niet volledig betaald, slechts 370 betaald
(5,50,854123) -- rekening van 50 volledig betaald

-- Dit is de cursor waar ik vanaf wil
DECLARE @CursorTransactieID INT
DECLARE @CursorBedragTeBetalen DECIMAL(16,2)
DECLARE @CursorContractID INT
DECLARE @CursorBalansOver DECIMAL(16,2)

DECLARE AlBetaaldeTransactiesToepassenCursor CURSOR FOR
  SELECT 
  TransactieID,
  Bedragbetaald,
  ContractID
  FROM #AlBetaald
OPEN AlBetaaldeTransactiesToepassenCursor

FETCH NEXT FROM AlBetaaldeTransactiesToepassenCursor INTO 
  @CursorTransactieID,
  @CursorBedragTeBetalen,
  @CursorContractID
WHILE @@FETCH_STATUS = 0 BEGIN
--Cusor logic

SET @CursorBalansOver = @CursorBedragTeBetalen
WHILE
-- Zolang als de rekening nog niet volledig betaald is (volledig = het bedrag dat voorheen betaald is) 
(SELECT ISNULL(SUM(ABS(Bedrag)),0) FROM #Transactie WHERE ToepassenID = @CursorTransactieID
  AND TransactieType = 'PMT') < ABS(@CursorBedragTeBetalen)
 AND 
 -- En het totaal aan betalingen gedaan groter is / gelijk aan dan de rekening.
 (SELECT SUM(ABS(Bedrag)) FROM #Betaling WHERE ContractID = @CursorContractID)
 >=
 (SELECT ISNULL(SUM(ABS(Bedrag)),0) FROM #Transactie
 WHERE ContractID = @CursorContractID
 AND TransactieType = 'PMT')
 BEGIN

 -- Voer het betalings record in 
 INSERT INTO #Transactie
 (BatchID,
  TransactieNo,
  EigenaarID,
  ContractID,
  TransactieType,
  TransactieDatum,
  ToepassenID,
  BetalingID,
  Bedrag)
  SELECT
  1,
  (SELECT ISNULL(MAX(TransactieNo),0)+1 FROM #Transactie), -- +1, dit is helaas geen identity in de tabel.
  (SELECT TOP 1 EigenaarID FROM #Transactie WHERE ContractID = @CursorContractID),
  @CursorContractID,
  'PMT', -- betaling
  GETDATE(),
  @CursorTransactieID, -- Pas deze betaling toe aan de rekening met id '@CursorTransactieID'
  BetalingGeldNemen.BetalingID, -- We gebruiken het geld van deze betaling om de rekening te betalen.
             CASE 
                WHEN ABS(BetalingGeldNemen.GeldOver) > ABS(@CursorBalansOver) THEN ABS(@CursorBalansOver) * -1
                ELSE BetalingGeldNemen.GeldOver * -1
             END AS PayBedrag
  FROM -- Zoek uit van welke betaling we nog geld kunnen pakken.
  (
     SELECT TOP 1 * FROM
     (SELECT
          TBetaling.BetalingID,
          (SELECT ABS(TBetaling.Bedrag)  -- het totaalbedrag dat is betaald 
             -  -- minus
            (SELECT ISNULL(SUM(ABS(Bedrag)),0) FROM 
             #Transactie WHERE BetalingID = TBetaling.BetalingID)) AS GeldOver -- het bedrag dat al is toegepast
        FROM #Betaling TBetaling
        WHERE TBetaling.ContractID = @CursorContractID
    )  Beschikbarebetaling
    WHERE Beschikbarebetaling.GeldOver > 0
  ) AS BetalingGeldNemen

  -- Herbereken hoeveel uitstaande balans er nog is voor deze transactie
  SET @CursorBalansOver = ABS(@CursorBedragTeBetalen) - 
  (SELECT ISNULL(SUM(ABS(Bedrag)),0) FROM #Transactie WHERE ToepassenID = @CursorTransactieID AND TransactieType = 'PMT')
 END

FETCH NEXT FROM AlBetaaldeTransactiesToepassenCursor INTO 
  @CursorTransactieID,
  @CursorBedragTeBetalen,
  @CursorContractID
END
CLOSE AlBetaaldeTransactiesToepassenCursor  
DEALLOCATE AlBetaaldeTransactiesToepassenCursor


SELECT * FROM #Transactie
SELECT * FROM #Betaling
SELECT * FROM #AlBetaald

DROP TABLE #Transactie
DROP TABLE #Betaling
DROP TABLE #AlBetaald

[ Voor 0% gewijzigd door RobIII op 24-10-2017 22:11 . Reden: quote naar code tags aangepast ]

Alle reacties


Acties:
  • +4 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Als het een eenmalige conversie/import is; who cares dan om die cursor? Als het werkt en doet wat het moet doen (binnen afzienbare tijd) waarom zou je er dan nog moeite in steken het "mooi" te maken? Met andere woorden: waarom wil je van die cursor af? Welk probleem probeer je op te lossen?

Verder: als je code post gebruik dan code tags aub. ;)

[ Voor 35% gewijzigd door RobIII op 24-10-2017 22:12 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
Dit is slechts een deel van de conversie, er zijn een paar miljoen transacties en op het moment duurt dit deel 2 dagen en 2 uur. We gaan big bang en dat zou betekenen dat alles 2 dagen stil zou liggen.

Acties:
  • +1 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Tja, ten eerste is de query in je cursor me nu te ingewikkeld om er überhaupt lang naar te staren zonder (achtergrond) kennis te hebben van de intieme details van je DB. Too much going on there. Dat zou ik eerst eens even proberen terug te brengen naar de essentie zodat je later de details invult. Kunnen die sub-queries niet een stuk efficiënter met wat joins o.i.d. trouwens?

Daarnaast zou ik 't eens door een profiler halen en kijken of je niet veel beter met wat slimme(re) / nieuwe(re) indexen o.i.d. het proces kunt bespoedigen (of voorgenoemde sub-queries wegwerken en dat soort 'optimalisaties' doorvoeren) met de huidige opzet zonder meteen (het idee van) die cursor te schrappen. Ik heb niet het idee dat je probleem in de cursor specifiek zit maar in de bak werk die überhaupt verzet moet worden; je kunt die cursor wel proberen eruit te halen maar als er niks essentieels verandert zal dat weinig tot geen (tijds)winst opleveren. Het is veel meer het bij elkaar sprokkelen van de data waar je probleem zit vermoed ik zo, afgaand op de code die je post.

Verder weet ik niet of je 's weekends o.i.d. ook geen downtime kunt hebben, maar anders zou je kunnen overwegen de conversie vrijdagavond te starten, zou 't maandag(ochtend) volgens je eigen estimates klaar moeten zijn (= zo goed als geen (merkbare) downtime). Of tijdens de kerstvakantie of zoiets...

[ Voor 53% gewijzigd door RobIII op 24-10-2017 22:28 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
Ik merk wel dat tijdens het uitvoeren van de query het resouce gebruik hoog is. Dat het geheugengebruik hoog is is normaal voor mssql maar dat hoge CPU gebruik valt mij op.

Afbeeldingslocatie: http://dominicr.ddns.net/82procent.PNG

plan is hier
http://dominicr.ddns.net/executionplan.zip

Ik gebruik veel temp tables (recursief), zou daar winst te behalen zijn ?

[ Voor 36% gewijzigd door dominicr op 24-10-2017 23:06 ]


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Maak daar even een screenshotje van a.u.b. Zit hier op een Mac en heb de juiste tooling niet bij de hand ;)

Upload de screenshot(s) naar imgur. o.i.d. (of je moet een t.net fotoalbum hebben natuurlijk ;) ) en zet ze met wat [img]-tags even in je laatste post.

[ Voor 22% gewijzigd door RobIII op 24-10-2017 23:12 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Onbekend
  • Registratie: Juni 2005
  • Laatst online: 23:44

Onbekend

...

Ik zie enorm veel inner-selects, en dat is echt slecht voor de performance. Ik heb zo'n vermoeden dat het statement een stuk beter kan worden. :)

Speel ook Balls Connect en Repeat


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Onbekend schreef op dinsdag 24 oktober 2017 @ 23:12:
Ik zie enorm veel inner-selects, en dat is echt slecht voor de performance.
Nou, dat kan slecht zijn voor de performance maar hoeft natuurlijk niet zo te zijn. Het is niet per-definitie zo. Daarom: meten == weten. Profiler pakken en meten!

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Wat ik zou bekijken is eigenlijk : Kan ik niet wat uit de cursor afhandeling halen?

Want ik gok zomaar dat 99% van de facturen wel betaald zijn (net afhankelijk van hoelang je historie is), kan je die wel compleet betaalde niet zonder een cursor erin zetten.
Hetzelfde met totaal onbetaalde facturen, kan je die niet op een simpelere manier afhandelen.

Dan hoef je niets aan je cursor afhandeling te veranderen, je verkleint gewoon met grote stappen het aantal wat er in je cursor komt.

Alternatief is gewoon je conversie in tijd op te splitsen. Over het algemeen gaan 99,99% van de dagelijkse vragen over de betalingen van de laatste maand, oftewel draai in 1 uur de laatste maand erdoorheen en de volgende avond draai je het laatste half-jaar erdoorheen etc. etc.

Je kan je wel blind gaan staren op de performance van je cursor functie, alleen bij menig conversie zit je grappen uit te halen die totaal niet door indexen gedekt zijn (omdat ze nooit hiervoor benodigd zijn geweest) en gebruik je tig functies die ook niet optimaal zijn voor je performance.

Een conversie is nu eenmaal een eenmalig iets (als het goed gaat in ieder geval), daar daadwerkelijk op gaan profilen is imho veelal zonde van de tijd, in mijn ervaring zijn er veel simpelere shortcuts te vinden dan te gaan profilen/optimizen etc voor een eenmalig iets.

Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
Ik heb een SQLplan, het probleem is dat voor iedere loop in de cursor er een nieuw schema lijkt te worden gemaakt.
Dit is van een tijdje terug de indexes heb ik inmiddels toegevoegd.

Optie is inderdaad om een week voor de conversie dit script uit te voeren. De tables (het zijn er maar 3) op te slaan en deze dan te importeren. We doen identity insert (want de buisiness units willen dat OwnerID en ContractID hetzelfde blijven in het nieuwe systeem) dus ik gok niet dat daar veel valkuilen in zitten ?

Afbeeldingslocatie: http://dominicr.ddns.net/sqlplan.png

[ Voor 40% gewijzigd door dominicr op 24-10-2017 23:26 ]


Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
dominicr schreef op dinsdag 24 oktober 2017 @ 23:23:
Ik heb een SQLplan, het probleem is dat voor iedere loop in de cursor er een nieuw schema lijkt te worden gemaakt.
Hoe bedoel je dit?
In wezen staat er boven elk plan welke query het betreft. Ik zie hier gewoon 3 verschillende query's staan.
Dit is van een tijdje terug de indexes heb ik inmiddels toegevoegd.
Ehm, toch niet blind hoop ik. SQL Server geeft je hints/tips en gaat ze expliciet niet voor je aanmaken.
Want in de praktijk zijn de indexen die SQL Server wil aanmaken simpelweg slecht omdat ze enkel betrekking hebben op die ene query en neit kijken naar het grotere geheel waarin een index gewoon tijd kost bij een insert/update.

Over het algemeen is een missing index hint wel een indicatie dat je een index mist. Maar je zal zelf in het grotere geheel moeten nadenken waarom hij niet al een bestaande index raakt en of hij in het grotere geheel wel alles bevat.

Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
Ik bedoel dat het plan zichzelf herhaalt, Ik zie hetzelfde statement vaker terugkomen. (het gaat hier om een van de sets binnen de cursor)

Acties:
  • 0 Henk 'm!

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
dominicr schreef op dinsdag 24 oktober 2017 @ 23:34:
Ik bedoel dat het plan zichzelf herhaalt, Ik zie hetzelfde statement vaker terugkomen. (het gaat hier om een van de sets binnen de cursor)
Ja, dat klopt toch? Vanwege je cursor loop je een aantal plannen continue. Als jij dan aangeeft dat jij de execution plans wilt zien dan ga je inderdaad continue dezelfde plannen zien als het goed is, je moet je pas zorgen gaan maken als dat niet zo is.

Mogelijk dat je andere sql profilers hebt die dan weer de execution plans kunnen samenvoegen oid (maar die ken ik niet), maar SMSS toont gewoon 1 execution plan per query exact zoals SQL Server die uitvoert.

Acties:
  • 0 Henk 'm!

  • sig69
  • Registratie: Mei 2002
  • Laatst online: 23:53
Ik denk niet dat het mogelijk is om jou in dit specifieke geval hiermee te helpen. Respect voor je OP, maar dat benaderd de uiteindelijke dataset waar dit op moet draaien natuurlijk totaal niet. Ik vermoed wel dat er gezien het weinige geheugen en je de complete omgeving door gaat er veel naar de storage gegaan wordt. Er zijn een aantal performance counters die je dat meteen kunnen vertellen:
-page lifetime expectancy: liefst zo hoog mogelijk, zou op moeten lopen "in rust"
-page reads: liefst 0
-buffer cache hit ratio: hoe dichter bij 100 hoe beter
Kijk hier eens naar misschien heb je gewoon een I/O probleem.

Roomba E5 te koop


Acties:
  • 0 Henk 'm!

  • dominicr
  • Registratie: Maart 2008
  • Laatst online: 12-11-2020
sig69 schreef op donderdag 26 oktober 2017 @ 02:06:
Ik denk niet dat het mogelijk is om jou in dit specifieke geval hiermee te helpen. Respect voor je OP, maar dat benaderd de uiteindelijke dataset waar dit op moet draaien natuurlijk totaal niet. Ik vermoed wel dat er gezien het weinige geheugen en je de complete omgeving door gaat er veel naar de storage gegaan wordt. Er zijn een aantal performance counters die je dat meteen kunnen vertellen:
-page lifetime expectancy: liefst zo hoog mogelijk, zou op moeten lopen "in rust"
-page reads: liefst 0
-buffer cache hit ratio: hoe dichter bij 100 hoe beter
Kijk hier eens naar misschien heb je gewoon een I/O probleem.
Ik neem aan dat dit niet goed is :+

Afbeeldingslocatie: https://www.mupload.nl/img/qsnfacn9xp0lg.png

Edit: 5 minuten later
Buffer cache hit ratio 1301724
Page life expectancy 242
Page reads/sec 11691502
Page writes/sec 16143081

[ Voor 11% gewijzigd door dominicr op 26-10-2017 15:52 ]


Acties:
  • +1 Henk 'm!

  • sig69
  • Registratie: Mei 2002
  • Laatst online: 23:53
Ik haal ze meestal uit de performance counters, dan komen er bij sommige wat zinnigere cijfers uit. Maar zo te zien doe je wel heel veel I/O tijdens het draaien van dit script (was ook wel te verwachten natuurlijk). Dit moet ook in de task manager terug te zien zijn.
Afhankelijk van je setup kunnen dan twee dingen meestal helpen:
-Bakken met geheugen toevoegen, zodat er niet meer naar de disks gegaan hoeft te worden om data te lezen.
-Als er dan toch iets van de disks moet komen, zorg dan dat het snel is (ssd's bij voorkeur).
Ik weet niet hoe groot de database is, maar het kan wellicht ook een optie zijn om een backup naar een snellere machine over te zetten om daar de conversie te draaien.

Roomba E5 te koop

Pagina: 1