když spustím následující skript, bude fungovat dobře:
declare @temp table ( name varchar(255), field varchar(255), filename varchar(255), filegroup varchar(255), size varchar(255), maxsize varchar(255), growth varchar(255), usage varchar(255) ); INSERT @temp exec sp_msforeachdb @command1="use ?; Exec sp_helpfile;"
ale když Používám svůj vlastní postup sp_foreachdb, zdrojový kód je na tomto odkazu níže:
Spolehlivější a flexibilnější sp_MSforeachdb
declare @temp table ( name varchar(255), field varchar(255), filename varchar(255), filegroup varchar(255), size varchar(255), maxsize varchar(255), growth varchar(255), usage varchar(255) ); INSERT @temp exec sp_foreachdb @command="use ?; Exec sp_helpfile;"
Zobrazuje se mi výjimka (upozorňujeme, že jsem při tomto postupu přidal zpracování výjimek)
--EXCEPTION WAS CAUGHT-- THE ERROR NUMBER:8164 SEVERITY: 16 STATE: 1 PROCEDURE: sp_foreachdb LINE NUMBER: 165 ERROR MESSAGE: An INSERT EXEC statement cannot be nested. ------------------------------------ the sql ------------------------------------ SELECT name FROM sys.databases WHERE 1=1 AND state_desc = N"ONLINE" AND is_read_only = 0 Msg 16916, Level 16, State 1, Procedure sp_foreachdb, Line 239 A cursor with the name "c" does not exist. Msg 16916, Level 16, State 1, Procedure sp_foreachdb, Line 240 A cursor with the name "c" does not exist. (0 row(s) affected)
Komentáře
- I ‚ si nejsem jistý, proč ‚ používáte toto spletité metoda. Proč se neptat pouze na sys.master_files?
- @Nic dobře poukázal, ale právě jsem použil sp_helpfile jako příklad. v reálném životě je to jedna z našich vlastních uložených procedur, které bych volal
odpověď
zdrojový kód pro Aarona „s sp_foreachdb
obsahuje následující řádek:
INSERT #x EXEC sp_executesql @sql;
Podle vašeho chybová zpráva:
Příkaz INSERT EXEC nelze vnořit.
Takže níže uvedený kód nebude platný, protože vnořuje INSERT xxx EXEC xxx
kód.
INSERT @temp exec sp_msforeachdb @command1="use ?; Exec sp_helpfile;"
Komentáře
- @marcellomiorelli Myslím, že k odstranění dynamického SQL by bylo zapotřebí velké změny v Aaron ‚ s SP. Pravděpodobně by to vyžadovalo použití další funkce.
Odpověď
Připojení k vlastnímu serveru pomocí metod distribuovaného dotazu by stačilo, kdyby sp_foreachdb vrátí jednu sadu výsledků.
create table #temp ( name varchar(255), field varchar(255), filename varchar(255), filegroup varchar(255), size varchar(255), maxsize varchar(255), growth varchar(255), usage varchar(255) ); insert into #temp select * FROM OPENROWSET("SQLNCLI", "SERVER=****;UID=****;PWD=****", " exec sp_foreachdb @command="" Exec ?..sp_helpfile;"" WITH RESULT SETS ((name varchar(255), field varchar(255), filename varchar(255), filegroup varchar(255), size varchar(255), maxsize varchar(255), growth varchar(255), usage varchar(255))); ") select * from #temp
nad dotazem w vrátím pouze první sadu výsledků sp_foreachdb provedení. ale níže uvedený dotaz vrátí výsledek pro všechny databáze v jedné sadě výsledků.
create table #temp ( name varchar(255), field varchar(255), filename varchar(255), filegroup varchar(255), size varchar(255), maxsize varchar(255), growth varchar(255), usage varchar(255) ); exec sp_foreachdb @command="INSERT INTO #temp Exec ?..sp_helpfile;" select * from #temp
Odpověď
Můžete zkusit přizpůsobit Aaronovu SP odstraněním jeho dynamické části. Dynamická část má na základě zadaných argumentů vytvořit dotaz, který přečte pouze názvy databází z sys.databases
. Aby byl dotaz co nejefektivnější, je vybrán dynamický SQL – a také udržovatelné. S přihlédnutím k vašim specifickým potřebám mohou být některé oběti v pořádku.
Tvrdím však, že výkon nemusí příliš trpět přepsáním, které níže nabízím, protože sys.databases
zobrazení systému obvykle nemá příliš mnoho řádků, ale v každém případě byste mohli na konec přidat OPTION (RECOMPILE)
. Může to však být pomalé, i když je pravděpodobné, že to skončí docela ošklivé, to mohu slíbit.
Metoda přepisování je následující. Aaronův postup vytváří dotaz pomocí opakujícího se vzoru, kde je kontrolována hodnota parametru a , na základě výsledku další dotaz se přidá k dynamickému dotazu, tj. takto:
SET @sql = N"SELECT name FROM sys.databases WHERE 1=1" + CASE WHEN some_condition1 THEN "AND some_filter1" ELSE "" END + CASE WHEN some_condition2 THEN "AND some_filter2" ELSE "" END + ...
Takto byste jej přepsali:
SELECT name FROM sys.databases WHERE 1=1 AND (some_filter1 OR opposite_of_some_condition1) AND (some_filter2 OR opposite_of_some_condition2) AND ... OPTION (RECOMPILE);
Například @system_only
řídí, zda má být zahrnut filtr database_id IN (1,2,3,4)
, například:
SET @sql = N"SELECT name FROM sys.databases WHERE 1=1" + CASE WHEN @system_only = 1 THEN " AND database_id IN (1,2,3,4)" ELSE "" END
Přepsaný dotaz by byl parametrizován takto:
SELECT name FROM sys.databases WHERE 1=1 AND (database_id IN (1,2,3,4) OR @system_only <> 1) -- or: @system_only = 0
Výsledný dotaz použijte v přímo kurzor, místo
SELECT CASE WHEN @suppress_quotename = 1 THEN db ELSE QUOTENAME(db) END FROM #x ORDER BY db
Jak je vidět, poslední dotek bude muset přejít do části SELECT, kde jednoduchý SELECT name
nového dotazu bude nahrazen
SELECT CASE WHEN @suppress_quotename = 1 THEN name ELSE QUOTENAME(name) END
Je zřejmé, že byste se nyní mohli také zbavit #x
tabulka již není v SP potřeba.
Jedna poslední poznámka se týká konkrétního skriptu, který plánujete spustit proti každé databázi. Místo
@command1="use ?; Exec sp_helpfile;"
můžete mít pouze
@command1="Exec ?..sp_helpfile;"
odpověď
Napsal jsem náhradu, která má několik vylepšení oproti původní sp_foreachdb
, včetně možnosti vnoření. Novější upřednostňovaný postup se nazývá sp_ineachdb
( část 1 / part 2 ), a je také k dispozici jako součást First Responder Kit Brenta Ozara .
Některé pozadí zde :