Optymalizacja kursora T-SQL

0

Cześć,
zrobiłem kursor który wylicza kolejno wartości dla zgrupowanych ID (ID_PODRZEDNE) wg. ID_GLOWNE. ID_GLOWNE zawsze jest najmniejsze w grupowaniu i zawsze ma wartość LP = 1 AND ID_GLOWNE = ID_PODRZEDNE.
Zarówno id_glowne jak i id_podrzędne są unikatowe (występują tylko raz w kolumnie ID_Podrzędne). Poniżej przyklad.

CREATE TABLE DBO.KURSOR (ID_GLOWNE INT, ID_PODRZEDNE INT,WARTOSC INT)

INSERT INTO DBO.KURSOR (ID_GLOWNE,ID_PODRZEDNE,WARTOSC)
VALUES 
(999,999,5),
(999,1000,10),
(999,2000,20),
(999,3000,30),
(999,4000,30),
(999,5000,80),
(999,6000,40),

(1111,1111,15),
(1111,2222,15),
(1111,3333,50),
(1111,4444,70),
(1111,5555,20)


SELECT *,ROW_NUMBER() OVER (PARTITION BY ID_GLOWNE ORDER BY ID_PODRZEDNE ASC) LP
INTO #TABELA
FROM DBO.KURSOR

DECLARE KURSOR CURSOR FOR
SELECT * FROM #TABELA
--SELECT *,ROW_NUMBER() OVER (PARTITION BY ID_GLOWNE ORDER BY ID_PODRZEDNE ASC) FROM DBO.KURSOR

DECLARE @ID_GLOWNE INT
DECLARE @ID_PODRZEDNE INT
DECLARE @WARTOSC INT
DECLARE @LP INT

DECLARE @TABLE TABLE (ID_GLOWNE INT,ID_PODRZEDNE INT,WARTOSC INT, LP INT, WARTOSC_NOWA INT )

OPEN KURSOR
FETCH NEXT FROM KURSOR INTO @ID_GLOWNE,@ID_PODRZEDNE,@WARTOSC,@LP
	WHILE @@FETCH_STATUS = 0
	BEGIN
		IF @LP = 1
			BEGIN
			INSERT INTO @TABLE (ID_GLOWNE ,ID_PODRZEDNE ,WARTOSC , LP , WARTOSC_NOWA)
			VALUES (@ID_GLOWNE,@ID_PODRZEDNE,@WARTOSC,@LP,@WARTOSC)
			END
		IF @LP > 1
			BEGIN
			DECLARE @WARTOSC_NOWA INT
			SET @WARTOSC_NOWA = (SELECT WARTOSC FROM #TABELA WHERE ID_GLOWNE = @ID_GLOWNE AND LP = @LP - 1)
			INSERT INTO @TABLE (ID_GLOWNE ,ID_PODRZEDNE ,WARTOSC , LP , WARTOSC_NOWA)
			VALUES (@ID_GLOWNE,@ID_PODRZEDNE,@WARTOSC,@LP,@WARTOSC - @WARTOSC_NOWA)
			END
	FETCH NEXT FROM KURSOR INTO @ID_GLOWNE,@ID_PODRZEDNE,@WARTOSC,@LP
	
	END

	CLOSE KURSOR
	DEALLOCATE KURSOR
	DROP TABLE #TABELA

	SELECT * FROM @TABLE

Mój kod działa jednak przy większej ilości rekordów trochę zwalnia i tutaj moje pytanie czy ktoś ma pomysł na jakąś jego optymalizację, zauważyłem że bardzo spowalnia go ta linijka:

SET @WARTOSC_NOWA = (SELECT WARTOSC FROM #TABELA WHERE ID_GLOWNE = @ID_GLOWNE AND LP = @LP - 1)

Jednak nie mam pomysłu jak inaczej to rozwiązać. Za sugestie będę bardzo wdzięczny.

3

Wg mnie nie trzeba używać kursora do tego zadania. Zwrotkę można uzyskać jednym zapytaniem:

with dane as (
SELECT id_glowne, id_podrzedne, wartosc ,ROW_NUMBER() OVER (PARTITION BY ID_GLOWNE ORDER BY ID_PODRZEDNE ASC) LP
FROM KURSOR
    )

select d1.id_glowne, d1.id_podrzedne, d1.wartosc, d1.lp, d1.wartosc - coalesce(d2.wartosc,0) wartosc_nowa
from dane d1
left join dane d2 on (d1.id_glowne = d2.id_glowne and d2.lp = d1.lp-1)

Testowałem na postgresie, ale powinno zadziałać również na ms sql. Mam nadzieję, że dobrze oddałem logikę tego co chcesz uzyskać w efekcie końcowym.

0
areklipno napisał(a):

Wg mnie nie trzeba używać kursora do tego zadania. Zwrotkę można uzyskać jednym zapytaniem:

with dane as (
SELECT id_glowne, id_podrzedne, wartosc ,ROW_NUMBER() OVER (PARTITION BY ID_GLOWNE ORDER BY ID_PODRZEDNE ASC) LP
FROM KURSOR
    )

select d1.id_glowne, d1.id_podrzedne, d1.wartosc, d1.lp, d1.wartosc - coalesce(d2.wartosc,0) wartosc_nowa
from dane d1
left join dane d2 on (d1.id_glowne = d2.id_glowne and d2.lp = d1.lp-1)

Testowałem na postgresie, ale powinno zadziałać również na ms sql. Mam nadzieję, że dobrze oddałem logikę tego co chcesz uzyskać w efekcie końcowym.

Dzięki za pomoc :)

0

@areklipno:
Cześć, sorry że Cię pinguje ale pomogłeś mi z tym tematem i teraz mam jeszcze jedną zadwozdke, mianowicie co jeżeli istnieją dwie i więcej ścieżek w rekurencji. Czy da sie je jakoś oznaczać?

CREATE TABLE DBO.CTE (ID INT,ID2 INT)

INSERT INTO DBO.CTE
(ID,ID2) VALUES
(10,20),
(10,30),
(20,40),
(30,60),
(40,80),
(60,120),
(80,160),
(120,240)
WITH CTE
AS 
(
SELECT ID,ID2,0 KROK FROM DBO.CTE WHERE ID=10
UNION ALL
SELECT D.ID,D.ID2, KROK + 1 FROM CTE C INNER JOIN DBO.CTE D ON C.ID2=D.ID


)
SELECT * FROM CTE

Jak widać są tutaj dwie ścieżki i chciałbym je pogrupować.
screenshot-20220623174327.png

0

@Tylywizor: , gdybyś miał możliwość dołożenia do danych wejściowych jakiejś liczby porządkowej byłoby to proste...

CREATE TABLE DBO.CTE (lp int, ID INT,ID2 INT)

INSERT INTO DBO.CTE
(lp, ID,ID2) VALUES
(1, 10,20),
(2, 10,30),
(3, 20,40),
(4, 30,60),
(5, 40,80),
(6, 60,120),
(7, 80,160),
(8, 120,240)

i wtedy:

WITH CTE
AS 
(
SELECT lp, ID,ID2,0 KROK FROM DBO.CTE WHERE ID=10
UNION ALL
SELECT c.lp , D.ID,D.ID2, KROK + 1 FROM CTE C INNER JOIN DBO.CTE D ON C.ID2=D.ID
) 
SELECT id, id2, krok, lp
FROM CTE

i rezultat wyglądałby wtedy:

screenshot-20220623224217.png

1 użytkowników online, w tym zalogowanych: 0, gości: 1