FORMAT is a convenient but expensive SQL Server function - Part 1

By:   |   Updated: 2022-02-16   |   Comments (2)   |   Related: 1 | 2 | 3 | 4 | 5 | > Functions System


Problem

I once demonstrated how FORMAT is about twice as expensive as CONVERT in a Microsoft SQL Server database. That comparison may have been a bit unfair - what about more convoluted CONVERT expressions? Are there other ways to have the convenience of FORMAT date function without the overhead at query time in a SQL database?

Solution

First, in the 2015 blog post, I compared these two T-SQL date format approaches:

SELECT FORMAT(sysdatetime(), 'yyyy-MM-dd'),
CONVERT(char(10), sysdatetime(), 120);

Admittedly, the SQL CONVERT function is trivial, and many of us generate more specialized output than simple ISO-type strings. Let's consider an even simpler SQL FORMAT expression that produces a more human readable output (though, to be completely transparent, this is really the job of the presentation layer):

DECLARE @d date = '20220114';
SELECT FORMAT(@d, 'D', 'en-us');

This syntax produces the following output:

Friday, January 14, 2022

To get the same output without FORMAT, we'd need a SELECT statement like the following:

SET LANGUAGE us_english;
DECLARE @d date = '20220114';
SELECT CONCAT_WS(', ', 
DATENAME(WEEKDAY, @d),
DATENAME(MONTH, @d) + ' '
+ CONVERT(varchar(2), DATEPART(DAY, @d)),
DATEPART(YEAR, @d)
);

This latter expression is clearly much more tedious to write, and not all that pretty to look at, either. And we need to start with the SET LANGUAGE statement to match the same locale specifier that FORMAT affords inline, because a user's own language settings may be different from the one we use during testing. (For completeness, though, I will mention that you usually want the user's own language settings to dictate output format.)

I have had conversations with many peers who need more complex expressions like this, and they really appreciate the simplification FORMAT offers, as well as the consistency with other languages like C#. They often suggest that, surely, all these extra computations will pull the performance down closer to FORMAT. Well, we can test this! Let's create a database with Query Store enabled:

USE master;
GO DROP DATABASE IF EXISTS FormatTest;
GO CREATE DATABASE FormatTest;
GO ALTER DATABASE FormatTest SET QUERY_STORE = ON (QUERY_CAPTURE_MODE = ALL);
GO USE FormatTest;
GO

Next, we can create a table with several thousand rows:

SELECT o.*, column_name = c.name 
INTO dbo.Columns
FROM sys.all_objects AS o
INNER JOIN sys.all_columns AS c
ON o.[object_id] = c.[object_id]; CREATE CLUSTERED INDEX cix ON dbo.Columns(name, column_name);

And run simple queries that scan the entire table and transform the modify_date value in each row:

SET NOCOUNT ON;
GO DBCC FREEPROCCACHE;
DBCC DROPCLEANBUFFERS;
GO DECLARE @d varchar(50); SELECT /* convert1 */ @d = CONCAT_WS(', ',
DATENAME(WEEKDAY, modify_date),
DATENAME(MONTH, modify_date) + ' '
+ CONVERT(varchar(2), DATEPART(DAY, modify_date)),
CONVERT(char(4), DATEPART(YEAR, modify_date)))
FROM dbo.Columns;
GO 50 DBCC FREEPROCCACHE;
DBCC DROPCLEANBUFFERS;
GO DECLARE @d varchar(50); SELECT /* format */ @d = FORMAT(modify_date, 'D', 'en-us')
FROM dbo.Columns;
GO 50

If we look at the metrics from Query Store, we can see that my earlier observations about FORMAT and its CLR overhead are still true even up against this more complex expression:

Query store results for convert vs. format

One reason people tend to gravitate toward FORMAT (even in spite of its performance overhead) is that they don't want to muck up their queries with complex expressions like the one I've listed above. Another way to avoid that is to hide the complex functionality away in a user-defined function. Let's try scalar and inline table-valued functions to hide the expression away:

CREATE FUNCTION dbo.PrettyDate_Scalar
(
@date date
)
RETURNS varchar(50)
WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT /* scalar */ PrettyDate = CONCAT_WS(', ',
DATENAME(WEEKDAY, @date),
DATENAME(MONTH, @date) + ' '
+ CONVERT(varchar(2), DATEPART(DAY, @date)),
CONVERT(char(4), DATEPART(YEAR, @date)))
);
END
GO CREATE FUNCTION dbo.PrettyDate_InlineTVF
(
@date date
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (SELECT /* inlineTVF */ PrettyDate = CONCAT_WS(', ',
DATENAME(WEEKDAY, @date),
DATENAME(MONTH, @date) + ' '
+ CONVERT(varchar(2), DATEPART(DAY, @date)),
CONVERT(char(4), DATEPART(YEAR, @date)))
);
GO

Then we can add these queries to our test rig:

DBCC FREEPROCCACHE;
DBCC DROPCLEANBUFFERS;
GO DECLARE /* scalar */ @d varchar(50); SELECT /* scalar */ @d = dbo.PrettyDate_Scalar(modify_date)
FROM dbo.Columns;
GO 50 DBCC FREEPROCCACHE;
DBCC DROPCLEANBUFFERS;
GO DECLARE /* inlineTVF */ @d varchar(50); SELECT /* inlineTVF */ @d = f.PrettyDate
FROM dbo.Columns AS c
CROSS APPLY dbo.PrettyDate_InlineTVF(c.modify_date) AS f;
GO 50

Results:

Query Store results for convert vs. format vs. functions

These are just microseconds, and so these differences might not be obvious in local testing, but this is a different universe and will be much more noticeable at scale.

Note when using inline formatting or functions, we could easily add additional formatting that FORMAT doesn't offer; for example, we could add suffixes on the day name, like 1st, 2nd 3rd, 4th, etc.

     + CONVERT(varchar(2), DATEPART(DAY, @d))
+ CASE WHEN DATEPART(DAY, @d) BETWEEN 10 AND 19 THEN 'th'
WHEN DATEPART(DAY, @d) % 10 = 1 THEN 'st'
WHEN DATEPART(DAY, @d) % 10 = 2 THEN 'nd'
WHEN DATEPART(DAY, @d) % 10 = 3 THEN 'rd'
ELSE 'th' END,

Adding this additional formatting logic to the inline convert query and a new table-valued function produces these results:

Query store results for convert vs. format vs. UDFs and adding additional formatting

While there is a tangible bump, we're still under the 10 millisecond range, remaining way better than FORMAT and its runtime approaching 200 milliseconds.

There are other ways to simplify queries and still get this output (other than letting the application layer handle it). Next time, we'll look at ways we can prevent the query from having to perform any formatting at all, namely with a computed column or a DML trigger.

Conclusion

While I like the idea of FORMAT because it makes for more convenient expressions that are consistent with other languages, the performance overhead is just too significant to ignore. When I suggest against its use, I often hear responses like, "Well, I'm only using it in this one place" or "This table will never get large." My issue with this logic is that it's very hard to draw the line, and it's very easy for your peers to "learn" that the practice is okay, and in turn use it in other scenarios where it won't be okay.

One other downside to FORMAT – if, like me, you are playing with using Azure SQL Edge for local development, this is one of the features not supported there (see this previous tip for some other differences).

Next Steps

See these tips and other resources:



sql server categories

sql server webinars

subscribe to mssqltips

sql server tutorials

sql server white papers

next tip



About the author
MSSQLTips author Aaron Bertrand Aaron Bertrand (@AaronBertrand) is a passionate technologist with industry experience dating back to Classic ASP and SQL Server 6.5. He is editor-in-chief of the performance-related blog, SQLPerformance.com, and also blogs at sqlblog.org.

This author pledges the content of this article is based on professional experience and not AI generated.

View all my tips


Article Last Updated: 2022-02-16

Comments For This Article




Thursday, September 29, 2022 - 1:22:04 PM - Raphael Ferreira Back To Top (90535)
Thank you for the excellent article. To be honest with you, it may have just been that, by chance and luck, I learned CONVERT() before I learned FORMAT(), but I much prefer convert when coding. I am used to it, and I find it's syntax actually MORE intuitive than format. I'm quite used to sometimes having to search for a specific value for the last parameter/argument, and the minute or so that it takes me to find that number out does not bother me at all. Most of the time I use 120, or 23, which I do have memorized, and not too often 1 or 101, also memorized. I find the syntax (<ToWhatFormat>(DataType),<Element>(datetime field/parameter/value/attribute/etc.),<Style>) much simpler than format's convoluted "format" argument that ties into "a valid .NET Framework format string", let alone the "culture" argument... It's also a factor that I deal 99% with T-SQL only, and hardly ever touch any programming languages, so, my take on this may have less "vice" than for other people's who are used to programming with these .NET Framework format strings.

Anyways. Great article. Thank you! Best, Raphael

Wednesday, February 16, 2022 - 11:25:10 AM - Jimbo99 Back To Top (89797)
I usually postpone formatting until the queries have summarized the detail records or as close to the final report to format anything. Virtual (Magic/Temporary) tables of smaller data sets. That way whatever indexes exist are still efficient & I'm not formatting the entire database date field.














get free sql tips
agree to terms