databasebeginner
SQL Server ā Setup and T-SQL Fundamentals
Get SQL Server running with Docker, connect with SSMS and Azure Data Studio, and learn T-SQL: tables, data types, queries, stored procedures, and common T-SQL extensions.
Asma HafeezApril 17, 20264 min read
sql-servert-sqldatabasedotnetbackend
SQL Server ā T-SQL Fundamentals
SQL Server is Microsoft's relational database, tightly integrated with the .NET ecosystem. T-SQL is its dialect of SQL with procedural extensions: variables, IF/ELSE, loops, stored procedures, and CTEs.
Setup with Docker
Bash
docker run -d \
--name sqlserver \
-e "ACCEPT_EULA=Y" \
-e "MSSQL_SA_PASSWORD=YourStrong@Passw0rd" \
-p 1433:1433 \
mcr.microsoft.com/mssql/server:2022-latestConnect with Azure Data Studio or SSMS:
Server: localhost,1433
Login: sa
Password: YourStrong@Passw0rdCreating a Database and Tables
SQL
CREATE DATABASE Bookstore;
GO
USE Bookstore;
GO
CREATE TABLE Authors (
Id INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(200) NOT NULL,
Email NVARCHAR(200) UNIQUE,
Country NVARCHAR(100),
CreatedAt DATETIME2 DEFAULT GETUTCDATE()
);
CREATE TABLE Books (
Id INT IDENTITY(1,1) PRIMARY KEY,
Title NVARCHAR(300) NOT NULL,
AuthorId INT NOT NULL REFERENCES Authors(Id),
Price DECIMAL(10, 2) NOT NULL
CONSTRAINT CHK_Books_Price CHECK (Price >= 0),
PublishedAt DATE,
Genre NVARCHAR(100),
InStock BIT DEFAULT 1
);T-SQL differences from standard SQL:
IDENTITY(1,1)instead ofSERIALNVARCHARfor Unicode strings (use this for all text)DATETIME2for dates with high precisionBITfor booleans (0/1)GOas a batch separator
Inserting Data
SQL
INSERT INTO Authors (Name, Email, Country)
VALUES
(N'Alice Johnson', N'alice@example.com', N'Norway'),
(N'Bob Smith', N'bob@example.com', N'UK');
-- Get the inserted ID
INSERT INTO Books (Title, AuthorId, Price, Genre)
OUTPUT INSERTED.Id
VALUES (N'SQL Server Mastery', 1, 39.99, N'Technology');T-SQL Variables and Control Flow
SQL
-- Variables
DECLARE @AuthorName NVARCHAR(200) = N'Alice Johnson';
DECLARE @BookCount INT;
-- Set with SELECT
SELECT @BookCount = COUNT(*) FROM Books WHERE AuthorId = 1;
PRINT N'Books by ' + @AuthorName + N': ' + CAST(@BookCount AS NVARCHAR);
-- IF/ELSE
IF @BookCount > 5
PRINT N'Prolific author';
ELSE IF @BookCount > 0
PRINT N'Has some books';
ELSE
PRINT N'No books yet';Common Table Expressions (CTEs)
SQL
-- Basic CTE
WITH TopAuthors AS (
SELECT
a.Id,
a.Name,
COUNT(b.Id) AS BookCount,
AVG(b.Price) AS AvgPrice
FROM Authors a
LEFT JOIN Books b ON b.AuthorId = a.Id
GROUP BY a.Id, a.Name
)
SELECT Name, BookCount, ROUND(AvgPrice, 2) AS AvgPrice
FROM TopAuthors
WHERE BookCount > 0
ORDER BY BookCount DESC;
-- Recursive CTE ā employee hierarchy
WITH EmployeeHierarchy AS (
-- Anchor: top-level employees (no manager)
SELECT Id, Name, ManagerId, 0 AS Level
FROM Employees
WHERE ManagerId IS NULL
UNION ALL
-- Recursive: employees reporting to those above
SELECT e.Id, e.Name, e.ManagerId, h.Level + 1
FROM Employees e
INNER JOIN EmployeeHierarchy h ON e.ManagerId = h.Id
)
SELECT Name, Level FROM EmployeeHierarchy ORDER BY Level, Name;Stored Procedures
SQL
CREATE OR ALTER PROCEDURE usp_GetBooksByGenre
@Genre NVARCHAR(100),
@MaxPrice DECIMAL(10,2) = NULL -- optional parameter
AS
BEGIN
SET NOCOUNT ON;
SELECT
b.Title,
a.Name AS Author,
b.Price
FROM Books b
JOIN Authors a ON b.AuthorId = a.Id
WHERE b.Genre = @Genre
AND (@MaxPrice IS NULL OR b.Price <= @MaxPrice)
ORDER BY b.Price;
END;
GO
-- Execute
EXEC usp_GetBooksByGenre @Genre = N'Technology', @MaxPrice = 40;Window Functions
SQL
-- ROW_NUMBER ā rank books by price within each genre
SELECT
Title,
Genre,
Price,
ROW_NUMBER() OVER (PARTITION BY Genre ORDER BY Price DESC) AS RankInGenre
FROM Books;
-- Running total
SELECT
Title,
Price,
SUM(Price) OVER (ORDER BY PublishedAt ROWS UNBOUNDED PRECEDING) AS RunningTotal
FROM Books
WHERE PublishedAt IS NOT NULL
ORDER BY PublishedAt;
-- LAG / LEAD ā compare to previous/next row
SELECT
Title,
Price,
LAG(Price, 1, 0) OVER (ORDER BY PublishedAt) AS PrevPrice,
LEAD(Price, 1, 0) OVER (ORDER BY PublishedAt) AS NextPrice
FROM Books
WHERE PublishedAt IS NOT NULL
ORDER BY PublishedAt;Indexes in SQL Server
SQL
-- Clustered index (one per table ā defines physical order)
-- PRIMARY KEY creates a clustered index by default
-- Nonclustered index
CREATE NONCLUSTERED INDEX IX_Books_Genre
ON Books (Genre)
INCLUDE (Title, Price); -- covering index ā includes extra columns
-- Filtered index ā only index in-stock books
CREATE NONCLUSTERED INDEX IX_Books_InStock
ON Books (Price)
WHERE InStock = 1;
-- Check index usage
SELECT name, type_desc, is_unique FROM sys.indexes WHERE object_id = OBJECT_ID('Books');
-- See query plan
SET STATISTICS IO ON;
SELECT * FROM Books WHERE Genre = N'Technology';Connecting from .NET
C#
// Install: dotnet add package Microsoft.EntityFrameworkCore.SqlServer
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServer")));
// appsettings.json
{
"ConnectionStrings": {
"SqlServer": "Server=localhost,1433;Database=Bookstore;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True"
}
}
// EF Core migration
dotnet ef migrations add InitialCreate
dotnet ef database updateKey Takeaways
- T-SQL adds variables, IF/ELSE, and stored procedures on top of standard SQL
- Use
NVARCHARfor all string columns ā it handles Unicode correctly - CTEs (
WITH ... AS) make complex queries readable; recursive CTEs handle hierarchical data - Window functions (
ROW_NUMBER,SUM OVER,LAG) replace many self-joins and subqueries - Covering indexes (
INCLUDE) avoid key lookups ā the query is satisfied entirely from the index
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.