Database
 sql >> Baza danych >  >> RDS >> Database

Zastępowanie kursorów SQL alternatywami w celu uniknięcia problemów z wydajnością

W tym artykule przyjrzymy się niektórym alternatywom dla używania kursorów SQL, które mogą pomóc w uniknięciu problemów z wydajnością spowodowanych przez użycie kursorów.

Zanim omówimy alternatywy, przejrzyjmy ogólną koncepcję kursorów SQL.

Szybki przegląd kursorów SQL

Kursory SQL są używane głównie tam, gdzie operacje oparte na zbiorach nie mają zastosowania i wymagane jest uzyskiwanie dostępu do danych i wykonywanie operacji jeden wiersz naraz, zamiast stosowania pojedynczej operacji opartej na zbiorach na całym obiekcie (takim jak tabela lub zbiór tabele).

Prosta definicja

Kursor SQL zapewnia dostęp do danych w jednym wierszu naraz, zapewniając w ten sposób bezpośrednią kontrolę wiersz po wierszu nad zbiorem wyników.

Definicja Microsoft

Zgodnie z dokumentacją Microsoft, instrukcje Microsoft SQL Server tworzą kompletny zestaw wyników, ale są chwile, kiedy najlepiej jest przetwarzać go po jednym wierszu na raz – co można zrobić, otwierając kursor na zestawie wyników.

Pięcioetapowy proces używania kursora

Proces używania kursora SQL można ogólnie opisać w następujący sposób:

  1. Zadeklaruj kursor
  2. Otwórz kursor
  3. Pobierz wiersze
  4. Zamknij kursor
  5. Zwolnij kursor

Ważna uwaga

Należy pamiętać, że według Vaidehi Pandere kursory są wskaźnikami, które zajmują pamięć systemową – która w innym przypadku byłaby zarezerwowana dla innych ważnych procesów. Dlatego przemierzanie dużego zestawu wyników za pomocą kursorów zwykle nie jest najlepszym pomysłem – chyba że istnieje uzasadniony powód, aby to zrobić.

Aby uzyskać bardziej szczegółowe informacje na ten temat, zapoznaj się z moim artykułem Jak używać kursorów SQL do celów specjalnych.

Przykład kursora SQL

Najpierw przyjrzymy się przykładowi wykorzystania kursora SQL do zmiany nazwy obiektów bazy danych jeden po drugim.

Aby utworzyć kursor SQL, którego potrzebujemy, skonfigurujmy przykładową bazę danych, abyśmy mogli uruchamiać na niej nasze skrypty.

Konfiguracja przykładowej bazy danych (UniversityV3)

Uruchom następujący skrypt, aby utworzyć i wypełnić przykładową bazę danych UniversityV3 dwiema tabelami:

-- (1) Create UniversityV3 sample database

CREATE DATABASE UniversityV3;

GO

USE UniversityV3

-- (2) Create Course table

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Course') 

DROP TABLE dbo.Course 

CREATE TABLE [dbo].[Course] (

    [CourseId] INT           IDENTITY (1, 1) NOT NULL,

    [Name]     VARCHAR (30)  NOT NULL,

    [Detail]   VARCHAR (200) NULL,

    CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED ([CourseId] ASC)

);

-- (3) Create Student table

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES T WHERE T.TABLE_NAME='Student') 

DROP TABLE dbo.Student 

CREATE TABLE [dbo].[Student] (

    [StudentId] INT           IDENTITY (1, 1) NOT NULL,

    [Name]      VARCHAR (30)  NULL,

    [Course]    VARCHAR (30)  NULL,

    [Marks]     INT           NULL,

    [ExamDate]  DATETIME2 (7) NULL,

    CONSTRAINT [PK_Student] PRIMARY KEY CLUSTERED ([StudentId] ASC)

);

-- (4) Populate Course table

SET IDENTITY_INSERT [dbo].[Course] ON

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (1, N'DevOps for Databases', N'This is about DevOps for Databases')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (2, N'Power BI Fundamentals', N'This is about Power BI Fundamentals')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (3, N'T-SQL Programming', N'About T-SQL Programming')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (4, N'Tabular Data Modeling', N'This is about Tabular Data Modeling')

INSERT INTO [dbo].[Course] ([CourseId], [Name], [Detail]) VALUES (5, N'Analysis Services Fundamentals', N'This is about Analysis Services Fundamentals')

SET IDENTITY_INSERT [dbo].[Course] OFF



-- (5) Populate Student table

SET IDENTITY_INSERT [dbo].[Student] ON

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (1, N'Asif', N'Database Management System', 80, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (2, N'Peter', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (3, N'Sam', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (4, N'Adil', N'Database Management System', 85, N'2016-01-01 00:00:00')

INSERT INTO [dbo].[Student] ([StudentId], [Name], [Course], [Marks], [ExamDate]) VALUES (5, N'Naveed', N'Database Management System', 90, N'2016-01-01 00:00:00')

SET IDENTITY_INSERT [dbo].[Student] OFF

Utwórz kursor SQL, aby zmienić nazwy tabel (_Backup)

Teraz rozważ spełnienie następującej specyfikacji za pomocą kursora:

  1. Musimy dodać „_Backup” do nazw wszystkich istniejących tabel w bazie danych
  2. Table, które już mają w nazwie „_Backup” nie powinny być zmieniane

Utwórzmy kursor SQL, aby zmienić nazwy wszystkich tabel w przykładowej bazie danych, dodając „_Backup” do nazwy każdej tabeli, jednocześnie upewniając się, że tabele zawierające „_Backup” w nazwie nie zostaną ponownie zmienione przez uruchomienie następującego kodu:

-- Declaring the Student cursor to rename all tables by adding ‘_backup’ to their names and also making sure that all tables that are already named correctly will be skipped:

USE UniversityV3
GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T;

OPEN Student_Cursor

FETCH NEXT FROM Student_Cursor INTO @TableName

WHILE @@FETCH_STATUS = 0

BEGIN

IF RIGHT(@TableName,6)<>'Backup' -- If Backup table does not exist then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

FETCH NEXT FROM Student_Cursor -- Get next row data into cursor and store it in variables

INTO @TableName

END

CLOSE Student_Cursor -- Close cursor locks on the rows

DEALLOCATE Student_Cursor -- Release cursor reference

Uruchom skrypt zmiany nazwy i wyświetl wyniki

Teraz naciśnij F5 w SSMS (SQL Server Management Studio), aby uruchomić skrypt i zobaczyć wyniki:

Odświeżenie nazw tabel w eksploratorze obiektów SSMS wyraźnie pokazuje, że pomyślnie zmieniliśmy je zgodnie z opisem.

Uruchommy skrypt ponownie, ponownie naciskając F5 i spójrzmy na wyniki:

Tworzenie kursora SQL w celu zresetowania _nazwy kopii zapasowych

Musimy również stworzyć skrypt, który używa kursora SQL, aby przywrócić nazwy tabel, które właśnie zmieniliśmy, z powrotem do pierwotnych – zrobimy to, usuwając „_Backup” z ich nazw.

Poniższy skrypt pozwoli nam to zrobić:

-- Declare the Student cursor to reset tables names _backup to their original forms by removing ‘_backup’

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE Student_Cursor CURSOR FOR SELECT T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T;

OPEN Student_Cursor

FETCH NEXT FROM Student_Cursor INTO @TableName

WHILE @@FETCH_STATUS = 0

BEGIN

IF RIGHT(@TableName,6)='Backup' -- If Backup table name exists then reset (rename) it

BEGIN

SET @NewTableName=SUBSTRING(@TableName,1,LEN(@TableName)-7) -- Remove _Backup from the table name

EXEC sp_rename @TableName,@NewTableName -- Rename table 

END

ELSE

PRINT 'Backup table name already reset: '[email protected]

FETCH NEXT FROM Student_Cursor – Get the data of the next row into cursor and store it in variables

INTO @TableName

END

CLOSE Student_Cursor -- Close cursor locks on the rows

DEALLOCATE Student_Cursor -- Release cursor reference

Uruchom skrypt resetowania i wyświetl wyniki

Uruchomienie skryptu pokazuje, że nazwy tabel zostały pomyślnie zresetowane:

Były to przykłady niektórych scenariuszy, w których trudno uniknąć użycia kursorów SQL ze względu na charakter wymagania. Jednak nadal można znaleźć alternatywne podejście.

Alternatywy kursora SQL

Istnieją dwie najpopularniejsze alternatywy dla kursorów SQL, więc przyjrzyjmy się szczegółowo każdemu z nich.

Alternatywna 1:Zmienne tabelowe

Jedną z tych alternatyw są zmienne tabelowe.

Zmienne tabel, podobnie jak tabele, mogą przechowywać wiele wyników – ale z pewnymi ograniczeniami. Zgodnie z dokumentacją Microsoft, zmienna tabeli jest specjalnym typem danych używanym do przechowywania zestawu wyników do późniejszego przetworzenia.

Należy jednak pamiętać, że zmiennych tabeli najlepiej używać z małymi zestawami danych.

Zmienne tabel mogą być bardzo wydajne w przypadku zapytań na małą skalę, ponieważ działają jak zmienne lokalne i są czyszczone automatycznie po wyjściu z zakresu.

Strategia dotycząca zmiennych tabeli:

Użyjemy zmiennych tabeli zamiast kursorów SQL, aby zmienić nazwy wszystkich tabel z bazy danych, wykonując następujące czynności:

  1. Zadeklaruj zmienną tabeli
  2. Przechowuj nazwy i identyfikatory tabel w zadeklarowanej przez nas zmiennej tabeli
  3. Ustaw licznik na 1 i uzyskaj całkowitą liczbę rekordów ze zmiennej tabeli
  4. Użyj pętli „while”, o ile licznik jest mniejszy lub równy całkowitej liczbie rekordów
  5. Wewnątrz pętli „while” będziemy zmieniać nazwy tabel pojedynczo, o ile nie zostały już zmienione i zwiększając licznik dla każdej tabeli

Kod zmiennej tabeli:

Uruchom następujący skrypt SQL, który tworzy i używa zmiennej tabeli do zmiany nazw tabel:

-- Declare Student Table Variable to rename all tables by adding ‘_backup’ t their name and also making sure that already renamed tables are skipped

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

DECLARE @StudentTableVar TABLE -- Declaring a table variable to store tables names
(
TableId INT,

TableName VARCHAR(40))

INSERT INTO @StudentTableVar -- insert tables names into the table variable 

SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T

DECLARE @TotalRows INT=(SELECT COUNT(*) FROM @StudentTableVar),@i INT=1 -- Get total rows and set counter to 1

WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records

BEGIN -- ‘While’ loop begins here

SELECT @TableName=TableName from @StudentTableVar WHERE [email protected]

IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add _Backup to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

SET @[email protected]+1

END -- 'While' loop ends here

Uruchom skrypt i wyświetl wyniki

Teraz uruchommy skrypt i sprawdźmy wyniki:

Alternatywna 2:Tabele tymczasowe

Możemy również użyć tabel tymczasowych zamiast kursorów SQL, aby iterować zestaw wyników po jednym wierszu na raz.

Tabele tymczasowe są używane od dłuższego czasu i stanowią doskonały sposób na zastąpienie kursorów w dużych zestawach danych.

Podobnie jak zmienne tabel, tabele tymczasowe mogą przechowywać zestaw wyników, dzięki czemu możemy wykonać niezbędne operacje, przetwarzając je za pomocą algorytmu iteracyjnego, takiego jak pętla „while”.

Tymczasowa strategia tabeli:

Użyjemy tabeli tymczasowej, aby zmienić nazwy wszystkich tabel w przykładowej bazie danych, wykonując następujące czynności:

  1. Zadeklaruj tabelę tymczasową
  2. Przechowuj nazwy i identyfikatory tabel w tabeli tymczasowej, którą właśnie zadeklarowaliśmy
  3. Ustaw licznik na 1 i uzyskaj całkowitą liczbę rekordów z tabeli tymczasowej
  4. Użyj pętli „while”, o ile licznik jest mniejszy lub równy całkowitej liczbie rekordów
  5. W pętli „while” zmieniaj nazwy tabel pojedynczo, o ile nie zostały już zmienione, i zwiększ licznik dla każdej tabeli

Zresetuj tabele

Musimy zresetować nazwy tabel do ich początkowej postaci, usuwając „_Backup” z końca ich nazw, więc uruchom ponownie skrypt resetowania, który już napisaliśmy i używaliśmy powyżej, abyśmy mogli zastosować inną metodę zmiany nazw tabel.

Tymczasowy kod tabeli:

Uruchom następujący skrypt SQL, aby utworzyć i użyć tymczasowej tabeli do zmiany nazw wszystkich tabel w naszej bazie danych:

-- Declare the Student Temporary Table to rename all tables by adding ‘_backup’ to their names while also making sure that already renamed tables are skipped

USE UniversityV3

GO

DECLARE @TableName VARCHAR(50) -- Existing table name
       ,@NewTableName VARCHAR(50) -- New table name

CREATE TABLE #Student -- Declaring a temporary table

(
TableId INT,
TableName VARCHAR(40)
)

INSERT INTO #Student -- insert tables names into the temporary table

SELECT ROW_NUMBER() OVER(ORDER BY T.TABLE_NAME),T.TABLE_NAME FROM INFORMATION_SCHEMA.TABLES T

DECLARE @TotalRows INT=(SELECT COUNT(*) FROM #Student),@i INT=1 -- Get the total amount of rows and set the counter to 1

WHILE @i<[email protected] -- begin as long as i (counter) is less than or equal to the total number of records

BEGIN -- ‘While’ loop begins here

SELECT @TableName=TableName from #Student WHERE [email protected]

IF RIGHT(@TableName,6)<>'Backup' -- If a Backup table does not exist, then rename the table

BEGIN

SET @[email protected]+'_Backup' -- Add ‘_Backup’ to the table’s current name

EXEC sp_rename @TableName,@NewTableName -- Rename the table as OLD table

END

ELSE

PRINT 'Backup table name already exists: '[email protected]

SET @[email protected]+1

END -- While loop ends here

DROP TABLE #Student

Uruchom skrypt i sprawdź wyniki

Teraz uruchommy skrypt, aby wyświetlić wyniki:

Rzeczy do zrobienia

Teraz, gdy znasz już alternatywy dla kursorów SQL — takie jak używanie zmiennych tabel i tabel tymczasowych — spróbuj wykonać następujące czynności, aby wygodnie zastosować tę wiedzę w praktyce:

  1. Twórz i zmieniaj nazwy indeksów wszystkich tabel w przykładowej bazie danych – najpierw za pomocą Kursora, a następnie za pomocą alternatywnych metod (zmienne tabel i tabele tymczasowe)
  2. Przywróć początkowe nazwy tabel z tego artykułu za pomocą alternatywnych metod (tabele tymczasowe i zmienne tabel)
  3. Możesz również odwołać się do pierwszych przykładów w moim artykule Jak używać kursorów SQL do celów specjalnych i spróbować wypełnić tabele dużą ilością wierszy oraz zmierzyć statystyki i czas dla zapytań, aby porównać podstawową metodę kursora z alternatywami

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Łączenie RazorSQL z Salesforce.com

  2. Zatrzymywanie błędów serwera połączonego

  3. Przyjazne dla przepustowości profilowanie zapytań dla Azure SQL Database

  4. Zmiana nazwy podłączanej bazy danych

  5. Łączenie się z 4D z Javy