T-SQL-Entwurfsprobleme
Gilt für: SQL Server Azure SQL-Datenbank Azure SQL Managed Instance
Wenn Sie den T-SQL-Code in Ihrem Datenbankprojekt analysieren, wird möglicherweise mindestens eine Warnung als Entwurfsprobleme kategorisiert. Sie sollten Designprobleme beheben, um die folgenden Situationen zu vermeiden:
- Nachfolgende Änderungen an Ihrer Datenbank können Anwendungen unterbrechen, die davon abhängig sind.
- Der Code erzeugt möglicherweise nicht das erwartete Ergebnis.
- Der Code kann nicht ausgeführt werden, wenn Sie ihn mit zukünftigen Versionen von SQL Server ausführen.
Im Allgemeinen sollten Sie ein Entwurfsproblem nicht unterdrücken, da es Ihre Anwendung entweder jetzt oder in Zukunft unterbrechen könnte.
Die bereitgestellten Regeln identifizieren die folgenden Entwurfsprobleme:
- SR0001: Vermeiden Sie AUSWÄHLEN * in gespeicherten Prozeduren, Ansichten und Tabellenwertfunktionen
- SR0008: Erwägen Sie die Verwendung von SCOPE_IDENTITY anstelle von @@IDENTITY
- SR0009: Vermeiden Sie die Verwendung von Typen variabler Länge mit der Größe 1 oder 2
- SR0010: Vermeiden Sie die Verwendung veralteter Syntax, wenn Sie Tabellen oder Ansichten verknüpfen
- SR0013: Der Ausgabeparameter (Parameter) wird nicht in allen Codepfaden aufgefüllt
- SR0014: Datenverlust kann beim Übertragen von {Type1} in {Type2} auftreten
SR0001: Vermeiden Sie AUSWÄHLEN * in gespeicherten Prozeduren, Ansichten und Tabellenwertfunktionen
Wenn Sie ein Platzhalterzeichen in einer gespeicherten Prozedur, Ansicht oder Tabellenwertfunktion verwenden, um alle Spalten in einer Tabelle oder Ansicht auszuwählen, kann sich die Anzahl oder Form der zurückgegebenen Spalten ändern, wenn sich die zugrunde liegende Tabelle oder Ansicht ändert. Die Form einer Spalte ist eine Kombination aus Typ und Größe. Diese Abweichung kann in Anwendungen, die die gespeicherte Prozedur, Ansicht oder Tabellenwertfunktion verwenden, Probleme verursachen, da diese Verbraucher eine andere Anzahl von Spalten erwarten.
Behandeln von Verstößen
Sie können Verbraucher der gespeicherten Prozedur, Ansicht oder Tabellenwertfunktion vor Schemaänderungen schützen, indem Sie das Wildcard-Zeichen durch eine vollqualifizierte Liste von Spaltennamen ersetzen.
Beispiel
Im folgenden Beispiel wird zuerst eine Tabelle mit dem Namen [Table2] definiert und dann werden zwei gespeicherte Prozeduren definiert. Das erste Verfahren enthält ein SELECT *
, das gegen die Regel SR0001 verstößt. Die zweite Prozedur vermeidet SELECT *
und listet die Spalten in der SELECT-Anweisung explizit auf.
CREATE TABLE [dbo].[Table2]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL,
[Comment] NVARCHAR (50)
)
ON [PRIMARY]
CREATE PROCEDURE [dbo].[procWithWarning]
AS
BEGIN
-- Contains code that breaks rule SR0001
SELECT *
FROM [dbo].[Table2]
END
CREATE PROCEDURE [dbo].[procFixed]
AS
BEGIN
-- Explicitly lists the column names in a SELECT statement
SELECT [dbo].[Table2].[ID], [dbo].[Table2].[c1], [dbo].[Table2].[Comment]
FROM [dbo].[Table2]
END
SR0008: Erwägen Sie die Verwendung von SCOPE_IDENTITY anstelle von @@IDENTITY
Da @@IDENTITY ein globaler Identitätswert ist, wurde er möglicherweise außerhalb des aktuellen Bereichs aktualisiert und hat einen unerwarteten Wert erhalten. Trigger, einschließlich geschachtelter Trigger, die von der Replikation verwendet werden, können @@IDENTITY außerhalb Ihres aktuellen Bereichs aktualisieren.
Behandeln von Verstößen
Um dieses Problem zu beheben, müssen Sie Verweise auf @@IDENTITY durch SCOPE_IDENTITY ersetzen, wodurch der neueste Identitätswert im Bereich der Benutzerausweisung zurückgegeben wird.
Beispiel
Im ersten Beispiel wird @@IDENTITY in einer gespeicherten Prozedur verwendet, die Daten in eine Tabelle einfügt. Die Tabelle wird dann für die Seriendruckreplikation veröffentlicht, wodurch Trigger zu Tabellen hinzugefügt werden, die veröffentlicht werden. Aus diesem Grund kann @@IDENTITY den Wert aus der Einfügeoperation in eine Replikationssystemtabelle zurückgeben statt aus der Einfügeoperation in eine Benutzertabelle.
Die Sales.Customer
-Tabelle besitzt den maximalen Identitätswert 29483. Wenn Sie eine Zeile in die Tabelle einfügen, werden von @@IDENTITY und SCOPE_IDENTITY() jeweils unterschiedliche Werte zurückgegeben. SCOPE_IDENTITY() gibt den Wert aus der Einfügeoperation in die Benutzertabelle zurück, @@IDENTITY hingegen gibt den Wert aus der Einfügeoperation in die Replikationssystemtabelle zurück.
Das zweite Beispiel zeigt, wie Sie SCOPE_IDENTITY() verwenden können, um auf den eingefügten Identitätswert zuzugreifen und die Warnung zu beheben.
CREATE PROCEDURE [dbo].[ProcedureWithWarning]
@param1 INT,
@param2 NCHAR(1),
@Param3 INT OUTPUT
AS
BEGIN
INSERT INTO Sales.Customer ([TerritoryID],[CustomerType]) VALUES (@param1,@param2);
SELECT @Param3 = @@IDENTITY
END
CREATE PROCEDURE [dbo].[ProcedureFixed]
@param1 INT,
@param2 NCHAR(1),
@param3 INT OUTPUT
AS
BEGIN
INSERT INTO Sales.Customer ([TerritoryID],[CustomerType]) VALUES (@param1,@param2);
SELECT @Param3 = SCOPE_IDENTITY()
END
SR0009: Vermeiden Sie die Verwendung von Typen variabler Länge mit der Größe 1 oder 2
Wenn Sie Datentypen variabler Länge wie VARCHAR, NVARCHAR und VARBINARY verwenden, entstehen zusätzliche Speicherkosten, um die Länge des im Datentyp gespeicherten Werts nachzuverfolgen. Darüber hinaus werden Spalten mit variabler Länge nach allen Spalten mit fester Länge gespeichert, die Auswirkungen auf die Leistung haben können. Sie erhalten auch eine Warnung, wenn Sie einen Variablenlängentyp deklarieren, z. B. VARCHAR, aber keine Länge angeben. Diese Warnung tritt auf, da, falls nicht angegeben, die Standardlänge 1 ist.
Behandeln von Verstößen
Wenn die Länge des Typs sehr klein (Größe 1 oder 2) und konsistent ist, deklarieren Sie sie als Typ fester Länge, z. B. CHAR, NCHAR und BINARY.
Beispiel
Dieses Beispiel zeigt Definitionen für zwei Tabellen. In der ersten Tabelle wird eine Zeichenfolge mit variabler Länge mit Länge 2 deklariert. Die zweite Tabelle deklariert stattdessen eine Zeichenfolge mit fester Länge, wodurch die Warnung vermieden wird.
CREATE TABLE [dbo].[TableWithWarning]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL PRIMARY KEY,
[c2] INT,
[c3] INT,
[SmallString] VARCHAR(2)
)
ON [PRIMARY]
CREATE TABLE [dbo].[FixedTable]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL PRIMARY KEY,
[c2] INT,
[c3] INT,
[SmallString] CHAR(2)
)
ON [PRIMARY]
Daten für Typen variabler Länge werden physisch nach Daten für Typen fester Länge gespeichert. Daher verursachen Sie eine Datenverschiebung, wenn Sie eine Spalte von variabler in feste Länge in einer Tabelle ändern, die nicht leer ist.
SR0010: Vermeiden Sie die Verwendung veralteter Syntax, wenn Sie Tabellen oder Ansichten verknüpfen
Verknüpfungen, die die veraltete Syntax verwenden, werden in zwei Kategorien unterteilt:
- Innerer Join: Bei einem inneren Join werden die Werte in den Spalten, die verknüpft werden, mithilfe eines Vergleichsoperators wie =, <, >= usw. verglichen. Innere Joins geben nur Zeilen zurück, wenn mindestens eine Zeile aus jeder Tabelle mit der Verknüpfungsbedingung übereinstimmt.
- Äußerer Join: Äußere Joins geben alle Zeilen aus mindestens einer der in der FROM-Klausel spezifizierten Tabellen oder Sichten zurück, sofern diese Zeilen ggf. die WHERE- oder HAVING-Suchbedingung erfüllen. Wenn Sie = oder = verwenden, um eine äußere Verknüpfung anzugeben, verwenden Sie veraltete Syntax.
Behandeln von Verstößen
Verwenden Sie die INNER JOIN
-Syntax, um einen Verstoß in einem inneren Join zu beheben.
Verwenden Sie die entsprechende OUTER JOIN
-Syntax, um einen Verstoß in einer äußeren Verknüpfung zu beheben. Sie haben die folgenden Optionen:
- LINKE ÄUSSERE VERKNÜPFUNG oder LINKE VERKNÜPFUNG
- RECHTE ÄUSSERE VERKNÜPFUNG oder RECHTE VERKNÜPFUNG
Beispiele für die veraltete Syntax und die aktualisierte Syntax finden Sie in den folgenden Beispielen. Weitere Informationen zu diesen Joins finden Sie unter Joins.
Beispiele
Die sechs Beispiele zeigen die folgenden Optionen:
- In Beispiel 1 wird die veraltete Syntax für einen inneren Join veranschaulicht.
- In Beispiel 2 wird veranschaulicht, wie Sie Beispiel 1 aktualisieren können, um die aktuelle Syntax zu verwenden.
- In Beispiel 3 wird die veraltete Syntax für eine linke äußere Verknüpfung veranschaulicht.
- In Beispiel 4 wird veranschaulicht, wie Sie Beispiel 2 aktualisieren können, um die aktuelle Syntax zu verwenden.
- In Beispiel 5 wird die veraltete Syntax für eine rechte äußere Verknüpfung veranschaulicht.
- In Beispiel 6 wird veranschaulicht, wie Sie Beispiel 5 aktualisieren können, um die aktuelle Syntax zu verwenden.
-- Example 1: Deprecated syntax for an inner join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] = [T2].[ID]
-- Example 2: Current syntax for an inner join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
INNER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
-- Example 3: Deprecated syntax for a left outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] *= [T2].[ID]
-- Example 4: Fixed syntax for a left outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
LEFT OUTER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
-- Example 5: Deprecated syntax for a right outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] T2, [dbo].[Table1] T1
WHERE [T1].[ID] =* [T2].[ID]
-- Example 6: Fixed syntax for a right outer join
SELECT [T2].[c3], [T1].[c3]
FROM [dbo].[Table2] AS T2
RIGHT OUTER JOIN [dbo].[Table1] as T1
ON [T2].[ID] = [T2].[ID]
SR0013: Der Ausgabeparameter (Parameter) wird nicht in allen Codepfaden aufgefüllt
Diese Regel identifiziert Code, in dem der Ausgabeparameter nicht auf einen Wert in einem oder mehreren Codepfaden über die gespeicherte Prozedur oder Funktion festgelegt ist. Diese Regel gibt nicht an, in welchen Pfaden der Ausgabeparameter festgelegt werden soll. Wenn mehrere Ausgabeparameter dieses Problem aufweisen, wird für jeden Parameter eine Warnung angezeigt.
Behandeln von Verstößen
Sie können dieses Problem auf zwei Arten beheben. Sie können dieses Problem am einfachsten beheben, wenn Sie die Ausgabeparameter am Anfang des Prozedurtexts in einen Standardwert initialisieren. Alternativ können Sie den Ausgabeparameter auch auf einen Wert in den spezifischen Codepfaden festlegen, in denen der Parameter nicht festgelegt ist. Sie könnten jedoch einen ungewöhnlichen Codepfad in einer komplexen Vorgehensweise übersehen.
Wichtig
Die Angabe eines Wertes in der Prozedurendeklaration, wie z.B. CREATE PROC MyProcedure (@param1 INT = 10 OUTPUT)
, wird das Problem nicht beheben. Sie müssen dem Ausgabeparameter innerhalb des Prozedurtexts einen Wert zuweisen.
Beispiel
Das folgende Beispiel zeigt zwei einfache Prozeduren. Die erste Prozedur legt den Wert des Ausgabeparameters @Sum
nicht fest. Die zweite Prozedur initialisiert den @Sum
-Parameter am Anfang der Prozedur, wodurch sichergestellt wird, dass der Wert in allen Codepfaden festgelegt wird.
CREATE PROCEDURE [dbo].[procedureHasWarning]
(
@Value1 BIGINT,
@Value2 INT,
@Value3 INT,
@Sum INT OUTPUT
)
AS
BEGIN
-- No initialization of the output parameter
--
-- Additional statements here.
--
RETURN 0;
END
--
CREATE PROCEDURE [dbo].[procedureFixed]
(
@Value1 BIGINT,
@Value2 INT,
@Value3 INT,
@Sum INT OUTPUT
)
AS
BEGIN
-- Initialize the out parameter
SET @Sum = 0;
--
-- Additional statements here
--
RETURN 0;
END
SR0014: Datenverlust kann beim Übertragen von {Type1} in {Type2} auftreten
Wenn Datentypen Spalten, Variablen oder Parametern inkonsistent zugewiesen werden, werden sie implizit konvertiert, wenn der Transact-SQL-Code, der diese Objekte enthält, ausgeführt wird. Diese Art von Konvertierung reduziert nicht nur die Leistung, sondern verursacht in einigen Fällen auch subtilen Datenverlust. Beispielsweise kann ein Tabellenscan ausgeführt werden, wenn jede Spalte in einer WHERE-Klausel konvertiert werden muss. Schlimmer noch: Daten gehen möglicherweise verloren, wenn eine Unicode-Zeichenkette in eine ASCII-Zeichenkette konvertiert wird, die eine andere Codepage verwendet.
Diese Regel macht Folgendes NICHT:
- Überprüfen des Typs einer berechneten Spalte, da der Typ erst zur Laufzeit bekannt ist.
- Analysieren von allem innerhalb einer CASE-Anweisung. Außerdem wird der Rückgabewert einer CASE-Anweisung nicht analysiert.
- Analysieren der Eingabeparameter oder des Rückgabewerts eines Aufrufs von ISNULL
In dieser Tabelle sind die von der Regel SR0014 abgedeckten Prüfungen zusammengefasst:
Sprachkonstrukt | Was ist aktiviert? | Beispiel |
---|---|---|
Standardwert der Parameter | Parameterdatentyp |
|
CREATE INDEX-Prädikat | Prädikat ist ein boolescher Wert |
|
Argumente von LINKS- oder RECHTS-Funktionen | Zeichenkettenargumenttyp und -länge |
|
Argumente von CAST- und CONVERT-Funktionen | Ausdruck und Typen sind gültig |
|
SET-Anweisung | Linke und rechte Seite verfügen über kompatible Typen |
|
IF-Anweisungsprädikat | Prädikat ist ein boolescher Wert |
|
WHILE-Anweisungsprädikat | Prädikat ist ein boolescher Wert |
|
INSERT-Anweisung | Werte und Spalten sind korrekt |
|
SELECT WHERE-Prädikat | Prädikat ist ein boolescher Wert |
|
SELECT TOP Ausdruck | Ausdruck ist ein Ganzzahl- oder Float-Typ |
|
UPDATE-Anweisung | Ausdruck und Spalte verfügen über kompatible Typen |
|
UPDATE-Prädikat | Prädikat ist ein boolescher Wert |
|
UPDATE TOP-Ausdruck | Ausdruck ist ein Ganzzahl- oder Float-Typ |
|
PRÄDIKAT LÖSCHEN | Prädikat ist ein boolescher Wert |
|
DELETE TOP-Ausdruck | Ausdruck ist ein Ganzzahl- oder Float-Typ |
|
DECLARE-Variablendeklaration | Anfangswert und Datentyp sind kompatibel |
|
EXECUTE-Anweisungsargumente und Rückgabetyp | Parameter und Argumente |
|
RETURN–Anweisung | Der RETURN-Ausdruck verfügt über einen kompatiblen Datentyp |
|
MERGE-Anweisungsbedingungen | Die Bedingung ist ein boolescher Wert |
|
Behandeln von Verstößen
Sie können diese Probleme vermeiden und beheben, indem Sie Datentypen konsistent und explizit konvertieren, wo sie benötigt werden. Weitere Informationen zum expliziten Konvertieren von Datentypen finden Sie auf dieser Seite auf der Microsoft-Website: CAST und CONVERT (Transact-SQL).
Beispiel
Dieses Beispiel zeigt zwei gespeicherte Prozeduren, die Daten in eine Tabelle einfügen. Die erste Prozedur procWithWarning führt zu einer impliziten Konvertierung eines Datentyps. Die zweite Prozedur procFixed zeigt, wie Sie eine explizite Konvertierung hinzufügen können, um die Leistung zu maximieren und alle Daten aufzubewahren.
CREATE TABLE [dbo].[Table2]
(
[ID] INT NOT NULL IDENTITY(0, 1),
[c1] INT NOT NULL,
[c2] INT NOT NULL,
[c3] BIGINT NOT NULL,
[Comment] VARCHAR (25)
)
ON [PRIMARY]
CREATE PROCEDURE [dbo].[procWithWarning]
(
@Value1 INT,
@Value2 INT,
@Value3 BIGINT,
@Comment CHAR(30)
)
AS
BEGIN
INSERT INTO [Table2] ([c1], [c2], [c3], Comment)
VALUES (@Value1, @Value2, @Value3, @Comment)
END
CREATE PROCEDURE [dbo].[procFixed]
(
@Value1 INT,
@Value2 INT,
@Value3 BIGINT,
@Comment CHAR(10)
)
AS
BEGIN
INSERT INTO [Table2] ([c1], [c2], [c3], Comment)
VALUES (@Value1, @Value2, @Value3, CAST(@Comment AS VARCHAR(25)))
END