V tomto článku se podíváme na některé alternativy k používání kurzorů SQL, které mohou pomoci vyhnout se problémům s výkonem způsobeným používáním kurzorů.
Než probereme alternativy, zopakujme si obecný koncept kurzorů SQL.
Rychlý přehled kurzorů SQL
Kurzory SQL se primárně používají tam, kde operace založené na množině nelze použít a musíte přistupovat k datům a provádět operace jeden řádek po druhém, místo abyste na celý objekt (jako je tabulka nebo sada tabulky).
Jednoduchá definice
Kurzor SQL poskytuje přístup k datům jeden řádek po druhém, čímž vám poskytuje přímou kontrolu po řádcích nad sadou výsledků.
Definice společnosti Microsoft
Podle dokumentace Microsoftu produkují příkazy Microsoft SQL Server kompletní sadu výsledků, ale jsou chvíle, kdy je nejlepší je zpracovat po jednom řádku – což lze provést otevřením kurzoru na výsledné sadě.
Proces použití kurzoru v 5 krocích
Proces použití SQL kurzoru lze obecně popsat takto:
- Deklarovat kurzor
- Otevřít kurzor
- Načíst řádky
- Zavřít kurzor
- Přidělit kurzor
Důležitá poznámka
Mějte prosím na paměti, že podle Vaidehi Pandere jsou kurzory ukazatele, které zabírají vaši systémovou paměť – která by jinak byla vyhrazena pro jiné důležité procesy. To je důvod, proč procházení velké sady výsledků pomocí kurzorů obvykle není nejlepší nápad – pokud k tomu neexistuje legitimní důvod.
Podrobnější informace o tom najdete v mém článku Jak používat SQL kurzory pro speciální účely.
Příklad SQL kurzoru
Nejprve se podíváme na příklad toho, jak lze SQL kurzor použít k přejmenování databázových objektů jeden po druhém.
Abychom vytvořili kurzor SQL, který potřebujeme, nastavíme ukázkovou databázi, abychom proti ní mohli spouštět naše skripty.
Nastavení ukázkové databáze (UniversityV3)
Spusťte následující skript k vytvoření a naplnění ukázkové databáze UniversityV3 dvěma tabulkami:
-- (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
Vytvořte kurzor SQL pro přejmenování tabulek (_Backup)
Nyní zvažte splnění následující specifikace pomocí kurzoru:
- Musíme přidat ‚_Backup‘ k názvům všech existujících tabulek v databázi
- Tabulky, které již mají v názvu „_Backup“, by neměly být přejmenovány
Vytvořme SQL kurzor pro přejmenování všech tabulek ve vzorové databázi přidáním ‚_Backup‘ k názvu každé tabulky a zároveň zajistíme, že tabulky obsahující ‚_Backup‘ ve svém názvu nebudou znovu přejmenovány spuštěním následujícího kódu:
-- 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
Spusťte skript pro přejmenování a zobrazte výsledky
Nyní stisknutím klávesy F5 v SSMS (SQL Server Management Studio) spusťte skript a zobrazte výsledky:
Obnovení názvů tabulek v průzkumníku objektů SSMS jasně ukazuje, že jsme je úspěšně změnili, jak je uvedeno.
Spusťte skript znovu opětovným stisknutím F5 a podívejte se na výsledky:
Vytvoření kurzoru SQL pro resetování pojmenování _Backup
Potřebujeme také vytvořit skript, který pomocí SQL kurzoru vrátí názvy tabulek, které jsme právě změnili, zpět na původní – provedeme to odstraněním „_Backup“ z jejich názvů.
Skript níže nám to umožní:
-- 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
Spusťte skript Resetovat a zobrazte výsledky
Spuštění skriptu ukazuje, že názvy tabulek byly úspěšně resetovány:
Toto byly příklady některých scénářů, ve kterých je obtížné se vyhnout použití kurzorů SQL kvůli povaze požadavku. Stále je však možné najít alternativní přístup.
Alternativy kurzoru SQL
Pro kurzory SQL existují dvě nejběžnější alternativy, takže se na každou z nich podíváme podrobně.
Alternativa 1:Proměnné tabulky
Jednou z těchto alternativ jsou proměnné tabulky.
Proměnné tabulky, stejně jako tabulky, mohou ukládat více výsledků – ale s určitým omezením. Podle dokumentace společnosti Microsoft je proměnná tabulky speciální datový typ používaný k uložení sady výsledků pro pozdější zpracování.
Mějte však na paměti, že tabulkové proměnné se nejlépe používají s malými soubory dat.
Proměnné tabulky mohou být velmi efektivní pro dotazy v malém měřítku, protože fungují jako místní proměnné a po opuštění rozsahu se automaticky vyčistí.
Strategie proměnných tabulek:
K přejmenování všech tabulek z databáze použijeme tabulkové proměnné místo SQL kurzorů podle následujících kroků:
- Deklarujte proměnnou tabulky
- Uložte názvy a ID tabulek do proměnné tabulky, kterou jsme deklarovali
- Nastavte počítadlo na 1 a získejte celkový počet záznamů z proměnné tabulky
- Používejte cyklus „while“, pokud je počítadlo menší nebo rovno celkovému počtu záznamů.
- Uvnitř cyklu „while“ přejmenujeme tabulky jednu po druhé, pokud již nejsou přejmenovány, a zvýšíme počítadlo pro každou tabulku.
Kód proměnné tabulky:
Spusťte následující skript SQL, který vytvoří a použije proměnnou tabulky k přejmenování tabulek:
-- 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
Spusťte skript a zobrazte výsledky
Nyní spustíme skript a zkontrolujeme výsledky:
Alternativa 2:Dočasné tabulky
Můžeme také použít dočasné tabulky místo SQL kurzorů k iteraci sady výsledků jeden řádek po druhém.
Dočasné tabulky se používají již dlouhou dobu a poskytují vynikající způsob, jak nahradit kurzory u velkých souborů dat.
Stejně jako proměnné tabulky mohou dočasné tabulky obsahovat sadu výsledků, takže můžeme provádět potřebné operace jejím zpracováním pomocí iteračního algoritmu, jako je smyčka „while“.
Strategie dočasné tabulky:
K přejmenování všech tabulek v ukázkové databázi použijeme dočasnou tabulku podle následujících kroků:
- Deklarujte dočasnou tabulku
- Uložte názvy a ID tabulek do dočasné tabulky, kterou jsme právě deklarovali
- Nastavte počítadlo na 1 a získejte celkový počet záznamů z dočasné tabulky
- Používejte cyklus „while“, pokud je počítadlo menší nebo rovno celkovému počtu záznamů.
- V rámci cyklu „while“ přejmenovávejte tabulky jednu po druhé, pokud již nejsou přejmenovány, a zvyšujte počítadlo pro každou tabulku.
Resetovat tabulky
Potřebujeme obnovit názvy tabulek do jejich původní podoby odstraněním '_Backup' z konce jejich názvů, takže prosím znovu spusťte resetovací skript, který jsme již napsali a použili výše, abychom mohli použít jinou metodu přejmenování tabulek.
Dočasný kód tabulky:
Spuštěním následujícího skriptu SQL vytvořte a použijte dočasnou tabulku k přejmenování všech tabulek v naší databázi:
-- 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
Spusťte skript a zkontrolujte výstup
Nyní spustíme skript a zobrazíme výsledky:
Co dělat
Nyní, když jste obeznámeni s alternativami ke kurzorům SQL – jako je používání proměnných tabulek a dočasných tabulek – zkuste provést následující kroky, abyste se s aplikací těchto znalostí v praxi pohodlně seznámili:
- Vytvářejte a přejmenovávejte indexy všech tabulek ve vzorové databázi – nejprve pomocí kurzoru a poté pomocí alternativních metod (tabulkové proměnné a dočasné tabulky)
- Vraťte názvy tabulek z tohoto článku zpět na jejich původní názvy pomocí alternativních metod (dočasné tabulky a proměnné tabulky)
- Můžete se také odkázat na první příklady v mém článku Jak používat kurzory SQL pro speciální účely a zkusit naplnit tabulky velkým množstvím řádků a měřit statistiky a čas pro dotazy, abyste mohli porovnat základní metodu kurzoru s alternativami.