V minulém článku jsem popsal jak nepsat dynamické SQL. Dneska ukážu jak to udělat správně. Rovnou začnu přepsanou procedurou z minulého článku.
CREATE OR ALTER PROCEDURE p_CiselnikDejNazevDleKodu
@Schema SYSNAME = 'dbo',
@Tabulka SYSNAME,
@Kod NVARCHAR(50),
@Debug BIT = 0
AS
BEGIN
DECLARE @prikaz NVARCHAR(MAX)
SET @prikaz = N'
SELECT TOP (1) nazev
FROM ' + QUOTENAME(@Schema) + '.' + QUOTENAME(@Tabulka) + N'
WHERE kod = @Kod';
IF @Debug = 1
PRINT(@prikaz);
EXEC sys.sp_executesql
@stmt = @prikaz,
@param = N'@Kod NVARCHAR(50)',
@Kod = @Kod
END
GO
Na vstupu procedury mi chybělo schéma, které jsem tady doplnil s výchozí hodnotou dbo. Schéma a název tabulky musím do příkazu zřetězit. Obojí ošetřuji funkcí QUTENAME, která názvy obalí hranatými závorkami a zdvojí případný výskyt pravé hranaté závorky a tím zabrání SQL injekci. Funkce QUTENAME by se měla používat na všechny systémové názvy, to je databáze, tabulky, sloupce, atd.. Co kdyby nějaký filuta pojmenoval tabulku [;DROP TABLE Students--], nebo tak nějak.
Abych ošetřil i parametr @Kod, tak místo zřetězení jeho hodnoty ho zadávám jako parametr @Kod rovnou do řetězce příkazu. Volání EXEC(@prikaz) jsem nahradil voláním EXEC sys.sp_executesql. Tomu předávám samotný příkaz, pak seznam parametrů a jejich datových typů, které jsou v příkazu použity a nakonec parametrům předám hodnotu. Příkaz PRINT jsem podmínil parametrem @Debug, aby se neprintovalo vždy a zbytečně.
Ještě kouknem, jak to vypadá v plan cache.
DBCC FREEPROCCACHE
EXEC p_CiselnikDejNazevDleKodu @Tabulka = N'Obor', @Kod = N'P'
EXEC p_CiselnikDejNazevDleKodu @Tabulka = N'Obor', @Kod = N'E'
EXEC p_CiselnikDejNazevDleKodu @Tabulka = N'TypObjektu', @Kod = N'CH'
EXEC p_CiselnikDejNazevDleKodu @Tabulka = N'TypObjektu', @Kod = N'D'
SELECT
p.usecounts,
p.cacheobjtype,
p.objtype,
p.size_in_bytes,
t.text
FROM sys.dm_exec_cached_plans p
CROSS APPLY sys.dm_exec_sql_text(plan_handle) t
WHERE p.cacheobjtype = 'Compiled Plan'
AND t.text NOT LIKE '%dm_exec_cached_plans%';
GO
V plan cache mám tentokrát dva prepared plány a každý z toho dvakrát použitý. Plán se tedy pro každou tabulku sestaví jednou a pak se používá opakovaně i pro různé hodnoty parametru @Kod. Minule se mi pro každou hodnotu kódu sestavoval nový adhoc plan.

