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

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:

  1. In Beispiel 1 wird die veraltete Syntax für einen inneren Join veranschaulicht.
  2. In Beispiel 2 wird veranschaulicht, wie Sie Beispiel 1 aktualisieren können, um die aktuelle Syntax zu verwenden.
  3. In Beispiel 3 wird die veraltete Syntax für eine linke äußere Verknüpfung veranschaulicht.
  4. In Beispiel 4 wird veranschaulicht, wie Sie Beispiel 2 aktualisieren können, um die aktuelle Syntax zu verwenden.
  5. In Beispiel 5 wird die veraltete Syntax für eine rechte äußere Verknüpfung veranschaulicht.
  6. 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:

SprachkonstruktWas ist aktiviert?Beispiel
Standardwert der ParameterParameterdatentyp
CREATE PROCEDURE p1(@p1 INT = 1)
AS
BEGIN
END
CREATE INDEX-PrädikatPrädikat ist ein boolescher Wert
CREATE INDEX index1 ON table1 (column1)
WHERE column1 > 10
Argumente von LINKS- oder RECHTS-FunktionenZeichenkettenargumenttyp und -länge
SET @v = LEFT('abc', 2)
Argumente von CAST- und CONVERT-FunktionenAusdruck und Typen sind gültig
SET @v = CAST('abc' AS CHAR(10))
SET-AnweisungLinke und rechte Seite verfügen über kompatible Typen
SET @v1 = 'xyz'
SELECT @v1 = c1 FROM t1
IF-AnweisungsprädikatPrädikat ist ein boolescher Wert
IF (@v > 10)
WHILE-AnweisungsprädikatPrädikat ist ein boolescher Wert
WHILE (@v > 10)
INSERT-AnweisungWerte und Spalten sind korrekt
INSERT INTO t1(c1, c2) VALUES (99, 'xyz')
INSERT INTO t1 SELECT c1 FROM t2.
SELECT WHERE-PrädikatPrädikat ist ein boolescher Wert
SELECT * FROM t1 WHERE c1 > 10
SELECT TOP AusdruckAusdruck ist ein Ganzzahl- oder Float-Typ
SELECT TOP 4 * FROM t1
SELECT TOP 1.5 PERCENT * FROM t1
UPDATE-AnweisungAusdruck und Spalte verfügen über kompatible Typen
UPDATE t1 SET c1 = 100
UPDATE-PrädikatPrädikat ist ein boolescher Wert
UPDATE t1 SET c1 = 100
WHERE c1 > 100
UPDATE TOP-AusdruckAusdruck ist ein Ganzzahl- oder Float-Typ
UPDATE TOP 4 table1
PRÄDIKAT LÖSCHENPrädikat ist ein boolescher Wert
DELETE t1 WHERE c1 > 10
DELETE TOP-AusdruckAusdruck ist ein Ganzzahl- oder Float-Typ
DELETE TOP 2 FROM t1
DECLARE-VariablendeklarationAnfangswert und Datentyp sind kompatibel
DECLARE @v INT = 10
EXECUTE-Anweisungsargumente und RückgabetypParameter und Argumente
CREATE PROCEDURE p1 (@p1 INT) AS
GO
EXECUTE p1 100
EXECUTE @v1 = p1 100
RETURN–AnweisungDer RETURN-Ausdruck verfügt über einen kompatiblen Datentyp
CREATE FUNCTION f1() RETURNS INT
AS
BEGIN
  RETURN 100
END
MERGE-AnweisungsbedingungenDie Bedingung ist ein boolescher Wert
MERGE t1 USING t2
ON t1.c1 = t2.c2
WHEN t1.c1 > 10 THEN DELETE

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