Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stored Procedures

Stored Procedure (SP) là một tập hợp các câu lệnh T-SQL được biên dịch, đặt tên và lưu trữ trong database. SP là công cụ lập trình quan trọng nhất trong SQL Server, cho phép đóng gói business logic, tăng bảo mật và cải thiện hiệu năng.

1. Lợi ích của Stored Procedures

Lợi íchGiải thích
Code ReuseViết một lần, gọi từ nhiều nơi
SecurityCấp quyền EXECUTE thay vì trực tiếp trên tables
PerformanceExecution plan được compile và cache
Reduced Network TrafficChỉ truyền tên SP + tham số
MaintainabilityThay đổi logic ở một chỗ
AbstractionẨn schema chi tiết khỏi application

Execution Plan Caching

-- Lần đầu: SQL Server compile SP → lưu plan vào cache
EXEC dbo.GetCustomerOrders @CustomerId = 1;
-- Những lần sau: reuse plan từ cache (nhanh hơn)
EXEC dbo.GetCustomerOrders @CustomerId = 2;

-- Xem execution plan trong cache
SELECT 
    cp.usecounts,
    cp.objtype,
    qt.text
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) qt
WHERE qt.text LIKE '%GetCustomerOrders%';

2. Tạo và Gọi Stored Procedure

CREATE PROCEDURE

-- Cú pháp cơ bản
CREATE PROCEDURE dbo.GetActiveOrders
AS
BEGIN
    SET NOCOUNT ON; -- Tắt "X rows affected", giảm network traffic

    SELECT 
        OrderId,
        CustomerId,
        OrderDate,
        TotalAmount
    FROM Orders
    WHERE Status = 'Active'
    ORDER BY OrderDate DESC;
END;
GO

ALTER PROCEDURE và DROP PROCEDURE

-- Thay đổi SP (giữ nguyên permissions)
ALTER PROCEDURE dbo.GetActiveOrders
AS
BEGIN
    SET NOCOUNT ON;
    SELECT * FROM Orders WHERE Status = 'Active' AND IsDeleted = 0;
END;
GO

-- Xóa SP
DROP PROCEDURE IF EXISTS dbo.GetActiveOrders; -- SQL 2016+
-- hoặc
IF OBJECT_ID('dbo.GetActiveOrders', 'P') IS NOT NULL
    DROP PROCEDURE dbo.GetActiveOrders;

Input Parameters

CREATE PROCEDURE dbo.GetOrdersByCustomer
    @CustomerId     INT,
    @Status         NVARCHAR(50),
    @StartDate      DATE = NULL, -- Tham số có giá trị mặc định
    @EndDate        DATE = NULL,
    @MaxRows        INT = 100
AS
BEGIN
    SET NOCOUNT ON;

    SELECT TOP (@MaxRows)
        o.OrderId,
        o.OrderDate,
        o.TotalAmount,
        c.CustomerName
    FROM Orders o
    INNER JOIN Customers c ON o.CustomerId = c.CustomerId
    WHERE 
        o.CustomerId = @CustomerId
        AND o.Status = @Status
        AND (@StartDate IS NULL OR o.OrderDate >= @StartDate)
        AND (@EndDate IS NULL OR o.OrderDate <= @EndDate)
    ORDER BY o.OrderDate DESC;
END;
GO

-- Gọi SP với named parameters (khuyến cáo)
EXEC dbo.GetOrdersByCustomer
    @CustomerId = 1,
    @Status = 'Shipped',
    @StartDate = '2025-01-01';

-- Gọi với positional parameters (không khuyến cáo)
EXEC dbo.GetOrdersByCustomer 1, 'Shipped';

OUTPUT Parameters

CREATE PROCEDURE dbo.CreateOrder
    @CustomerId     INT,
    @TotalAmount    DECIMAL(18,2),
    @OrderId        INT OUTPUT,      -- OUTPUT parameter
    @ErrorMessage   NVARCHAR(500) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;

    SET @ErrorMessage = NULL;

    IF @TotalAmount <= 0
    BEGIN
        SET @ErrorMessage = 'TotalAmount phải lớn hơn 0';
        SET @OrderId = -1;
        RETURN -1; -- Return code
    END;

    INSERT INTO Orders (CustomerId, TotalAmount, OrderDate, Status)
    VALUES (@CustomerId, @TotalAmount, GETDATE(), 'Pending');

    SET @OrderId = SCOPE_IDENTITY(); -- Lấy ID vừa insert

    RETURN 0; -- Thành công
END;
GO

-- Gọi SP với OUTPUT parameters
DECLARE @NewOrderId INT;
DECLARE @ErrMsg NVARCHAR(500);
DECLARE @ReturnCode INT;

EXEC @ReturnCode = dbo.CreateOrder
    @CustomerId   = 5,
    @TotalAmount  = 299.99,
    @OrderId      = @NewOrderId OUTPUT,
    @ErrorMessage = @ErrMsg OUTPUT;

IF @ReturnCode = 0
    PRINT 'Order created: ' + CAST(@NewOrderId AS VARCHAR);
ELSE
    PRINT 'Error: ' + @ErrMsg;

EXEC vs EXECUTE

-- Hai cú pháp tương đương
EXEC dbo.GetActiveOrders;
EXECUTE dbo.GetActiveOrders;

-- EXEC cũng có thể chạy dynamic string
EXEC ('SELECT GETDATE()');

3. Control Flow

IF/ELSE

CREATE PROCEDURE dbo.ProcessPayment
    @OrderId    INT,
    @Amount     DECIMAL(18,2)
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @CurrentBalance DECIMAL(18,2);

    SELECT @CurrentBalance = Balance 
    FROM CustomerAccounts 
    WHERE CustomerId = (SELECT CustomerId FROM Orders WHERE OrderId = @OrderId);

    IF @CurrentBalance >= @Amount
    BEGIN
        -- Block code khi điều kiện đúng
        UPDATE CustomerAccounts 
        SET Balance = Balance - @Amount
        WHERE CustomerId = (SELECT CustomerId FROM Orders WHERE OrderId = @OrderId);

        UPDATE Orders SET Status = 'Paid' WHERE OrderId = @OrderId;
        PRINT 'Thanh toán thành công';
    END
    ELSE IF @CurrentBalance > 0
    BEGIN
        PRINT 'Số dư không đủ. Còn: ' + CAST(@CurrentBalance AS VARCHAR(20));
    END
    ELSE
    BEGIN
        PRINT 'Tài khoản không có số dư';
    END;
END;

WHILE, BREAK, CONTINUE

CREATE PROCEDURE dbo.GenerateMonthlySummary
    @Year INT
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @Month INT = 1;

    WHILE @Month <= 12
    BEGIN
        -- CONTINUE: bỏ qua tháng nếu không có dữ liệu
        IF NOT EXISTS (SELECT 1 FROM Orders 
                       WHERE YEAR(OrderDate) = @Year 
                       AND MONTH(OrderDate) = @Month)
        BEGIN
            SET @Month = @Month + 1;
            CONTINUE;
        END;

        -- Xử lý tháng hiện tại
        INSERT INTO MonthlySummary (Year, Month, TotalOrders, TotalRevenue)
        SELECT 
            @Year,
            @Month,
            COUNT(*),
            SUM(TotalAmount)
        FROM Orders
        WHERE YEAR(OrderDate) = @Year AND MONTH(OrderDate) = @Month;

        -- BREAK: dừng vòng lặp nếu đủ 6 tháng đầu
        IF @Month = 6
        BEGIN
            BREAK;
        END;

        SET @Month = @Month + 1;
    END;
END;

RETURN

CREATE PROCEDURE dbo.ValidateCustomer
    @CustomerId INT
AS
BEGIN
    IF @CustomerId <= 0
    BEGIN
        RETURN -1; -- Return code âm = lỗi
    END;

    IF NOT EXISTS (SELECT 1 FROM Customers WHERE CustomerId = @CustomerId AND IsActive = 1)
    BEGIN
        RETURN -2; -- Customer không tồn tại hoặc inactive
    END;

    -- Tiếp tục xử lý...
    RETURN 0; -- Thành công
END;
GO

DECLARE @Result INT;
EXEC @Result = dbo.ValidateCustomer @CustomerId = 5;
IF @Result <> 0
    PRINT 'Validation failed with code: ' + CAST(@Result AS VARCHAR);

GOTO (Tại sao nên tránh)

-- GOTO làm code khó đọc, khó maintain - NÊN TRÁNH
CREATE PROCEDURE dbo.ProcessWithGoto
AS
BEGIN
    DECLARE @Counter INT = 0;
    
    StartLoop:
        IF @Counter >= 10 GOTO EndLoop;
        SET @Counter = @Counter + 1;
        
        IF @Counter = 5 GOTO SkipFive;
        PRINT @Counter;
        
    SkipFive:
        GOTO StartLoop;
    
    EndLoop:
        PRINT 'Done';
END;

-- THAY THẾ BẰNG: WHILE với CONTINUE là rõ ràng hơn

4. Variables

CREATE PROCEDURE dbo.CalculateOrderStats
    @CustomerId INT
AS
BEGIN
    SET NOCOUNT ON;

    -- DECLARE nhiều biến
    DECLARE 
        @TotalOrders    INT,
        @TotalSpent     DECIMAL(18,2),
        @AverageOrder   DECIMAL(18,2),
        @CustomerName   NVARCHAR(200);

    -- SET: gán một giá trị cụ thể
    SET @CustomerName = (
        SELECT CustomerName 
        FROM Customers 
        WHERE CustomerId = @CustomerId
    );

    -- SELECT: gán từ query (linh hoạt hơn cho multiple columns)
    SELECT 
        @TotalOrders  = COUNT(*),
        @TotalSpent   = SUM(TotalAmount)
    FROM Orders
    WHERE CustomerId = @CustomerId AND Status = 'Completed';

    -- Tính toán
    SET @AverageOrder = CASE 
        WHEN @TotalOrders > 0 THEN @TotalSpent / @TotalOrders 
        ELSE 0 
    END;

    -- Kết quả
    SELECT 
        @CustomerName AS CustomerName,
        @TotalOrders  AS TotalOrders,
        @TotalSpent   AS TotalSpent,
        @AverageOrder AS AverageOrderValue;
END;

Lưu ý quan trọng: Nếu SELECT @Variable = column FROM table WHERE condition không tìm thấy row nào, biến giữ nguyên giá trị trước đó (không về NULL). Đây là một nguy cơ bug phổ biến!


5. Temp Tables trong Stored Procedures

Local Temp Table (#) vs Global Temp Table (##)

CREATE PROCEDURE dbo.ProcessLargeDataset
    @BatchSize INT = 1000
AS
BEGIN
    SET NOCOUNT ON;

    -- LOCAL temp table: chỉ visible trong session hiện tại
    -- Tự động drop khi SP kết thúc
    CREATE TABLE #TempOrders (
        OrderId     INT,
        CustomerId  INT,
        TotalAmount DECIMAL(18,2),
        INDEX IX_Temp_CustomerId (CustomerId) -- Có thể thêm index!
    );

    INSERT INTO #TempOrders (OrderId, CustomerId, TotalAmount)
    SELECT OrderId, CustomerId, TotalAmount
    FROM Orders
    WHERE Status = 'Pending' AND ProcessedFlag = 0;

    -- GLOBAL temp table: visible cho tất cả sessions
    -- Dùng khi cần share data giữa các sessions/connections
    -- CREATE TABLE ##GlobalTemp (...)  -- Hiếm khi dùng

    -- Xử lý từng batch
    WHILE EXISTS (SELECT 1 FROM #TempOrders WHERE ProcessedFlag IS NULL)
    BEGIN
        UPDATE TOP (@BatchSize) #TempOrders
        SET ProcessedFlag = 1
        OUTPUT inserted.OrderId, inserted.TotalAmount
        INTO OrderProcessingLog (OrderId, Amount)
        WHERE ProcessedFlag IS NULL;
    END;

    -- Explicit drop (optional, sẽ tự drop khi SP kết thúc)
    DROP TABLE IF EXISTS #TempOrders;
END;

Temp Table vs Table Variable

-- Table Variable: nhỏ, no statistics, ít log overhead
DECLARE @OrderSummary TABLE (
    CustomerId  INT,
    OrderCount  INT,
    TotalAmount DECIMAL(18,2)
);

INSERT INTO @OrderSummary
SELECT CustomerId, COUNT(*), SUM(TotalAmount)
FROM Orders
GROUP BY CustomerId;

-- Temp Table: lớn, có statistics, optimizer dùng được
CREATE TABLE #OrderSummary (
    CustomerId  INT PRIMARY KEY,
    OrderCount  INT,
    TotalAmount DECIMAL(18,2)
);

-- Dùng Temp Table khi:
-- 1. Dataset lớn (> vài nghìn rows)
-- 2. Cần index phức tạp
-- 3. Cần thống kê (statistics) để optimizer plan tốt
-- 4. Reuse trong nhiều queries

6. Error Handling

TRY…CATCH

CREATE PROCEDURE dbo.TransferFunds
    @FromAccountId  INT,
    @ToAccountId    INT,
    @Amount         DECIMAL(18,2)
AS
BEGIN
    SET NOCOUNT ON;

    IF @Amount <= 0
        THROW 50001, 'Số tiền chuyển phải lớn hơn 0', 1;

    BEGIN TRY
        BEGIN TRANSACTION;

        -- Kiểm tra số dư
        DECLARE @Balance DECIMAL(18,2);
        SELECT @Balance = Balance FROM Accounts WHERE AccountId = @FromAccountId;

        IF @Balance < @Amount
            THROW 50002, 'Số dư không đủ để thực hiện giao dịch', 1;

        -- Thực hiện transfer
        UPDATE Accounts SET Balance = Balance - @Amount WHERE AccountId = @FromAccountId;
        UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountId = @ToAccountId;

        -- Ghi log
        INSERT INTO TransactionLog (FromAccount, ToAccount, Amount, TransactionDate)
        VALUES (@FromAccountId, @ToAccountId, @Amount, GETDATEUTCDATE());

        COMMIT TRANSACTION;
        PRINT 'Transfer thành công';

    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- Lấy thông tin lỗi
        DECLARE 
            @ErrorNumber    INT = ERROR_NUMBER(),
            @ErrorMessage   NVARCHAR(4000) = ERROR_MESSAGE(),
            @ErrorSeverity  INT = ERROR_SEVERITY(),
            @ErrorState     INT = ERROR_STATE(),
            @ErrorLine      INT = ERROR_LINE(),
            @ErrorProc      NVARCHAR(200) = ERROR_PROCEDURE();

        -- Ghi log lỗi
        INSERT INTO ErrorLog (ErrorNumber, ErrorMessage, ErrorProcedure, ErrorLine, CreatedAt)
        VALUES (@ErrorNumber, @ErrorMessage, @ErrorProc, @ErrorLine, GETDATE());

        -- Re-throw lỗi cho caller
        THROW;
    END CATCH;
END;

ERROR_ Functions

-- Các function lấy thông tin lỗi trong CATCH block
BEGIN CATCH
    SELECT
        ERROR_NUMBER()    AS ErrorNumber,    -- Số hiệu lỗi
        ERROR_MESSAGE()   AS ErrorMessage,   -- Thông điệp lỗi
        ERROR_SEVERITY()  AS ErrorSeverity,  -- Mức độ nghiêm trọng (1-25)
        ERROR_STATE()     AS ErrorState,     -- State code
        ERROR_LINE()      AS ErrorLine,      -- Dòng code gây lỗi
        ERROR_PROCEDURE() AS ErrorProcedure; -- Tên SP/trigger gây lỗi
END CATCH;

RAISERROR vs THROW

-- RAISERROR (cũ, nhưng vẫn dùng)
RAISERROR('Lỗi xử lý đơn hàng %d', 16, 1, @OrderId);
-- %d = int placeholder, %s = string placeholder

-- THROW (SQL 2012+, khuyến cáo dùng)
THROW 50001, 'Lỗi xử lý đơn hàng', 1;

-- Re-throw trong CATCH (không tham số)
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW; -- Giữ nguyên error number, message, state gốc
END CATCH;

7. Nested Stored Procedures

-- SP cấp 1: Outer
CREATE PROCEDURE dbo.ProcessDailyBatch
AS
BEGIN
    SET NOCOUNT ON;
    PRINT 'NESTLEVEL: ' + CAST(@@NESTLEVEL AS VARCHAR); -- 1

    -- Gọi SP khác
    EXEC dbo.ValidateOrders;
    EXEC dbo.UpdateInventory;
    EXEC dbo.SendNotifications;
END;

-- SP cấp 2: Inner
CREATE PROCEDURE dbo.ValidateOrders
AS
BEGIN
    SET NOCOUNT ON;
    PRINT 'NESTLEVEL: ' + CAST(@@NESTLEVEL AS VARCHAR); -- 2

    EXEC dbo.CheckOrderRules; -- Level 3
END;

-- Giới hạn: tối đa 32 levels
-- @@NESTLEVEL = 0 khi gọi trực tiếp từ client

8. Transactions trong Stored Procedures

CREATE PROCEDURE dbo.PlaceOrder
    @CustomerId     INT,
    @Items          OrderItemType READONLY, -- Table-valued parameter
    @OrderId        INT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON; -- Tự động rollback khi có lỗi runtime

    BEGIN TRY
        BEGIN TRANSACTION;

        -- Tạo Order
        INSERT INTO Orders (CustomerId, OrderDate, Status)
        VALUES (@CustomerId, GETDATE(), 'Pending');

        SET @OrderId = SCOPE_IDENTITY();

        -- Insert Order Items
        INSERT INTO OrderItems (OrderId, ProductId, Quantity, UnitPrice)
        SELECT @OrderId, ProductId, Quantity, UnitPrice
        FROM @Items;

        -- Cập nhật stock
        UPDATE p
        SET p.Stock = p.Stock - i.Quantity
        FROM Products p
        INNER JOIN @Items i ON p.ProductId = i.ProductId;

        -- Kiểm tra stock âm
        IF EXISTS (SELECT 1 FROM Products p INNER JOIN @Items i ON p.ProductId = i.ProductId WHERE p.Stock < 0)
            THROW 50003, 'Sản phẩm không đủ hàng trong kho', 1;

        COMMIT TRANSACTION;

    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
END;

@@TRANCOUNT và SAVE TRANSACTION

CREATE PROCEDURE dbo.UpdateWithSavepoint
AS
BEGIN
    -- @@TRANCOUNT: số transaction đang active
    PRINT '@@TRANCOUNT trước: ' + CAST(@@TRANCOUNT AS VARCHAR); -- 0

    BEGIN TRANSACTION; -- @@TRANCOUNT = 1

    INSERT INTO AuditLog (Event) VALUES ('Process started');

    SAVE TRANSACTION MySavepoint; -- Tạo savepoint

    BEGIN TRY
        -- Thao tác có thể lỗi
        INSERT INTO Orders (CustomerId, TotalAmount) VALUES (999999, -100);
    END TRY
    BEGIN CATCH
        -- Rollback về savepoint, giữ lại INSERT AuditLog
        ROLLBACK TRANSACTION MySavepoint;
        INSERT INTO AuditLog (Event) VALUES ('Order insert failed: ' + ERROR_MESSAGE());
    END CATCH;

    COMMIT TRANSACTION; -- Commit AuditLog inserts
END;

9. Dynamic SQL

sp_executesql (Khuyến cáo)

CREATE PROCEDURE dbo.SearchProducts
    @SearchTerm     NVARCHAR(100),
    @CategoryId     INT = NULL,
    @SortColumn     NVARCHAR(50) = 'ProductName',
    @SortDirection  NVARCHAR(4) = 'ASC'
AS
BEGIN
    SET NOCOUNT ON;

    -- Whitelist cho sort columns (ngăn SQL injection)
    IF @SortColumn NOT IN ('ProductName', 'Price', 'CreatedDate', 'Stock')
        SET @SortColumn = 'ProductName';

    IF @SortDirection NOT IN ('ASC', 'DESC')
        SET @SortDirection = 'ASC';

    DECLARE @SQL NVARCHAR(MAX);
    DECLARE @Params NVARCHAR(500);

    SET @SQL = N'
        SELECT ProductId, ProductName, Price, CategoryId
        FROM Products
        WHERE 
            (ProductName LIKE @SearchTerm OR Description LIKE @SearchTerm)
            AND (@CategoryId IS NULL OR CategoryId = @CategoryId)
            AND IsActive = 1
        ORDER BY ' + QUOTENAME(@SortColumn) + ' ' + @SortDirection; -- QUOTENAME bảo vệ tên column

    SET @Params = N'@SearchTerm NVARCHAR(100), @CategoryId INT';

    EXEC sp_executesql 
        @SQL, 
        @Params,
        @SearchTerm = '%' + @SearchTerm + '%',
        @CategoryId = @CategoryId;
END;

Nguy hiểm SQL Injection với EXEC()

-- ĐỪNG làm thế này - dễ bị SQL injection
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = 'SELECT * FROM Products WHERE Name = ''' + @UserInput + '''';
EXEC (@SQL);
-- Nếu @UserInput = "'; DROP TABLE Products; --" thì GG!

-- ĐÚNG: Dùng sp_executesql với parameters
SET @SQL = N'SELECT * FROM Products WHERE Name = @Name';
EXEC sp_executesql @SQL, N'@Name NVARCHAR(200)', @Name = @UserInput;

10. Recompilation và Parameter Sniffing

Vấn đề Parameter Sniffing

-- SP này có vấn đề parameter sniffing
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
AS
    SELECT * FROM Orders WHERE CustomerId = @CustomerId;
-- Nếu compile với CustomerId = 1 (ít orders) → Index Seek plan
-- Khi gọi với CustomerId = 999 (nhiều orders) → vẫn dùng Seek plan → chậm!

Các giải pháp

-- Giải pháp 1: OPTION (RECOMPILE) trên query
-- Recompile query này mỗi lần, tốt cho queries không consistent
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
AS
    SELECT * FROM Orders 
    WHERE CustomerId = @CustomerId
    OPTION (RECOMPILE); -- Recompile mỗi lần, overhead nhỏ

-- Giải pháp 2: WITH RECOMPILE trên SP
-- Recompile toàn bộ SP mỗi lần
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
WITH RECOMPILE -- Không cache plan
AS
    SELECT * FROM Orders WHERE CustomerId = @CustomerId;

-- Giải pháp 3: Local variable (ngăn sniffing)
-- Optimizer không biết giá trị → dùng statistics thay vì parameter value
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
AS
    DECLARE @LocalId INT = @CustomerId;
    SELECT * FROM Orders WHERE CustomerId = @LocalId;

-- Giải pháp 4: OPTIMIZE FOR hint
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
AS
    SELECT * FROM Orders 
    WHERE CustomerId = @CustomerId
    OPTION (OPTIMIZE FOR (@CustomerId = 100)); -- Optimize cho giá trị điển hình

-- Giải pháp 5: OPTIMIZE FOR UNKNOWN
CREATE PROCEDURE dbo.GetOrders @CustomerId INT
AS
    SELECT * FROM Orders 
    WHERE CustomerId = @CustomerId
    OPTION (OPTIMIZE FOR (@CustomerId UNKNOWN)); -- Dùng statistics thay vì parameter value

sp_recompile

-- Force recompile SP lần sau khi gọi
EXEC sp_recompile 'dbo.GetOrders';

-- Force recompile tất cả SPs dùng bảng này
EXEC sp_recompile 'dbo.Orders';

11. System Stored Procedures

-- Thông tin về object
EXEC sp_help 'Orders';              -- Thông tin table/view
EXEC sp_helpindex 'Orders';         -- Danh sách indexes
EXEC sp_helpconstraint 'Orders';    -- Constraints

-- Monitoring
EXEC sp_who2;                       -- Các connections hiện tại
EXEC sp_lock;                       -- Locks đang active

-- Dynamic SQL (đã nói ở trên)
EXEC sp_executesql @SQL, @Params;

-- Thống kê
EXEC sp_updatestats;                -- Update tất cả statistics

-- Metadata
SELECT 
    name, 
    OBJECT_DEFINITION(object_id) AS Definition,
    create_date,
    modify_date
FROM sys.procedures
WHERE name LIKE 'GetOrder%';

-- Tìm SP nào reference đến một table
SELECT DISTINCT 
    o.name AS ProcedureName
FROM sys.sql_modules m
INNER JOIN sys.objects o ON m.object_id = o.object_id
WHERE 
    o.type = 'P'
    AND m.definition LIKE '%Orders%';

Best Practices Tóm Tắt

-- Template cho một Stored Procedure chuẩn
CREATE PROCEDURE dbo.MyProcedure
    @Param1 INT,
    @Param2 NVARCHAR(100) = NULL
AS
BEGIN
    SET NOCOUNT ON;      -- Tắt "X rows affected"
    SET XACT_ABORT ON;   -- Auto rollback khi lỗi

    -- Validate inputs
    IF @Param1 IS NULL OR @Param1 <= 0
        THROW 50001, 'Param1 không hợp lệ', 1;

    BEGIN TRY
        BEGIN TRANSACTION;

        -- Business logic ở đây

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- Log error
        INSERT INTO dbo.ErrorLog (ErrorMessage, ErrorProcedure, ErrorLine, CreatedAt)
        VALUES (ERROR_MESSAGE(), ERROR_PROCEDURE(), ERROR_LINE(), GETDATE());

        THROW; -- Re-throw cho caller
    END CATCH;
END;
GO
Best PracticeLý do
SET NOCOUNT ONGiảm network traffic
SET XACT_ABORT ONAuto rollback an toàn
Validate parameters đầu vàoFail fast với thông báo rõ ràng
Dùng schema prefix (dbo.)Tránh resolution ambiguity
Dùng sp_executesql cho dynamic SQLNgăn SQL injection, plan reuse
Tránh SELECT *Explicit columns tốt hơn
Dùng THROW thay RAISERRORModern, giữ context tốt hơn
Comment business logic phức tạpMaintainability