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:
- Zadeklaruj kursor
- Otwórz kursor
- Pobierz wiersze
- Zamknij kursor
- 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:
- Musimy dodać „_Backup” do nazw wszystkich istniejących tabel w bazie danych
- 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:
- Zadeklaruj zmienną tabeli
- Przechowuj nazwy i identyfikatory tabel w zadeklarowanej przez nas zmiennej tabeli
- Ustaw licznik na 1 i uzyskaj całkowitą liczbę rekordów ze zmiennej tabeli
- Użyj pętli „while”, o ile licznik jest mniejszy lub równy całkowitej liczbie rekordów
- 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:
- Zadeklaruj tabelę tymczasową
- Przechowuj nazwy i identyfikatory tabel w tabeli tymczasowej, którą właśnie zadeklarowaliśmy
- Ustaw licznik na 1 i uzyskaj całkowitą liczbę rekordów z tabeli tymczasowej
- Użyj pętli „while”, o ile licznik jest mniejszy lub równy całkowitej liczbie rekordów
- 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:
- 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)
- Przywróć początkowe nazwy tabel z tego artykułu za pomocą alternatywnych metod (tabele tymczasowe i zmienne tabel)
- 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