SQL Server  

Writing a Dynamic Pivot Grid Stored Procedure for Reports in SQL Server

Reporting modules often require dynamic pivoting —turning rows into columns based on varying data such as months, statuses, categories, locations, or user-defined fields.
Since these column values change over time, a static PIVOT query won’t work.
A Dynamic Pivot Stored Procedure gives full flexibility for:

  • Financial and accounting reports

  • Inventory and warehouse analytics

  • Attendance and HR dashboards

  • Sales reporting

  • Audit or workflow matrices

This article explains how to create a fully reusable Dynamic Pivot SP that works for any report model.

1. High-Level Architecture Diagram

  
    +-------------+       Calls        +---------------------------+|  Frontend   |------------------->|   API / Report Service    || (Angular)   |                    +------------+--------------++-------------+                                 |
                                                | Executes
                                                v
                                     +---------------------------+
                                     | Dynamic Pivot Stored Proc |
                                     | (SQL Server)              |
                                     +------------+--------------+
                                                  |
                                                  v
                                     +---------------------------+
                                     |     Resulting Pivot Data  |
                                     +---------------------------+
  

2. What a Dynamic Pivot Solves

A dynamic pivot supports:

  • Dynamic column headers (e.g., Jan, Feb, Mar…)

  • Dynamic values (e.g., Totals, Counts, Sums)

  • Variable groupings (e.g., Region, Department, Category)

  • Runtime filters (date range, customer, status)

Example: Convert this:

EmployeeMonthHours
RajeshJan160
RajeshFeb150
AmitJan140
AmitFeb145

Into:

EmployeeJanFeb
Rajesh160150
Amit140145

This requires dynamic SQL.

3. Core Structure of a Dynamic Pivot Procedure

A reusable procedure includes:

  1. Generating dynamic column list

  2. Building the PIVOT query

  3. Executing using sp_executesql

  4. Returning the final dataset

4. Full Dynamic Pivot Stored Procedure (Reusable)

  
    CREATE OR ALTER PROCEDURE usp_DynamicPivotReport
(
    @TableName NVARCHAR(200),
    @RowField NVARCHAR(200),        -- Stays fixed
    @ColumnField NVARCHAR(200),     -- Dynamic Column Names
    @ValueField NVARCHAR(200),      -- Aggregated Value Field
    @WhereClause NVARCHAR(MAX) = '', -- Optional Filtering
    @AggregateFunction NVARCHAR(20) = 'SUM' -- COUNT, SUM, AVG, MAX, MIN
)
ASBEGIN
    SET NOCOUNT ON;

    DECLARE @SQL NVARCHAR(MAX);
    DECLARE @Columns NVARCHAR(MAX);

    ------------------------------------------------------------
    -- Get dynamic column list
    ------------------------------------------------------------
    SET @Columns = (
        SELECT DISTINCT QUOTENAME(CAST([Value] AS NVARCHAR(200)))
        FROM (
            SELECT DISTINCT CAST((SELECT TOP 1 @ColumnField) AS NVARCHAR(200)) AS KeyCol,
                CAST((SELECT TOP 1 @ColumnField) AS NVARCHAR(200)) AS Value
        ) AS T
    );

    -- Fix: Using dynamic SQL to fetch column values properly
    DECLARE @ColSQL NVARCHAR(MAX) =
    'SELECT DISTINCT QUOTENAME(' + @ColumnField + N') AS Col
     FROM ' + @TableName +
    CASE WHEN @WhereClause <> '' THEN ' WHERE ' + @WhereClause ELSE '' END;

    CREATE TABLE #Cols (Col NVARCHAR(200));
    INSERT INTO #Cols EXEC (@ColSQL);

    SELECT @Columns = STRING_AGG(Col, ',') FROM #Cols;

    ------------------------------------------------------------
    -- Build final pivot SQL
    ------------------------------------------------------------
    SET @SQL = '
        SELECT ' + @RowField + ', ' + @Columns + '
        FROM
        (
            SELECT ' + @RowField + ',
                   ' + @ColumnField + ' AS PivotColumn,
                   ' + @ValueField + ' AS PivotValue
            FROM ' + @TableName + '
            ' + CASE WHEN @WhereClause <> '' THEN 'WHERE ' + @WhereClause ELSE '' END + '
        ) AS SourceTable
        PIVOT
        (
            ' + @AggregateFunction + '(PivotValue)
            FOR PivotColumn IN (' + @Columns + ')
        ) AS PivotTable
        ORDER BY ' + @RowField + ';
    ';

    PRINT @SQL;    -- For debugging
    EXEC sp_executesql @SQL;
END
  

5. Example Usage

5.1 Monthly Hours Report

  
    EXEC usp_DynamicPivotReport
    @TableName = 'EmployeeHours',
    @RowField = 'EmployeeName',
    @ColumnField = 'MonthName',
    @ValueField = 'Hours',
    @WhereClause = 'Year = 2025',
    @AggregateFunction = 'SUM';
  

5.2 Sales Pivot by Category

  
    EXEC usp_DynamicPivotReport
    @TableName = 'Sales',
    @RowField = 'Region',
    @ColumnField = 'Category',
    @ValueField = 'TotalAmount',
    @WhereClause = 'SaleDate >= ''2025-01-01''',
    @AggregateFunction = 'SUM';
  

5.3 Status-Wise Ticket Count

  
    EXEC usp_DynamicPivotReport
    @TableName = 'Tickets',
    @RowField = 'AssignedTo',
    @ColumnField = 'Status',
    @ValueField = 'TicketId',
    @AggregateFunction = 'COUNT';
  

6. Sequence Diagram: Dynamic Pivot Execution Flow

  
    Frontend (Angular)
      |
      | calls API
      v
Backend (.NET API)
      |
      | Executes Stored Proc
      v
SQL Server (usp_DynamicPivotReport)
      |
      | Builds Column List
      |
      | Builds Dynamic SQL
      |
      | Executes Pivot
      v
Returns Pivoted Result
  

7. Integrating With ASP.NET Core API

Controller Example

  
    [HttpPost("pivot-report")]
public async Task<IActionResult> GetPivotReport([FromBody] PivotRequest req)
{
    var data = await _repo.ExecutePivot(req);
    return Ok(data);
}
  

Repository

  
    public async Task<IEnumerable<dynamic>> ExecutePivot(PivotRequest req)
{
    return await _db.QueryAsync<dynamic>(
        "usp_DynamicPivotReport",
        new {
            req.TableName,
            req.RowField,
            req.ColumnField,
            req.ValueField,
            req.WhereClause,
            req.AggregateFunction
        },
        commandType: CommandType.StoredProcedure
    );
}
  

8. Best Practices for Dynamic Pivots

AreaBest Practice
SecurityValidate table + column names to prevent SQL injection
PerformanceUse proper indexing on row + column fields
Avoid NULLsWrap value: ISNULL(ValueField, 0)
Large DatasetsUse temp tables before pivot
UI IntegrationKeep pivot rows minimal; too many columns cause grid overflow

9. When to Use Dynamic Pivoting

Recommended For

  • Reports where column names change dynamically

  • Month/Quarter-based analytics

  • Status or category-based aggregation

  • Data warehouse summary reports

Avoid When

  • Extremely wide pivot (>200 dynamic columns)

  • Real-time operations (consider pre-aggregated tables)

10. Conclusion

A Dynamic Pivot Stored Procedure is one of the most powerful SQL templates for reporting systems. It gives your application:

  • Full flexibility

  • High performance

  • Minimal backend coding

  • Perfect support for dashboards and analytics

Once built, this SP becomes the backbone for dozens of report use cases.