SQL Server Stored Procedures – Das Kompendium für Entwickler

📦 SQL SERVER · ENTWICKLUNG

Stored Procedures – Das Kompendium

Von der ersten einfachen Prozedur bis zur professionellen Entwicklung mit Parametern, Fehlerbehandlung und sauberer Code-Struktur.

🎯 Zielgruppe: SQL-Entwickler, Datenbankentwickler 📅 Gültig für SQL Server 2012 – 2025

Creating Stored Procedures – Grundlagen

Syntax und erste Schritte

Eine Stored Procedure ist eine gespeicherte Sammlung von T-SQL-Anweisungen, die auf dem Server ausgeführt wird. Sie wird mit CREATE PROCEDURE (oder kurz CREATE PROC) erstellt und kann Parameter, Variablen, Kontrollfluss und Fehlerbehandlung enthalten.

-- Grundgerüst einer Stored Procedure 
CREATE PROCEDURE schema_name.procedure_name 
@parameter1 datatype = default_value, 
@parameter2 datatype OUTPUT 
AS 
BEGIN 
SET NOCOUNT ON; 
-- SQL-Anweisungen 
END;
💡 Best Practice: Verwenden Sie immer das Schema (z. B. dbo) und einen aussagekräftigen Namen. Nutzen Sie BEGIN...END für den Prozedurkörper.

Simple Stored Procedure

Das minimalistische Beispiel

Eine einfache Prozedur ohne Parameter, die Daten selektiert oder ändert.

-- Einfache Prozedur: Alle Kunden abrufen 
CREATE PROCEDURE dbo.GetAllCustomers 
AS 
BEGIN 
SET NOCOUNT ON; 
SELECT CustomerID, Name, City FROM dbo.Customers ORDER BY Name; 
END; 
GO 
 
-- Ausführen 
EXEC dbo.GetAllCustomers;
Hinweis: Auch bei "einfachen" Prozeduren sollten Sie SET NOCOUNT ON verwenden, um die Anzahl der betroffenen Zeilen zu unterdrücken – das spart Netzwerkverkehr.

Input Parameters

Dynamische Prozeduren mit Werten von außen

Parameter werden nach dem Prozedurnamen in Klammern definiert. Sie können Standardwerte haben.

-- Prozedur mit Input-Parametern 
CREATE PROCEDURE dbo.GetCustomersByCity 
@City NVARCHAR(50), 
@MinOrders INT = 0 -- Standardwert 
AS 
BEGIN 
SET NOCOUNT ON; 
SELECT CustomerID, Name, City, OrderCount 
FROM dbo.Customers 
WHERE City = @City AND OrderCount >= @MinOrders 
ORDER BY OrderCount DESC; 
END; 
GO 
 
-- Ausführung mit Parameter 
EXEC dbo.GetCustomersByCity @City = 'Berlin', @MinOrders = 5; 
-- Oder mit Standardwert 
EXEC dbo.GetCustomersByCity @City = 'Hamburg';
📌 Tipp: Verwenden Sie immer benannte Parameter beim Aufruf – das macht den Code lesbarer und weniger fehleranfällig.

Output Parameters

Werte aus der Prozedur zurückgeben

Mit dem Schlüsselwort OUTPUT können Sie Werte aus der Prozedur an den Aufrufer zurückgeben – ideal für Statusmeldungen, berechnete Werte oder Primärschlüssel.

-- Prozedur mit Output-Parameter 
CREATE PROCEDURE dbo.InsertOrder 
@CustomerID INT, 
@OrderDate DATE, 
@NewOrderID INT OUTPUT 
AS 
BEGIN 
SET NOCOUNT ON; 
INSERT INTO dbo.Orders (CustomerID, OrderDate) 
VALUES (@CustomerID, @OrderDate); 
 
SET @NewOrderID = SCOPE_IDENTITY(); 
END; 
GO 
 
-- Aufruf mit Output-Variable 
DECLARE @OrderID INT; 
EXEC dbo.InsertOrder @CustomerID = 123, @OrderDate = '2026-05-08', @NewOrderID = @OrderID OUTPUT; 
SELECT @OrderID AS NeueBestellung;
✅ Merke: Output-Parameter sind effizienter als ein einzelnes Resultset, wenn nur ein Wert zurückgegeben werden soll.

Try … Catch – Robuste Fehlerbehandlung

Transaktionen sicher machen

Ab SQL Server 2005 können Sie Fehler mit BEGIN TRY...BEGIN CATCH abfangen und gezielt behandeln – unerlässlich für fehlertoleranten Code.

CREATE PROCEDURE dbo.TransferMoney 
@FromAccount INT, 
@ToAccount INT, 
@Amount DECIMAL(18,2) 
AS 
BEGIN 
SET NOCOUNT ON; 
BEGIN TRY 
BEGIN TRANSACTION; 
 
UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountID = @FromAccount; 
UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountID = @ToAccount; 
 
COMMIT TRANSACTION; 
END TRY 
BEGIN CATCH 
ROLLBACK TRANSACTION; 
 
-- Fehlerinformationen zurückgeben 
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(); 
DECLARE @ErrorSeverity INT = ERROR_SEVERITY(); 
DECLARE @ErrorState INT = ERROR_STATE(); 
 
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState); 
END CATCH 
END;
Nützliche Fehlerfunktionen im CATCH-Block:
ERROR_NUMBER(), ERROR_MESSAGE(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_LINE(), ERROR_PROCEDURE().

Commenting Code – Dokumentation im Code

Warum Kommentare den Unterschied machen

SQL unterstützt einzeilige (--) und mehrzeilige (/*...*/) Kommentare. Gute Kommentare erklären das "Warum", nicht das "Was".

CREATE PROCEDURE dbo.CleanupOldRecords 
@RetentionDays INT 
AS 
BEGIN 
SET NOCOUNT ON; 
 
-- Lösche alle Datensätze, die älter als @RetentionDays sind 
-- Die Tabelle ist nach CreateDate partitioniert, daher effizient 
DELETE FROM dbo.AuditLog 
WHERE CreateDate < DATEADD(DAY, -@RetentionDays, GETDATE()); 
 
/* 
Mehrzeiliger Kommentar: 
Nach dem Löschen wird der Index neu organisiert. 
Dies geschieht nur, wenn mehr als 1000 Zeilen betroffen waren. 
*/ 
IF @@ROWCOUNT > 1000 
ALTER INDEX IX_AuditLog_CreateDate ON dbo.AuditLog REORGANIZE; 
END;
Wichtige Regel: Kommentieren Sie nicht offensichtliches (z. B. "Inkrementiere Zähler"). Erklären Sie Fachlogik, Annahmen oder Hintergründe. Veraltete Kommentare sind schlimmer als keine.

Naming Conventions – Einheitliche Benennung

Lesbarkeit und Wartbarkeit steigern

Empfohlene Konventionen

  • Präfix: usp_ oder sp_? Vorsicht: sp_ ist systemreserviert – besser usp_ (User Stored Procedure) oder gar kein Präfix.
  • PascalCase verwenden: GetCustomerOrders statt get_customer_orders (konsistent mit systemgespeicherten Prozeduren).
  • Schema immer angeben: dbo.usp_GetCustomers – vermeidet Namensauflösungsprobleme.
  • Parameter: @CustomerID, @OrderDateFrom (camelCase mit @).
  • Output-Parameter: Endung Out oder Output (z. B. @NewIDOut).

Beispiele für gute Namen

  • usp_GetProductsByCategory
  • usp_InsertOrder
  • usp_UpdateEmployeeSalary
  • usp_CalculateInvoiceTotal
  • usp_DeleteInactiveCustomers

SET NOCOUNT ON – Warum diese Zeile so wichtig ist

Leistungsgewinn durch Unterdrückung von Rowcount-Meldungen

SET NOCOUNT ON verhindert, dass SQL Server für jede betroffene Zeile die Meldung "(x Zeile(n) betroffen)" an den Client sendet. Das reduziert den Netzwerkverkehr und kann die Leistung erheblich verbessern – besonders bei Schleifen oder vielen Updates.

CREATE PROCEDURE dbo.BulkUpdate 
AS 
BEGIN 
SET NOCOUNT ON; -- Unbedingt am Anfang 
 
UPDATE dbo.LargeTable SET Status = 'Processed' WHERE Status = 'New'; 
-- Ohne SET NOCOUNT ON würde hier eine Meldung über Hunderttausende Zeilen gesendet 
 
DELETE FROM dbo.Logs WHERE LogDate < '2020-01-01'; 
-- Auch hier: Keine Rückmeldung über gelöschte Zeilen an den Client 
END;
✅ Best Practice: Setzen Sie SET NOCOUNT ON am Anfang jeder Stored Procedure. Nur wenn Sie die Anzahl der betroffenen Zeilen in einer Anwendung auswerten müssen, lassen Sie es ausnahmsweise weg.

DROP PROCEDURE – Entfernen einer Prozedur

Löschen mit Vorsicht

Mit DROP PROCEDURE (oder kurz DROP PROC) wird eine gespeicherte Prozedur vollständig aus der Datenbank entfernt. Das Löschen ist nicht rückgängig zu machen.

-- Einzelne Prozedur löschen 
DROP PROCEDURE dbo.GetAllCustomers; 
 
-- Mehrere Prozeduren mit einer Anweisung 
DROP PROCEDURE dbo.GetAllCustomers, dbo.InsertOrder; 
 
-- Prüfen, ob Prozedur existiert (sicheres Löschen) 
IF OBJECT_ID('dbo.GetAllCustomers', 'P') IS NOT NULL 
DROP PROCEDURE dbo.GetAllCustomers;
⚠️ Achtung: Vor dem Löschen einer Prozedur sollten Sie abhängige Objekte (andere Prozeduren, Funktionen, Trigger) prüfen, die sie möglicherweise aufrufen. Verwenden Sie sp_depends oder die Abhängigkeitsansichten.

ALTER PROCEDURE – Ändern ohne Verlust von Berechtigungen

Der sicherere Weg, Prozeduren zu aktualisieren

ALTER PROCEDURE ändert die Definition einer vorhandenen Prozedur, behält aber bestehende Berechtigungen (GRANT/DENY) bei. Das ist der große Vorteil gegenüber DROP/CREATE.

-- Vorhandene Prozedur ändern 
ALTER PROCEDURE dbo.GetCustomersByCity 
@City NVARCHAR(50), 
@MinOrders INT = 0, 
@IncludeInactive BIT = 0 -- Neuer Parameter 
AS 
BEGIN 
SET NOCOUNT ON; 
SELECT CustomerID, Name, City, OrderCount 
FROM dbo.Customers 
WHERE City = @City  
AND OrderCount >= @MinOrders 
AND (IsActive = 1 OR @IncludeInactive = 1) 
ORDER BY OrderCount DESC; 
END;
📌 Tipp: Verwenden Sie in Ihren Migrationsskripten immer ALTER für bestehende Prozeduren, es sei denn, Sie wollen bewusst alle Berechtigungen zurücksetzen. Mit ALTER bleiben auch die SQL-Agent-Jobs, die die Prozedur aufrufen, funktionsfähig.

Vollständiges Beispiel – Best Practices vereint

Eine Prozedur mit allem Drum und Dran

Diese Beispielprozedur zeigt eine professionelle Umsetzung mit Parametern, Standardwerten, Fehlerbehandlung, Transaktion, NOCOUNT und aussagekräftigen Kommentaren.

CREATE PROCEDURE dbo.usp_UpdateProductStock 
@ProductID INT, 
@QuantityChange INT, 
@NewStockOut INT = NULL OUTPUT, 
@Success BIT OUTPUT 
AS 
BEGIN 
SET NOCOUNT ON; 
 
BEGIN TRY 
BEGIN TRANSACTION; 
 
-- Aktuellen Bestand ermitteln 
DECLARE @CurrentStock INT; 
SELECT @CurrentStock = StockQuantity 
FROM dbo.Products 
WHERE ProductID = @ProductID; 
 
-- Prüfen, ob Produkt existiert 
IF @CurrentStock IS NULL 
BEGIN 
SET @Success = 0; 
RAISERROR('Produkt %d nicht gefunden.', 16, 1, @ProductID); 
END 
 
-- Neuen Bestand berechnen 
UPDATE dbo.Products 
SET StockQuantity = StockQuantity + @QuantityChange, 
LastModified = GETDATE() 
WHERE ProductID = @ProductID; 
 
-- Output-Parameter füllen 
SELECT @NewStockOut = StockQuantity 
FROM dbo.Products 
WHERE ProductID = @ProductID; 
 
SET @Success = 1; 
COMMIT TRANSACTION; 
END TRY 
BEGIN CATCH 
ROLLBACK TRANSACTION; 
SET @Success = 0; 
SET @NewStockOut = NULL; 
 
-- Fehler protokollieren (in eigene Log-Tabelle) 
INSERT INTO dbo.ErrorLog (ProcedureName, ErrorMessage, ErrorLine, ErrorDate) 
VALUES ('usp_UpdateProductStock', ERROR_MESSAGE(), ERROR_LINE(), GETDATE()); 
 
-- Fehler erneut auslösen (für Aufrufer) 
THROW; 
END CATCH 
END;

Systemansichten – Überblick über alle Prozeduren

-- Alle benutzerdefinierten Prozeduren anzeigen 
SELECT  
SCHEMA_NAME(schema_id) AS SchemaName, 
name AS ProcedureName, 
create_date, 
modify_date 
FROM sys.procedures 
WHERE is_ms_shipped = 0 
ORDER BY SchemaName, name; 
 
-- Prozedur-Code anzeigen 
EXEC sp_helptext 'dbo.usp_UpdateProductStock'; 
 
-- Abhängigkeiten einer Prozedur finden 
SELECT referencing_schema_name, referencing_entity_name 
FROM sys.dm_sql_referencing_entities('dbo.usp_UpdateProductStock', 'OBJECT');
Alle Beispiele getestet unter SQL Server 2016 – 2025 | Weitere Tipps unter dtc-sql.de