Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

10 SP_EXECUTESQL Fallstricke, die Sie für besseres dynamisches SQL vermeiden sollten

Wissen Sie, wie leistungsfähig ein Tool wie dynamisches SQL sein kann? Wenn Sie es falsch verwenden, können Sie jemandem erlauben, Ihre Datenbank zu übernehmen. Außerdem könnte es zu viel Komplexität geben. Dieser Artikel zielt darauf ab, die Fallstricke bei der Verwendung von SP_EXECUTESQL vorzustellen und bietet die 10 häufigsten Fallstricke, die es zu vermeiden gilt.

SP_EXECUTESQL ist eine der Möglichkeiten, wie Sie in eine Zeichenfolge eingebettete SQL-Befehle ausführen können. Sie erstellen diese Zeichenfolge dynamisch durch den Code. Deshalb nennen wir das dynamisches SQL. Neben einer Reihe von Anweisungen können Sie ihm auch eine Liste von Parametern und Werten übergeben. Tatsächlich unterscheiden sich diese Parameter und Werte vom EXEC-Befehl. EXEC akzeptiert keine Parameter für dynamisches SQL. Dennoch führen Sie SP_EXECUTESQL mit EXEC!

aus

Für einen Neuling in Bezug auf dynamisches SQL sehen Sie hier, wie Sie dies aufrufen.

EXEC sp_executesql <command string>[, <input or output parameters list>, <parameter value1>, <parameter value n>]

Sie bilden die Befehlsfolge, die gültige SQL-Anweisungen enthält. Optional können Sie eine Liste von Eingabe- oder Ausgabeparametern und deren Datentypen übergeben. Und schließlich übergeben Sie eine durch Kommas getrennte Liste von Werten. Wenn Sie Parameter übergeben, müssen Sie Werte übergeben. Später werden Sie beim Weiterlesen sowohl richtige als auch falsche Beispiele dafür sehen.

SP_EXECUTESQL verwenden, wenn Sie es nicht brauchen

Stimmt. Wenn Sie es nicht brauchen, verwenden Sie es nicht. Wenn dies die 10 Gebote von SP_EXECUTESQL werden, ist dies das erste. Dies liegt daran, dass dieses Systemverfahren leicht missbraucht werden kann. Aber woher wissen Sie das?

Beantworten Sie dies:Gibt es ein Problem, wenn der Befehl in Ihrem dynamischen SQL statisch wird? Wenn Sie zu diesem Punkt nichts zu sagen haben, brauchen Sie es nicht. Siehe Beispiel.

DECLARE @sql NVARCHAR(100) = N'SELECT ProductID, Name FROM Production.Product ' +
			      'WHERE ProductID = @ProductID';
DECLARE @paramsList NVARCHAR(100) = N'@ProductID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramsList, @param1Value
GO

Wie Sie sehen können, ist die Verwendung von SP_EXECUTESQL mit einer Befehlszeichenfolge, einem Parameter und einem Wert abgeschlossen. Aber muss es so sein? Sicher nicht. Es ist vollkommen in Ordnung, Folgendes zu haben:

DECLARE @productID INT = 1;

SELECT ProductID, Name
FROM Production.Product
WHERE ProductID = @productID;

Aber es könnte mir peinlich sein, wenn Sie die Beispiele weiter unten im Artikel sehen. Denn ich werde meiner Behauptung in diesem ersten Punkt widersprechen. Sie werden kurze dynamische SQL-Anweisungen sehen, die besser sind als statische. Also haben Sie Geduld mit mir, denn die Beispiele werden nur die hier skizzierten Punkte beweisen. Tun Sie für die restlichen Beispiele eine Weile so, als würden Sie sich den Code ansehen, der für dynamisches SQL gedacht ist.

Objekte und Variablen außerhalb des Geltungsbereichs

Das Ausführen einer Reihe von SQL-Befehlen in einer Zeichenfolge mit SP_EXECUTESQL ist wie das Erstellen und Ausführen einer namenlosen gespeicherten Prozedur. Wenn Sie dies wissen, sind Objekte wie temporäre Tabellen und Variablen außerhalb der Befehlszeichenfolge außerhalb des Gültigkeitsbereichs. Aus diesem Grund treten Laufzeitfehler auf.

Bei Verwendung von SQL-Variablen

Sehen Sie sich das an.

DECLARE @extraText VARCHAR(10) = 'Name is '; -- note this variable
DECLARE @sql NVARCHAR(100) = N'SELECT @extraText + FirstName + SPACE(1) + LastName
                               FROM Person.Person WHERE BusinessEntityID = @BusinessEntityID';
DECLARE @paramList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramList, @BusinessEntityId = @param1Value;
GO

Die Variable @extraText ist für ausgeführte Befehle unsichtbar. Stattdessen ist ein Literal-String oder die innerhalb des dynamischen SQL-Strings deklarierte Variable viel besser. Wie auch immer, das Ergebnis ist:

Haben Sie diesen Fehler in Abbildung 1 gesehen? Wenn Sie einen Wert innerhalb der dynamischen SQL-Zeichenfolge übergeben müssen, fügen Sie einen weiteren Parameter hinzu.

Bei Verwendung temporärer Tabellen

Temporäre Tabellen in SQL Server existieren auch im Rahmen eines Moduls. Was halten Sie von diesem Code?

DECLARE @sql NVARCHAR(200) = N'SELECT BusinessEntityID, LastName, FirstName, MiddleName
                               INTO #TempNames
                               FROM Person.Person
                               WHERE BusinessEntityID BETWEEN 1 and 100';

EXEC sp_executesql @sql;
EXEC sp_executesql N'SELECT * FROM #TempNames'
GO

Der obige Code führt 2 gespeicherte Prozeduren nacheinander aus. Die aus dem ersten dynamischen SQL erstellte temporäre Tabelle ist für das zweite nicht zugänglich. Als Ergebnis erhalten Sie einen ungültigen Objektnamen #TempNames Fehler.

SQL Server QUOTENAME-Fehler

Hier ist ein weiteres Beispiel, das auf den ersten Blick gut aussieht, aber den Fehler „Ungültiger Objektname“ verursacht. Siehe Code unten.

DECLARE @sql NVARCHAR(100) = N'SELECT * FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(@tableName));

PRINT @sql;
EXEC sp_executesql @sql;
GO

Um für SELECT gültig zu sein, schließen Sie das Schema und die Tabelle wie folgt in eckige Klammern ein:[Schema].[Table] . Oder schließen Sie sie überhaupt nicht ein (es sei denn, der Tabellenname enthält ein oder mehrere Leerzeichen). Im obigen Beispiel wurden sowohl für die Tabelle als auch für das Schema keine eckigen Klammern verwendet. Anstatt @Table zu verwenden als Parameter wurde es zum Platzhalter für REPLACE. Der QUOTENAME wurde verwendet.

ZITATNAME umschließt eine Zeichenfolge mit einem Trennzeichen. Dies ist auch gut für den Umgang mit einfachen Anführungszeichen in der dynamischen SQL-Zeichenfolge. Die eckige Klammer ist das Standardtrennzeichen. Was glauben Sie, hat QUOTENAME im obigen Beispiel getan? Siehe Abbildung 2 unten.

Die SQL PRINT-Anweisung half uns beim Debuggen des Problems, indem sie die dynamische SQL-Zeichenfolge ausgab. Jetzt kennen wir das Problem. Was ist der beste Weg, dies zu beheben? Eine Lösung ist der folgende Code:

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(PARSENAME(@tableName,2)) + '.'
                               + QUOTENAME(PARSENAME(@tableName,1)));
PRINT @sql;
EXEC sp_executesql @sql;
GO

Lassen Sie uns das erklären.

Zuerst @Table wird als Platzhalter für REPLACE verwendet. Es wird nach dem Vorkommen von @Table gesucht und durch die richtigen Werte ersetzen. Wieso den? Wenn dies als Parameter verwendet wird, tritt ein Fehler auf. Das ist auch der Grund für die Verwendung von REPLACE.

Dann haben wir PARSENAME verwendet. Die Zeichenfolge, die wir an diese Funktion übergeben haben, trennt sie in Schema und Tabelle. PARSENAME(@tableName,2) erhält das Schema. Während PARSENAME(@tableName,1) bekommt die Tabelle.

Schließlich schließt QUOTENAME das Schema und die Tabellennamen separat ein, nachdem PARSENAME fertig ist. Das Ergebnis ist [Person].[Person] . Jetzt ist es gültig.

Eine bessere Möglichkeit, die dynamische SQL-Zeichenfolge mit Objektnamen zu bereinigen, wird jedoch später gezeigt.

Ausführen von SP_EXECUTESQL mit einer NULL-Anweisung

Es gibt Tage, an denen Sie verärgert oder niedergeschlagen sind. Fehler können auf dem Weg gemacht werden. Fügen Sie der Mischung und den NULL-Werten dann eine lange dynamische SQL-Zeichenfolge hinzu. Und das Ergebnis?

Nichts.

SP_EXECUTESQL gibt Ihnen ein leeres Ergebnis. Wie? Durch versehentliches Verketten einer NULL. Betrachten Sie das folgende Beispiel:

DECLARE @crlf NCHAR(2);
DECLARE @sql NVARCHAR(200) = N'SELECT' + @crlf +
	' p.Name AS Product' + @crlf +
	',v.Name AS Vendor' + @crlf +
	',v.AccountNumber' + @crlf +
	',p.ListPrice' + @crlf +
	'FROM Purchasing.ProductVendor pv' + @crlf +
	'INNER JOIN Production.Product p ON pv.ProductID = p.ProductID' + @crlf +
	'INNER JOIN Purchasing.Vendor v ON pv.BusinessEntityID = v.BusinessEntityID' + @crlf +
	'WHERE pv.BusinessEntityID = @BusinessEntityID';
DECLARE @paramsList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @BusinessEntityID INT = 1500;

PRINT @sql;
EXEC sp_executesql @sql, @paramsList, @BusinessEntityID
GO

Auf den ersten Blick sieht der Code gut aus. Doch die Adleraugen unter uns werden das @crlf bemerken Variable. Dessen Wert? Die Variable wurde nicht initialisiert. Es ist also NULL.

Aber was ist der Sinn dieser Variable? In einem späteren Abschnitt erfahren Sie, wie wichtig Formatierung und Debugging sind. Konzentrieren wir uns zunächst auf den eigentlichen Punkt.

Erstens führt die Verkettung einer NULL-Variablen mit der dynamischen SQL-Zeichenfolge zu NULL. Dann druckt PRINT leer. Schließlich wird SP_EXECUTESQL mit der dynamischen NULL-SQL-Zeichenfolge einwandfrei ausgeführt. Aber es gibt nichts zurück.

NULLen können uns an einem ohnehin schon schlechten Tag hypnotisieren. Machen Sie eine kurze Pause. Entspannen. Dann komm mit klarerem Verstand zurück.

Inline-Parameterwerte

Chaotisch.

So sieht das Inlining von Werten in dynamische SQL-Strings aus. Es gibt viele einfache Anführungszeichen für Zeichenfolgen und Datumsangaben. Wenn Sie nicht aufpassen, verursachen auch die O’Briens und O’Neils Fehler. Und da dynamisches SQL eine Zeichenfolge ist, müssen Sie die Werte in eine Zeichenfolge KONVERTIEREN oder umwandeln. Hier ist ein Beispiel.

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN ' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + '''' + ' AND DATEADD(MONTH,1,' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + ''') ' +
 'AND sod.ProductID = ' + CAST(@productID AS VARCHAR(8)) +
 ' GROUP BY soh.ShipDate, sod.ProductID' +
 ' ORDER BY sod.ProductID';
 
 PRINT @sql;
 EXEC sp_executesql @sql;

Ich habe chaotischere dynamische Saiten als diese gesehen. Beachten Sie die einfachen Anführungszeichen, CONVERT und CAST. Wenn Parameter verwendet würden, könnte dies besser aussehen. Sie können es unten sehen

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 DECLARE @paramList NVARCHAR(500) = N'@shipDate DATETIME, @productID INT';
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN @shipDate AND DATEADD(MONTH,1,@shipDate)
 AND sod.ProductID = @productID
  GROUP BY soh.ShipDate, sod.ProductID
  ORDER BY sod.ProductID';

PRINT @sql;
EXEC sp_executesql @sql, @paramList, @shipDate, @productID
GO

Sehen? Weniger einfache Anführungszeichen, kein CONVERT und CAST und auch sauberer.

Aber es gibt einen noch gefährlicheren Nebeneffekt von Inline-Werten.

SQL-Injection

Wenn wir in einer Welt leben würden, in der alle Menschen gut wären, würde man niemals an SQL-Injection denken. Aber das ist nicht der Fall. Jemand könnte bösartigen SQL-Code in Ihren einschleusen. Wie kann das passieren?

Hier ist das Szenario, das wir in unserem Beispiel verwenden werden:

  • Werte werden wie in unserem vorherigen Beispiel mit der dynamischen SQL-Zeichenfolge verschmolzen. Keine Parameter.
  • Die dynamische SQL-Zeichenfolge ist bis zu 2 GB groß, wenn NVARCHAR(MAX) verwendet wird. Viel Platz zum Einschleusen von bösartigem Code.
  • Das Schlimmste ist, dass die dynamische SQL-Zeichenfolge mit erhöhten Berechtigungen ausgeführt wird.
  • Die SQL Server-Instanz akzeptiert die SQL-Authentifizierung.

Ist das zu viel? Bei Systemen, die von einem Mann verwaltet werden, kann dies passieren. Ihn kontrolliert sowieso niemand. Bei größeren Unternehmen existiert manchmal eine laxe IT-Abteilung mit Sicherheit.

Ist das heute noch eine Bedrohung? Laut Cloud-Service-Anbieter Akamai in seinem State of the Internet/Security-Bericht. Zwischen November 2017 und März 2019 macht SQL-Injection fast zwei Drittel aller Angriffe auf Webanwendungen aus. Das ist die höchste aller untersuchten Bedrohungen. Sehr schlecht.

Möchten Sie es selbst sehen?

SQL-Injection-Praxis:Schlechtes Beispiel

Lassen Sie uns in diesem Beispiel eine SQL-Injection durchführen. Sie können dies in Ihrem eigenen AdventureWorks ausprobieren Datenbank. Stellen Sie jedoch sicher, dass die SQL-Authentifizierung zulässig ist, und führen Sie sie mit erhöhten Berechtigungen aus.

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = ' + NCHAR(39) + @lastName + NCHAR(39) + ' ' + @crlf +
'AND p.FirstName = '  + NCHAR(39) + @firstName + NCHAR(39);

-- SELECT @sql;	-- uncomment if you want to see what's in @sql					
EXEC sp_executesql @sql;
GO

Der obige Code stellt keinen tatsächlichen Code eines bestehenden Unternehmens dar. Es ist nicht einmal per App aufrufbar. Aber dies veranschaulicht die böse Tat. Also, was haben wir hier?

Zuerst erstellt der eingefügte Code ein SQL-Konto, das wie sa aussieht , aber es ist nicht. Und wie sa , das hat sysadmin Berechtigungen. Letztendlich wird dies verwendet, um jederzeit mit vollen Rechten auf die Datenbank zuzugreifen. Sobald dies erledigt ist, ist alles möglich:Stehlen, Löschen, Manipulieren von Unternehmensdaten, was auch immer.

Wird dieser Code ausgeführt? Definitiv! Und sobald dies der Fall ist, wird das Superkonto im Hintergrund erstellt. Und natürlich erscheint die Adresse von Zheng Mu im Ergebnissatz. Alles andere ist normal. Shady, meinst du nicht?

Nachdem Sie den obigen Code ausgeführt haben, versuchen Sie auch, diesen auszuführen:

SELECT IS_SRVROLEMEMBER('sysadmin','sà')

Wenn es 1 zurückgibt, ist er drin, wer auch immer dieses ist ist. Alternativ können Sie es in den Sicherheitsanmeldungen Ihres SQL Servers in SQL Server Management Studio überprüfen.

Also, was hast du bekommen?

Beängstigend, nicht wahr? (Wenn das echt ist, dann ist es das.)

SQL-Injection-Praxis:Gutes Beispiel

Lassen Sie uns nun den Code ein wenig ändern, indem wir Parameter verwenden. Die anderen Bedingungen sind immer noch die gleichen.

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = @lastName' + @crlf +
'AND p.FirstName = @firstName';

DECLARE @paramList NVARCHAR(300) = N'@lastName NVARCHAR(50), @firstName NVARCHAR(50)';

-- SELECT @sql;	-- uncomment if you want to see what's in @sql
EXEC sp_executesql @sql, @paramList, @lastName, @firstName;
GO

Es gibt 2 Unterschiede im Ergebnis im Vergleich zum ersten Beispiel.

  • Erstens wird die Adresse von Zheng Mu nicht angezeigt. Die Ergebnismenge ist leer.
  • Dann wird das abtrünnige Konto nicht erstellt. Die Verwendung von IS_SRVROLEMEMBER gibt NULL zurück.

Was ist passiert?

Da Parameter verwendet werden, ist der Wert von @firstName ist ‘Zheng”; LOGIN ERSTELLEN mit PASSWORT=”12345”; ALT’ . Dies wird als Literalwert genommen und auf nur 50 Zeichen gekürzt. Überprüfen Sie den Vornamenparameter im obigen Code. Es ist NVARCHAR(50). Deshalb ist die Ergebnismenge leer. Keine Person mit einem solchen Vornamen ist in der Datenbank.

Dies ist nur ein Beispiel für SQL-Injection und eine Möglichkeit, dies zu vermeiden. Es gehört mehr dazu, die eigentliche Sache zu tun. Aber ich hoffe, ich habe deutlich gemacht, warum Inline-Werte in dynamischem SQL schlecht sind.

Parameter-Sniffing

Haben Sie eine langsam laufende gespeicherte Prozedur von einer App erlebt, aber als Sie versuchten, sie in SSMS auszuführen, wurde sie schnell? Es ist verwirrend, weil Sie die exakten Parameterwerte verwendet haben, die in der App verwendet werden.

Das ist Parameter-Sniffing in Aktion. SQL Server erstellt einen Ausführungsplan, wenn die gespeicherte Prozedur zum ersten Mal ausgeführt oder neu kompiliert wird. Verwenden Sie den Plan dann für den nächsten Lauf wieder. Das klingt großartig, weil SQL Server den Plan nicht jedes Mal neu erstellen muss. Aber es gibt Zeiten, in denen ein anderer Parameterwert einen anderen Plan erfordert, um schnell ausgeführt zu werden.

Hier ist eine Demonstration mit SP_EXECUTESQL und einfachem statischem SQL.

DECLARE @sql NVARCHAR(150) = N'SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID';
DECLARE @paramList NVARCHAR(100) = N'@ProductSubcategoryID INT';
DECLARE @ProductSubcategoryID INT = 23;

EXEC sp_executesql @sql, @paramList, @ProductSubcategoryID

Dieser ist sehr einfach. Überprüfen Sie den Ausführungsplan in Abbildung 3.

Versuchen wir nun dieselbe Abfrage mit statischem SQL.

DECLARE @ProductSubcategoryID INT = 23;
SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID

Schauen Sie sich Abbildung 4 an und vergleichen Sie sie dann mit Abbildung 3.

In Abbildung 3, Indexsuche und Verschachtelte Schleife werden verwendet. Aber in Abbildung 4 ist es ein Clustered Index Scan . Obwohl es an dieser Stelle keine erkennbaren Leistungseinbußen gibt, zeigt dies, dass Parameter-Sniffing nicht nur eine Einbildung ist.

Dies kann sehr frustrierend sein, sobald die Abfrage langsam wird. Möglicherweise verwenden Sie Techniken wie das Neukompilieren oder die Verwendung von Abfragehinweisen, um dies zu vermeiden. Jede davon hat Nachteile.

Unformatierter dynamischer SQL-String in SP_EXECUTESQL

Was kann mit diesem Code schiefgehen?

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) AS ProductCount' +
                              'FROM Production.Product';
PRINT @sql;
EXEC sp_executesql @sql;

Es ist kurz und einfach. Aber siehe Abbildung 5 unten.

Fehler treten auf, wenn Sie beim Bilden des dynamischen SQL-Strings ein einzelnes Leerzeichen zwischen Schlüsselwörtern und Objekten nicht stören. Wie in Abbildung 5, wo der ProductCount Spaltenalias und das Schlüsselwort FROM haben kein Leerzeichen dazwischen. Es wird verwirrend, sobald ein Teil eines Strings in die nächste Codezeile fließt. Es lässt Sie glauben, dass die Syntax korrekt ist.

Beachten Sie auch, dass die Zeichenfolge 2 Zeilen im Codefenster verwendet, aber die Ausgabe von PRINT 1 Zeile zeigt. Stellen Sie sich vor, dies wäre eine sehr, sehr lange Befehlszeichenfolge. Es ist schwierig, das Problem zu finden, bis Sie die Zeichenfolge auf der Registerkarte „Nachrichten“ richtig formatiert haben.

Um dieses Problem zu lösen, fügen Sie einen Wagenrücklauf und einen Zeilenvorschub hinzu. Sie bemerken wahrscheinlich eine Variable @crlf aus den vorherigen Beispielen. Wenn Sie Ihre dynamische SQL-Zeichenfolge mit Leerzeichen und einer neuen Zeile formatieren, wird die dynamische SQL-Zeichenfolge besser lesbar. Dies eignet sich auch hervorragend zum Debuggen.

Betrachten Sie eine SELECT-Anweisung mit JOIN. Es benötigt mehrere Codezeilen wie im Beispiel unten.

DECLARE @sql NVARCHAR(400)
DECLARE @shipDate DATETIME = '06/11/2011';
DECLARE @paramList NVARCHAR(100) = N'@shipDate DATETIME';
DECLARE @crlf NCHAR(2) = NCHAR(13) + NCHAR(10);

set @sql = N'SELECT ' + @crlf +
 'soh.ShipDate ' + @crlf +
 ',sod.ProductID ' + @crlf +
 ',SUM(sod.OrderQty) AS TotalQty ' + @crlf +
 ',SUM(sod.LineTotal) AS LineTotal ' + @crlf +
 'FROM Sales.SalesOrderHeader soh ' + @crlf +
 'INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID ' + @crlf +
 'WHERE soh.ShipDate = @shipDate' + @crlf +
 'GROUP BY soh.ShipDate, sod.ProductID ' + @crlf +
 'ORDER BY sod.ProductID';

 PRINT @sql;
 EXEC sp_executesql @sql,@paramList,@shipDate
 GO

Um den String zu formatieren, muss @crlf Variable ist auf NCHAR(13), einen Wagenrücklauf, und NCHAR(10), einen Zeilenvorschub, gesetzt. Es wird mit jeder Zeile verkettet, um eine lange Reihe von SELECT-Anweisungen zu unterbrechen. Um das Ergebnis auf der Registerkarte Nachrichten anzuzeigen, verwenden wir PRINT. Überprüfen Sie die Ausgabe in Abbildung 6 unten.

Wie Sie den dynamischen SQL-String bilden, bleibt Ihnen überlassen. Was auch immer zu Ihnen passt, um es klar, lesbar und leicht zu debuggen, wenn die Zeit gekommen ist.

Nicht bereinigte Objektnamen

Müssen Sie Tabellen-, Ansichts- oder Datenbanknamen aus irgendeinem Grund dynamisch festlegen? Anschließend müssen Sie diese Objektnamen „bereinigen“ oder validieren, um Fehler zu vermeiden.

In unserem Beispiel verwenden wir einen Tabellennamen, obwohl das Validierungsprinzip auch für Ansichten gelten kann. Wie Sie als nächstes damit umgehen, wird anders sein.

Früher haben wir PARSENAME verwendet, um den Schemanamen vom Tabellennamen zu trennen. Es kann auch verwendet werden, wenn die Zeichenfolge einen Server- und Datenbanknamen enthält. Aber in diesem Beispiel verwenden wir nur Schema- und Tabellennamen. Den Rest überlasse ich Ihren brillanten Köpfen. Dies funktioniert unabhängig davon, ob Sie Ihre Tabellen mit oder ohne Leerzeichen benennen. Leerzeichen in Tabellen- oder Ansichtsnamen sind gültig. Es funktioniert also für dbo.MyFoodCravings oder [dbo].[Meine Essgelüste] .

BEISPIEL

Lassen Sie uns eine Tabelle erstellen.

CREATE TABLE [dbo].[My Favorite Bikes]
(
	id INT NOT NULL,
	BikeName VARCHAR(50)
)
GO

Dann erstellen wir ein Skript, das SP_EXECUTESQL verwendet. Dadurch wird eine generische DELETE-Anweisung für jede Tabelle mit einem 1-Spalten-Schlüssel ausgeführt. Als erstes müssen Sie den vollständigen Objektnamen parsen.

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);

Auf diese Weise trennen wir das Schema von der Tabelle. Zur weiteren Validierung verwenden wir OBJECT_ID. Wenn es nicht NULL ist, dann ist es gültig.

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
	PRINT @object + ' is valid!'
	-- do the rest of your stuff here
END
ELSE
BEGIN
        PRINT 'Invalid object name ' + @object
	-- if you need to do anything else, insert it here
END

Beachten Sie auch, dass wir QUOTENAME verwendet haben. Dadurch wird sichergestellt, dass Tabellennamen mit Leerzeichen keinen Fehler auslösen, indem sie in eckige Klammern eingeschlossen werden.

Aber wie wäre es mit der Validierung der Schlüsselspalte? Sie können die Existenz der Spalte der Zieltabelle in sys.columns prüfen .

IF (SELECT COUNT(*) FROM sys.columns
	    WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
		  AND [name] = @idKey) > 0
BEGIN
     -- add miscellaneous code here, if needed
     EXEC sp_executesql @sql, @paramsList, @id
END
ELSE
BEGIN
     PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
     -- if you need to do anything else, insert it here
END

Nun, hier ist das vollständige Skript zu dem, was wir erreichen wollen.

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);
DECLARE @isDebug BIT = 1;
DECLARE @idKey NVARCHAR(128) = N'id';

DECLARE @sql NVARCHAR(200) = N'DELETE FROM @object WHERE @idKey = @id';
DECLARE @id INT = 0;
DECLARE @paramList NVARCHAR(100) = N'@id INT';

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
   PRINT @object + ' is valid!'
   
   IF (SELECT COUNT(*) FROM sys.columns
       WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
         AND [name] = @idKey) > 0
   BEGIN
       SET @sql = REPLACE(@sql, '@object', QUOTENAME(@schemaName) + '.' +          
                  QUOTENAME(@tableName));
       SET @sql = REPLACE(@sql, '@idkey', QUOTENAME(@idKey));
       IF @isDebug = 1
	   PRINT @sql;
       EXEC sp_executesql @sql, @paramList, @id
   END
   ELSE
       PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
END
ELSE
BEGIN
   PRINT 'Invalid object name ' + @object
   -- if you need to do anything else, insert it here
END
GO

Das Ergebnis dieses Skripts ist in Abbildung 7 unten dargestellt.

Sie können dies mit anderen Tabellen versuchen. Ändern Sie einfach das @object , @idkey , und @id Variablenwerte.

Keine Bereitstellung für Debugging

Fehler können passieren. Sie müssen also die generierte dynamische SQL-Zeichenfolge kennen, um die Ursache zu finden. Wir sind keine Wahrsager oder Zauberer, um die Form des dynamischen SQL-Strings zu erraten. Sie brauchen also ein Debug-Flag.

Beachten Sie in Abbildung 7 zuvor, dass die dynamische SQL-Zeichenfolge auf der Registerkarte „Meldungen“ von SSMS gedruckt wird. Wir haben ein @isDebug hinzugefügt BIT-Variable und setzen Sie sie im Code auf 1. Wenn der Wert 1 ist, wird die dynamische SQL-Zeichenfolge gedruckt. Dies ist gut, wenn Sie ein Skript oder eine gespeicherte Prozedur wie diese debuggen müssen. Setzen Sie es einfach auf Null zurück, wenn Sie mit dem Debuggen fertig sind. Wenn es sich um eine gespeicherte Prozedur handelt, machen Sie dieses Flag zu einem optionalen Parameter mit einem Standardwert von Null.

Um den dynamischen SQL-String anzuzeigen, können Sie 2 mögliche Methoden verwenden.

  • Verwenden Sie PRINT, wenn die Zeichenfolge kleiner oder gleich 8000 Zeichen ist.
  • Oder verwenden Sie SELECT, wenn die Zeichenfolge mehr als 8000 Zeichen hat.

Dynamisches SQL mit schlechter Leistung in SP_EXECUTESQL verwendet

SP_EXECUTESQL kann langsam sein, wenn Sie ihm eine langsam laufende Abfrage zuweisen. Zeitraum. Probleme mit dem Parameter-Sniffing sind damit noch nicht verbunden.

Beginnen Sie also statisch mit dem Code, den Sie dynamisch ausführen möchten. Überprüfen Sie dann den Ausführungsplan und die STATISTIK IO. Prüfen Sie, ob Indizes fehlen, die Sie erstellen müssen. Stellen Sie es frühzeitig ein.

Das Endergebnis in SP_EXECUTESQL

Die Verwendung von SP_EXECUTESQL ist wie das Führen einer mächtigen Waffe. Aber derjenige, der es ausübt, muss darin geübt sein. Wobei das auch keine Raketenwissenschaft ist. Wenn Sie heute ein Neuling sind, kann es mit der Zeit zum gesunden Menschenverstand werden.

Diese Liste von Fallstricken ist nicht vollständig. Aber es deckt die üblichen ab. Wenn Sie weitere Informationen benötigen, sehen Sie sich diese Links an:

  • Fluch und Segen von Dynamic SQL, von Erland Sommarskog
  • SP_EXECUTESQL (Transact-SQL), von Microsoft

So was? Dann teilen Sie es bitte auf Ihren bevorzugten Social-Media-Plattformen. Sie können uns auch Ihre bewährten Tipps im Kommentarbereich mitteilen.