Below are the basic concepts in SQL Server:
1. Stored Procedure
A stored procedure is a group of SQL statements that can be executed together as a single unit.
CREATE PROCEDURE GetEmployeeByID
@EmployeeID INT
AS
BEGIN
SELECT FirstName, LastName, Department
FROM Employees
WHERE EmployeeID = @EmployeeID;
END;
2. Function
A function is a reusable piece of code that returns a value.
CREATE FUNCTION CalculateTotalSalary
(@EmployeeID INT)
RETURNS MONEY
AS
BEGIN
DECLARE @TotalSalary MONEY;
SELECT @TotalSalary = SUM(Salary)
FROM SalaryDetails
WHERE EmployeeID = @EmployeeID;
RETURN @TotalSalary;
END;
Once the function is created, you can use it in your queries like this:
-- Use the function to calculate the total price for a product
DECLARE @UnitPrice MONEY = 10.50;
DECLARE @Quantity INT = 5;
SELECT ProductName, @UnitPrice AS UnitPrice, @Quantity AS Quantity,
dbo.CalculateTotalPrice(@UnitPrice, @Quantity) AS TotalPrice
FROM Products
WHERE ProductID = 1;
3. Trigger
A trigger is a piece of code that automatically executes when a specific event (like an INSERT, UPDATE, DELETE) occurs on a table.
CREATE TRIGGER AuditEmployeeChanges
ON Employees
AFTER UPDATE
AS
BEGIN
INSERT INTO EmployeeAuditLog (EmployeeID, Action, ChangeDate)
SELECT EmployeeID, 'UPDATE', GETDATE()
FROM inserted;
END;
4. Common Table Expression (CTE)
A CTE is a temporary result set within a query that can be referenced within the same query.
WITH DepartmentCTE AS (
SELECT DepartmentID, DepartmentName
FROM Departments
WHERE Location = 'New York'
)
SELECT E.EmployeeID, E.FirstName, E.LastName, D.DepartmentName
FROM Employees E
JOIN DepartmentCTE D ON E.DepartmentID = D.DepartmentID;
5. TRY...CATCH
The TRY...CATCH block is used to handle errors in a controlled manner.
BEGIN TRY
-- Attempt an operation that might cause an error
INSERT INTO NonExistentTable (ID, Name) VALUES (1, 'Example');
END TRY
BEGIN CATCH
-- Handle the error
PRINT 'An error occurred: ' + ERROR_MESSAGE();
END CATCH;
You can use functions like ERROR_MESSAGE()
, ERROR_NUMBER()
, ERROR_STATE()
, etc., to access error information.
ERROR_MESSAGE():
Returns the error message text for the error that occurred.
Provides a detailed description of the error.
Example:
PRINT 'Error Message: ' + ERROR_MESSAGE();
ERROR_NUMBER():
Returns the error number or code associated with the error.
Can help you identify the specific error type.
Example:
PRINT 'Error Number: ' + CAST(ERROR_NUMBER() AS NVARCHAR(10));
ERROR_SEVERITY():
Returns the severity level of the error.
The severity indicates the type of error and its impact.
Example:
PRINT 'Error Severity: ' + CAST(ERROR_SEVERITY() AS NVARCHAR(10));
ERROR_STATE():
Returns the state number of the error.
The state can provide additional information about the error context.
Example:
PRINT 'Error State: ' + CAST(ERROR_STATE() AS NVARCHAR(10));
ERROR_PROCEDURE():
Returns the name of the stored procedure or trigger where the error occurred (if applicable).
Useful for identifying the location of the error in your code.
Example:
PRINT 'Error Procedure: ' + ERROR_PROCEDURE();
ERROR_LINE():
Returns the line number in the batch or procedure where the error occurred.
Helps pinpoint the exact location of the error in the code.
Example:
PRINT 'Error Line: ' + CAST(ERROR_LINE() AS NVARCHAR(10));
ERROR_RAISEERROR():
This function is used to re-throw the original error caught in the
CATCH
block.You can use this to propagate the error to an outer level if needed.
Example:
BEGIN CATCH -- Handle the error PRINT 'Error Message: ' + ERROR_MESSAGE(); -- Re-throw the error THROW; END CATCH;
6. Index
indexes are used to improve the efficiency of data retrieval operations by providing faster access to the underlying data. There are two main types of indexes: clustered indexes and non-clustered indexes. Let's explore each type with an example and an explanation of when to use them.
1. Clustered Index:
A clustered index determines the physical order of data rows in a table. Each table can have only one clustered index because it defines the actual storage order of the data. Typically, the primary key of a table is used as the basis for the clustered index.
Example: Suppose you have a table named Customers
with the following columns:
CustomerID (Primary Key)
FirstName
LastName
Email
You decide to create a clustered index on the CustomerID
column:
CREATE CLUSTERED INDEX IX_CustomerID ON Customers (CustomerID);
In this case, the data rows in the Customers
table will be physically ordered based on the values in the CustomerID
column.
When to Use a Clustered Index:
Choose a clustered index when you want to physically order the data rows for fast retrieval of ranges of data.
Typically, the primary key is a good candidate for a clustered index because it's unique and often used in JOINs and WHERE clauses.
Be mindful that frequent updates to the clustered index can result in performance overhead due to page splits.
2. Non-Clustered Index:
A non-clustered index is a separate data structure that contains a copy of the indexed columns along with a reference to the corresponding rows in the table. Each table can have multiple non-clustered indexes.
Example: Continuing with the Customers
table, you decide to create a non-clustered index on the LastName
column:
CREATE NONCLUSTERED INDEX IX_LastName ON Customers (LastName);
In this case, the non-clustered index will contain a copy of the LastName
column along with pointers to the actual data rows in the table.
When to Use a Non-Clustered Index:
Choose a non-clustered index when you want to improve the performance of specific queries that involve filtering, sorting, or joining.
Non-clustered indexes are useful for columns frequently used in WHERE clauses, JOIN conditions, and ORDER BY clauses.
Be aware that non-clustered indexes consume additional storage space and can have a slight overhead on data modification operations.
Deciding Between Clustered and Non-Clustered Index:
When choosing between a clustered and non-clustered index, consider the nature of your queries, the distribution of data, and the usage patterns of your application.
Clustered indexes work well for range-based queries and primary key access.
Non-clustered indexes are suited for specific queries that involve filtering, sorting, and joining.
Deciding which columns should have an index
Suppose you have two tables: Orders
and Customers
. You want to retrieve a list of customers who made orders in the last month, grouped by their city, and ordered by the total order amount.
SELECT
C.City,
SUM(O.TotalAmount) AS TotalOrderAmount
FROM
Customers AS C
JOIN
Orders AS O ON C.CustomerID = O.CustomerID
WHERE
O.OrderDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY
C.City
ORDER BY
TotalOrderAmount DESC;
Here's how we can consider indexes for each part of the query:
WHERE Clause (Filtering): The
WHERE
clause filters rows based on theOrderDate
. An index on theOrderDate
column in theOrders
table can speed up the filtering process.CREATE NONCLUSTERED INDEX IX_OrderDate ON Orders (OrderDate);
JOIN Clause (Joining): The
JOIN
involves joining theCustomers
andOrders
tables using theCustomerID
column. Indexing theCustomerID
column in both tables can improve the performance of the join operation.CREATE NONCLUSTERED INDEX IX_CustomerID_Customers ON Customers (CustomerID); CREATE NONCLUSTERED INDEX IX_CustomerID_Orders ON Orders (CustomerID);
GROUP BY Clause (Grouping): Since we're grouping by the
City
column, indexing theCity
column in theCustomers
table can help with grouping.CREATE NONCLUSTERED INDEX IX_City ON Customers (City);
ORDER BY Clause (Sorting): The
ORDER BY
clause is used to sort the results by the calculatedTotalOrderAmount
. In this case, indexing won't directly impact the sorting operation, but the indexing decisions made for other parts of the query can indirectly affect sorting performance.
7. Transactions
Transactions in SQL Server are used to group a sequence of database operations into a single unit of work. Transactions ensure that a set of related operations are either completed successfully or rolled back entirely if an error occurs, maintaining data integrity and consistency. Transactions follow the ACID properties: Atomicity, Consistency, Isolation, and Durability.
Let's go through the concepts of transactions with examples:
Example 1: Basic Transaction Suppose we have a table named BankAccounts
with columns AccountNumber
and Balance
, and we want to transfer money from one account to another within a transaction.
BEGIN TRANSACTION; -- Start the transaction
BEGIN TRY
UPDATE BankAccounts
SET Balance = Balance - 100
WHERE AccountNumber = 'A123'; -- Deduct $100 from Account A
UPDATE BankAccounts
SET Balance = Balance + 100
WHERE AccountNumber = 'B456'; -- Add $100 to Account B
COMMIT; -- Commit the transaction if successful
END TRY
BEGIN CATCH
ROLLBACK; -- Rollback the transaction if an error occurs
PRINT 'Transaction failed: ' + ERROR_MESSAGE();
END CATCH;
In this example:
We start a transaction using
BEGIN TRANSACTION
.We perform two
UPDATE
operations within the transaction to transfer $100 from Account A to Account B.If both updates succeed, we commit the transaction using
COMMIT
.If an error occurs (e.g., insufficient funds), we roll back the transaction using
ROLLBACK
.
Example 2: Nested Transactions SQL Server supports nested transactions, but only the outermost transaction needs to be explicitly committed or rolled back. Inner transactions are treated as savepoints within the outer transaction.
BEGIN TRANSACTION;
BEGIN TRY
-- Outer transaction
UPDATE BankAccounts SET Balance = Balance - 200 WHERE AccountNumber = 'C789';
BEGIN TRANSACTION; -- Nested transaction
UPDATE BankAccounts SET Balance = Balance + 200 WHERE AccountNumber = 'D012';
-- Inner transaction can be committed or rolled back
-- COMMIT; -- Uncomment to commit inner transaction
-- ROLLBACK; -- Uncomment to rollback inner transaction
COMMIT; -- Commit outer transaction
END TRY
BEGIN CATCH
ROLLBACK; -- Rollback outer transaction if an error occurs
PRINT 'Transaction failed: ' + ERROR_MESSAGE();
END CATCH;
In this example:
We start an outer transaction and perform an
UPDATE
operation.We then start a nested transaction and perform another
UPDATE
operation.Depending on whether the inner transaction is committed or rolled back, the outer transaction will be committed or rolled back accordingly.
Transactions ensure data consistency and help maintain the integrity of the database even when errors occur during complex operations. It's important to use transactions wisely and consider their impact on concurrency and performance.