Explaining CTE in SQL Server

Common Table Expressions (CTEs) in SQL are temporary result sets that you can reference within a SELECT, INSERT, UPDATE, or DELETE statement. They can be used to simplify complex queries and improve readability. Here's a detailed explanation with three real examples, complete with table creation scripts and sample data for end-to-end execution.

Simple Usage to select data

Scenario

Suppose you have a table of employees, and you want to get a list of employees along with their department names.

SQL

-- Create departments table
CREATE TABLE Departments (
    DepartmentID INT PRIMARY KEY,
    DepartmentName VARCHAR(50)
)

GO

-- Create employees table
CREATE TABLE Employees (
    EmployeeID INT PRIMARY KEY,
    EmployeeName VARCHAR(50),
    DepartmentID INT,
    FOREIGN KEY (DepartmentID) REFERENCES Departments(DepartmentID)
);

GO

-- Insert sample data into departments
INSERT INTO Departments (DepartmentID, DepartmentName) VALUES
    (1, 'HR'),
    (2, 'Finance'),
    (3, 'IT'),
    (4, 'Accounts');

GO

-- Insert sample data into employees
INSERT INTO Employees (EmployeeID, EmployeeName, DepartmentID) VALUES
    (101, 'Devesh', 1),
    (102, 'Pankaj Kumar', 2),
    (103, 'Sanjay Kumar', 3),
    (104, 'Tanuj Sharma', 2),
    (105, 'Rajesh', 1),
    (106, 'Gaurav', 4);

CTE Query

-- Define the CTE
WITH EmployeeCTE AS (
    SELECT
        e.EmployeeID,
        e.EmployeeName,
        d.DepartmentName
    FROM
        Employees e
    JOIN
        Departments d ON e.DepartmentID = d.DepartmentID
)

-- Use the CTE to select data
SELECT * 
FROM EmployeeCTE;

SQL Query

Recursive CTE for Organizational Chart

Scenario: You need to create an organizational chart that displays the hierarchy of employees and calculates the depth level of each employee within the organization

We will be defining a table having a Manager ID. And the Manager ID is nothing but its emp ID from the same table.

SQL Script

-- Create employees table with a self-referencing foreign key
CREATE TABLE OrgEmployees (
    EmployeeID INT PRIMARY KEY,
    EmployeeName VARCHAR(50),
    ManagerID INT,
    FOREIGN KEY (ManagerID) REFERENCES OrgEmployees(EmployeeID)
);

-- Insert sample data into OrgEmployees
INSERT INTO OrgEmployees (EmployeeID, EmployeeName, ManagerID) VALUES
    (1, 'CEO', NULL),
    (2, 'CTO', 1),
    (3, 'CFO', 1),
    (4, 'Lead Developer', 2),
    (5, 'Developer', 4),
    (6, 'Lead Accountant', 3),
    (7, 'Accountant', 6);

CTE query

-- Define the recursive CTE
WITH OrgHierarchy AS (
    SELECT
        EmployeeID,
        EmployeeName,
        ManagerID,
        0 AS DepthLevel,
        CAST(EmployeeName AS VARCHAR(MAX)) AS HierarchyPath
    FROM
        OrgEmployees
    WHERE
        ManagerID IS NULL

    UNION ALL

    SELECT
        e.EmployeeID,
        e.EmployeeName,
        e.ManagerID,
        oh.DepthLevel + 1,
        CAST(oh.HierarchyPath + ' -> ' + e.EmployeeName AS VARCHAR(MAX)) AS HierarchyPath
    FROM
        OrgEmployees e
    JOIN
        OrgHierarchy oh ON e.ManagerID = oh.EmployeeID
)

-- Use the CTE to select the organizational chart with depth levels
SELECT
    EmployeeID,
    EmployeeName,
    ManagerID,
    DepthLevel,
    HierarchyPath
FROM
    OrgHierarchy
ORDER BY
    HierarchyPath;

Result

Result

Multi-Level Aggregation and Filtering

Scenario: You have a sales database with multiple levels of product categories, and you want to calculate total sales for each category, subcategory, and product, then filter out categories with total sales below a certain threshold.

SQL

-- Create category table
CREATE TABLE Categories (
    CategoryID INT PRIMARY KEY,
    CategoryName VARCHAR(50),
    ParentCategoryID INT NULL
);

-- Create products table
CREATE TABLE Products (
    ProductID INT PRIMARY KEY,
    ProductName VARCHAR(50),
    CategoryID INT,
    FOREIGN KEY (CategoryID) REFERENCES Categories(CategoryID)
);

-- Create sales table
CREATE TABLE Sales (
    SaleID INT PRIMARY KEY,
    ProductID INT,
    SaleAmount DECIMAL(10, 2),
    SaleDate DATE,
    FOREIGN KEY (ProductID) REFERENCES Products(ProductID)
);

-- Insert sample data into categories
INSERT INTO Categories (CategoryID, CategoryName, ParentCategoryID) VALUES
    (1, 'Electronics', NULL),
    (2, 'Computers', 1),
    (3, 'Laptops', 2),
    (4, 'Desktops', 2),
    (5, 'Accessories', 1);

-- Insert sample data into products
INSERT INTO Products (ProductID, ProductName, CategoryID) VALUES
    (1, 'Laptop A', 3),
    (2, 'Laptop B', 3),
    (3, 'Desktop A', 4),
    (4, 'Mouse', 5),
    (5, 'Keyboard', 5);

-- Insert sample data into sales
INSERT INTO Sales (SaleID, ProductID, SaleAmount, SaleDate) VALUES
    (1, 1, 1000.00, '2024-01-01'),
    (2, 2, 1500.00, '2024-01-02'),
    (3, 3, 800.00, '2024-01-03'),
    (4, 4, 20.00, '2024-01-04'),
    (5, 5, 30.00, '2024-01-05');

CTE for multilevel aggregation and filtering

-- Define the CTE for product sales
WITH ProductSales AS (
    SELECT
        p.CategoryID,
        SUM(s.SaleAmount) AS TotalSales
    FROM
        Sales s
    JOIN
        Products p ON s.ProductID = p.ProductID
    GROUP BY
        p.CategoryID
),

-- Define the CTE for category sales
CategorySales AS (
    SELECT
        c.CategoryID,
        c.CategoryName,
        c.ParentCategoryID,
        COALESCE(ps.TotalSales, 0) AS TotalSales
    FROM
        Categories c
    LEFT JOIN
        ProductSales ps ON c.CategoryID = ps.CategoryID
),

-- Recursive CTE to accumulate sales for parent categories
RecursiveCategorySales AS (
    SELECT
        CategoryID,
        CategoryName,
        ParentCategoryID,
        TotalSales
    FROM
        CategorySales

    UNION ALL

    SELECT
        c.CategoryID,
        c.CategoryName,
        c.ParentCategoryID,
        cs.TotalSales
    FROM
        CategorySales c
    JOIN
        RecursiveCategorySales cs ON c.CategoryID = cs.ParentCategoryID
)

-- Aggregate the total sales for each category including parent categories
SELECT
    CategoryID,
    CategoryName,
    SUM(TotalSales) AS TotalSales
FROM
    RecursiveCategorySales
GROUP BY
    CategoryID, CategoryName
HAVING
    SUM(TotalSales) > 500; -- Filter for categories with sales above a threshold

CategoryID


Similar Articles