Building Dynamic Reports with Month and Weekday Labels in SQL Server

When you’re building reports in SQL Server, there’s a good chance you’ll need to display dates in a more human-readable format than the ISO 8601 standard that will likely be returned in the absence of any formatting. Nobody wants to see “2024-03-15” when “March” or “Friday” would make the report instantly clearer. SQL Server gives you several ways to extract and format these labels, and knowing which approach fits your situation can save you time and make your queries cleaner.

The Basic Tools: DATENAME() and FORMAT()

The most straightforward way to get month and weekday names is with the DATENAME() function. It works consistently, and does pretty much exactly what you’d expect:

SELECT 
    DATENAME(MONTH, GETDATE()) AS MonthName,
    DATENAME(WEEKDAY, GETDATE()) AS WeekdayName;

Result:

MonthName  WeekdayName
--------- -----------
November Sunday

This gives you full names like “November” and “Sunday”.

If you need abbreviated versions, you can use the FORMAT() function, though it can be a bit slower for large datasets:

SELECT 
    FORMAT(GETDATE(), 'MMM') AS MonthAbbr,
    FORMAT(GETDATE(), 'ddd') AS WeekdayAbbr;

Result:

MonthAbbr  WeekdayAbbr
--------- -----------
Nov Sun

For most reporting scenarios, DATENAME() is the the way to go because it’s fast and reliable. The FORMAT() function is more flexible when you need custom date formatting, but that flexibility comes with a performance cost.

One benefit that FORMAT() has over DATENAME() though, is its locale aware formatting capabilities. You can specify a locale right there within the function, which may be just what you need in some cases.

Example

Let’s say you’re tracking when support tickets are created and you want to see patterns by day of the week and month. Here’s a simple dataset we can work with:

-- Create sample data
CREATE TABLE SupportTickets (
    TicketID INT,
    CreatedDate DATE,
    Priority VARCHAR(10),
    ResolvedHours INT
);

INSERT INTO SupportTickets (TicketID, CreatedDate, Priority, ResolvedHours)
VALUES 
    (1, '2024-01-15', 'High', 4),
    (2, '2024-01-18', 'Medium', 12),
    (3, '2024-02-03', 'Low', 48),
    (4, '2024-02-14', 'High', 2),
    (5, '2024-03-07', 'Medium', 8),
    (6, '2024-03-22', 'High', 5),
    (7, '2024-03-29', 'Low', 36),
    (8, '2024-04-11', 'Medium', 15),
    (9, '2024-04-18', 'High', 3),
    (10, '2024-05-02', 'Low', 24);

Now let’s create a report showing ticket volume by month with readable labels:

SELECT 
    DATENAME(MONTH, CreatedDate) AS Month,
    MONTH(CreatedDate) AS MonthNumber,
    COUNT(*) AS TicketCount,
    AVG(ResolvedHours) AS AvgResolutionTime
FROM SupportTickets
GROUP BY DATENAME(MONTH, CreatedDate), MONTH(CreatedDate)
ORDER BY MonthNumber;

Result:

Month     MonthNumber  TicketCount  AvgResolutionTime
-------- ----------- ----------- -----------------
January 1 2 8
February 2 2 25
March 3 3 16
April 4 2 9
May 5 1 24

Notice we’re including MONTH(CreatedDate) in both the GROUP BY and SELECT clauses. This gives us a numeric value we can use for proper sorting, because alphabetically “April” comes before “January”, which isn’t what we want.

Handling Day-of-Week Analysis

When you want to see patterns by weekday, things get a bit trickier because weeks don’t follow the same sorting logic as months. You’ll want to control whether Monday or Sunday is considered the first day:

SELECT 
    DATENAME(WEEKDAY, CreatedDate) AS Weekday,
    DATEPART(WEEKDAY, CreatedDate) AS WeekdayNumber,
    COUNT(*) AS TicketCount,
    SUM(CASE WHEN Priority = 'High' THEN 1 ELSE 0 END) AS HighPriorityCount
FROM SupportTickets
GROUP BY DATENAME(WEEKDAY, CreatedDate), DATEPART(WEEKDAY, CreatedDate)
ORDER BY WeekdayNumber;

Result:

Weekday    WeekdayNumber  TicketCount  HighPriorityCount
--------- ------------- ----------- -----------------
Monday 2 1 1
Wednesday 4 1 1
Thursday 5 5 1
Friday 6 2 1
Saturday 7 1 0

The DATEPART(WEEKDAY, ...) value depends on your server’s DATEFIRST setting.

If you need consistent ordering regardless of server settings, you can use DATEDIFF() with a reference date:

SELECT 
    DATENAME(WEEKDAY, CreatedDate) AS Weekday,
    (DATEDIFF(DAY, '19000101', CreatedDate) % 7) AS DayPosition,
    COUNT(*) AS TicketCount
FROM SupportTickets
GROUP BY DATENAME(WEEKDAY, CreatedDate), (DATEDIFF(DAY, '19000101', CreatedDate) % 7)
ORDER BY DayPosition;

Result:

Weekday    DayPosition  TicketCount
--------- ----------- -----------
Monday 0 1
Wednesday 2 1
Thursday 3 5
Friday 4 2
Saturday 5 1

This calculates days since a known Monday (January 1, 1900) and uses modulo to get a consistent 0-6 value.

Dynamic Pivoting for Calendar Views

Sometimes you want a matrix view where months are columns and weekdays are rows, or vice versa. Here’s a straightforward pivot showing ticket counts:

SELECT 
    DATENAME(WEEKDAY, CreatedDate) AS Weekday,
    SUM(CASE WHEN MONTH(CreatedDate) = 1 THEN 1 ELSE 0 END) AS Jan,
    SUM(CASE WHEN MONTH(CreatedDate) = 2 THEN 1 ELSE 0 END) AS Feb,
    SUM(CASE WHEN MONTH(CreatedDate) = 3 THEN 1 ELSE 0 END) AS Mar,
    SUM(CASE WHEN MONTH(CreatedDate) = 4 THEN 1 ELSE 0 END) AS Apr,
    SUM(CASE WHEN MONTH(CreatedDate) = 5 THEN 1 ELSE 0 END) AS May
FROM SupportTickets
GROUP BY DATENAME(WEEKDAY, CreatedDate), DATEPART(WEEKDAY, CreatedDate)
ORDER BY DATEPART(WEEKDAY, CreatedDate);

Result:

Weekday    Jan  Feb  Mar  Apr  May
--------- --- --- --- --- ---
Monday 1 0 0 0 0
Wednesday 0 1 0 0 0
Thursday 1 0 1 2 1
Friday 0 0 2 0 0
Saturday 0 1 0 0 0

This gives you a quick view of which combinations of weekday and month see the most activity.

Performance Considerations

When you’re working with larger datasets, keep in mind that DATENAME() and DATEPART() functions can prevent index usage if they’re applied to indexed date columns in WHERE clauses. If you’re filtering by month or weekday, it’s better to use date range comparisons:

-- Less efficient
WHERE DATENAME(MONTH, CreatedDate) = 'March'

-- More efficient
WHERE CreatedDate >= '2024-03-01' AND CreatedDate < '2024-04-01'

That said, using these functions in SELECT and GROUP BY clauses is perfectly fine for most reporting needs.

In summary, month and weekday labels make your reports instantly understandable, and with the right sorting strategy, you can present the data in a logical order that makes sense to your audience.