{"id":105151,"date":"2025-02-12T03:15:58","date_gmt":"2025-02-12T03:15:58","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=105151"},"modified":"2025-02-11T02:32:18","modified_gmt":"2025-02-11T02:32:18","slug":"exploring-scalar-solutions-to-complex-data-math","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/exploring-scalar-solutions-to-complex-data-math\/","title":{"rendered":"Exploring Scalar Solutions to Complex Data Math"},"content":{"rendered":"\n<p>There are many functions and tools available to database professionals that can solve data math challenges, regardless of complexity. A recent problem came across my desk that called for not only a valid solution, but one that performs as optimally as possible when included in a scalar User-Defined-Function (UDF).<\/p>\n\n\n\n<p>This left me asking: How often these problems impact us and what general solutions can we find that may solve them more easily for us?<\/p>\n\n\n\n<p>The impetus for this was a simple question, \u201cHow many instances of days occur between two dates?\u201d. For example: \u201cHow many Mondays were there between 1\/1\/2024 and 6\/17\/2024?\u201d Or: \u201cHow many Tuesdays and Thursdays are there in total from 9\/2\/2022 and 9\/1\/2025?\u201d<\/p>\n\n\n\n<p>This sounds super-simple at first, but as I dove in for a universal solution, I found myself coming back to either iteration, calendar tables, or exceptionally long SQL that replaced iteration with copies of the same code over-and-over. There are no built-in functions in SQL Server that do what I wanted.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-notes-on-datefirst-and-sql-server-defaults\">Notes on DATEFIRST and SQL Server Defaults<\/h2>\n\n\n\n<p>SQL Server by default assigns a number to each day of the week that can be used in date math and identification. The defaults are as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"845\" height=\"309\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-1.jpeg\" alt=\"\" class=\"wp-image-105152\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-1.jpeg 845w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-1-300x110.jpeg 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-1-768x281.jpeg 768w\" sizes=\"auto, (max-width: 845px) 100vw, 845px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>This means that the default in SQL Server is for Sunday to be the first day of the week. Any date math that identifies the day of the week will return \u201c1\u201d for the day corresponding to Sunday, like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT DATEPART(DW, '12\/31\/2023') AS DayOfWeekResult;<\/pre>\n\n\n\n<p>12\/31\/2023 was a Sunday, so the results are as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"158\" height=\"73\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-2.jpeg\" alt=\"\" class=\"wp-image-105153\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Most developers will go along with this convention as it is the default and easy enough to accommodate with consistent code. The default can be changed at the connection level, using <code>SET DATEFIRST<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SET DATEFIRST 1;<\/pre>\n\n\n\n<p>For my session, the first day of the week is now Monday, and the day of the week for 12\/31\/2023 is now going to be a different value:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"165\" height=\"82\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-3.jpeg\" alt=\"\" class=\"wp-image-105154\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Sunday is defined as the last day of the week and therefore is indicated with a day-of-week of 7. Keep in mind that this setting only applies to a given session and all other processes in SQL Server will continue to return results as if <code>SET DATEFIRST<\/code> were never invoked.<\/p>\n\n\n\n<p>Maintaining the defaults is far easier than changing the first day of the week in SQL Server. Regardless of defaults, this article will maintain Sunday as the first day of the week and not change this as part of the problem-solving done here. Before continuing, let\u2019s return this setting to the default, to prevent any potential confusion later on:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SET DATEFIRST 7;<\/pre>\n\n\n\n<p><em>Note that the code in this article assumes that this default is enabled. If your SQL Server has another default, or if defaults are adjusted on a session-by-session basis, then the days of the week used here will also adjust accordingly.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-solutions-that-do-not-perform-well-enough\">Solutions That Do Not Perform Well Enough<\/h2>\n\n\n\n<p>In this section I will cover some of the solutions that work, often seemly fast enough, but for dealing with lot of rows, they start slowing down the output to a level that is not desirable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-simple-iteration\">Simple Iteration<\/h3>\n\n\n\n<p>Iteration is the easiest solution to the problem. Consider the first example question: \u201cHow many Mondays were there between 1\/1\/2024 and 6\/17\/2024?\u201d We can set a variable equal to the start date and iterate through every date between it and the end date, counting instances of Mondays along the way, like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @StartDate DATE = '1\/1\/2024';\nDECLARE @EndDate DATE = '6\/17\/2024';\nDECLARE @CurrentDate DATE = @StartDate;\nDECLARE @Result INT = 0;\nWHILE @CurrentDate &lt;= @EndDate\nBEGIN\n\tIF DATEPART(DW, @CurrentDate) = 2\n\tBEGIN\n\t\tSELECT @Result = @Result + 1;\n\tEND\n\tSELECT @CurrentDate = DATEADD(DAY, 1, @CurrentDate);\nEND\nSELECT @Result;<\/pre>\n\n\n\n<p>Nothing fancy here \u2013 we get the result by iterating 168 times and done. The wider the date range, the more effort is needed to solve for the result. If joined into a table and directly applied to a column, then the effort would increase based both on row counts and date ranges.<\/p>\n\n\n\n<p>Of course, when you execute this directly, any slowness is imperceptible. But when you try to use this function with large datasets, the iterations become more and more noticeable. In this article I won\u2019t delve into the testing of the functions, but it is important to consider the amount of data your code may be used with and test with as much more than that that you can.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-tactical-iteration\">Tactical Iteration<\/h3>\n\n\n\n<p>An alternative that is less painful, but also quite methodical is to iterate by weeks prior to iterating by days. We know that for each 7-day span, each day of the week will occur once.<\/p>\n\n\n\n<p>Therefore, iteration can be accomplished via incrementing by weeks, until within seven days of the @EndDate. This solution can be implemented using the following T-SQL:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DECLARE @StartDate DATE = '1\/1\/2024';\nDECLARE @EndDate DATE = '6\/17\/2024';\nDECLARE @CurrentDate DATE = @StartDate;\nDECLARE @Result INT = 0;\n--get the number of full weeks past\nWHILE DATEADD(DAY, 7, @CurrentDate) &lt;= @EndDate\nBEGIN\n    SELECT @Result = @Result + 1;\n    SELECT @CurrentDate = DATEADD(DAY, 7, @CurrentDate);\nEND\n--then see if there is a Monday in the remainder of the week.\nWHILE @CurrentDate &lt;= @EndDate\nBEGIN\n    IF DATEPART(DW, @CurrentDate) = 2\n    BEGIN\n        SELECT @Result = @Result + 1;\n    END\n    SELECT @CurrentDate = DATEADD(DAY, 1, @CurrentDate);\nEND\nSELECT @Result;<\/pre>\n\n\n\n<p>This solution has two <code>WHILE<\/code> blocks. The first one loops by increments of seven days, adding one to the result each tie. The second <code>WHILE<\/code> loop performs the same logic as the previous solution, iterating the remainder of days until reaching <code>@EndDate<\/code>. While this iterates less, it\u2019s more complex and still relies on <code>WHILE<\/code> loops that have to iterate some number of times each to return a result.<\/p>\n\n\n\n<p>It is useful to be reminded that any solution built here is likely to be encapsulated into a scalar function. Once converted to a function, it can be embedded into any part of a query, including aggregation, filtering, and ordering. Therefore, any performance challenges identified, even if minor, will be magnified when applied to any part of a query against data of potentially any size!<\/p>\n\n\n\n<p>The following code is the natural extension of what was done here:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE FUNCTION dbo.ReturnDaysBetweenDates_Iteration\n    (@StartDate DATE, @EndDate DATE)\nRETURNS INT\nAS\nBEGIN\n    DECLARE @CurrentDate DATE = @StartDate;\n    DECLARE @Result INT = 0;\n    WHILE @CurrentDate &lt;= @EndDate\n    BEGIN\n        IF DATEPART(DW, @CurrentDate) = 2\n        BEGIN\n            SELECT @Result = @Result + 1;\n        END\n        SELECT @CurrentDate = DATEADD(DAY, 1, @CurrentDate);\n    END\n    RETURN @Result;\nEND<\/pre>\n\n\n\n<p>Once wrapped into a function, it may be reused anywhere, including inside additional functions, stored procedures, or views.<\/p>\n\n\n\n<p>While reusing optimal object can result in great savings, reusing suboptimal code can causes inefficiencies being multiplied exponentially, which for the sake of performance can be absolutely crushing!<\/p>\n\n\n\n<p>For this reason, maximizing efficiency is quite important here, so it is important to test, and in some cases, looking for even more efficient, if not as straightforward solutions that will work fast enough when used in any situation! In this case, needing to iterate was deemed to be a suboptimal solution, so I continued at it looking for a better solution.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-solving-it-all-at-once\">Solving it All-at-Once<\/h2>\n\n\n\n<p>The key to solving this date problem lies in the fact that the effort needed to compute the answer does not need to be linear to the time frame analyzed.<\/p>\n\n\n\n<p>When solving this sort of issues, it can be a good idea to look at the problem from a bit higher level. While you should always be careful to not extend the scope too far, sometimes a general solution can also be the better solution.<\/p>\n\n\n\n<p>In this case, it was determined that it would also be ideal to expand the solution to allow for calculating day-of-the-week counts for any individual day or combination of days. Weekdays are a common data request, but different fields will require other combinations of days based on differing business models. For the demonstration here, the example of solving for Monday will be completed first, and then a more complex scenario involving multiple days.<\/p>\n\n\n\n<p>Calculating days of the week over a one-hundred-year span should not require more effort than over a one-month span. A visualization can help in seeing a better solution:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1183\" height=\"261\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall.png\" alt=\"A close-up of a calendar\n\nDescription automatically generated\" class=\"wp-image-105155\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall.png 1183w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-300x66.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1024x226.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-768x169.png 768w\" sizes=\"auto, (max-width: 1183px) 100vw, 1183px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In this example, the start date for the date range is on a Tuesday and the <code>@EndDate<\/code> is on a Friday, 2 weeks later. To calculate the number of Mondays in this time frame requires 2 bits of math:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>Count the number of 7-day spans in the range (weeks)<\/li>\n\n\n\n<li>Count the remaining days.<\/li>\n<\/ol>\n<\/div>\n\n\n<p>This was accomplished earlier via iteration, but by visualizing the problem, it becomes clear that there are 2 calculations that need to be completed. The first is trivial: take the number of days in the date range and divide by seven. The result provides the first part of the solution:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1180\" height=\"262\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1.png\" alt=\"A close-up of a calendar\n\nDescription automatically generated\" class=\"wp-image-105156\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1.png 1180w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1-300x67.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1-1024x227.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-close-up-of-a-calendar-description-automaticall-1-768x171.png 768w\" sizes=\"auto, (max-width: 1180px) 100vw, 1180px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The remaining challenge is to count the extra days at the end of the date range, which will be a number from 0-6. Visually, it is easy to see how many days remain and which days of the week they are, but how can this programmatically be calculated? The number of days and which days of the week are represented are both required to solve this problem, without any human\/visual inspection.<\/p>\n\n\n\n<p>The first and simplest scenario is one where the number of days in the date range is an even multiple of seven:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1186\" height=\"263\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica.png\" alt=\"A screenshot of a calendar\n\nDescription automatically generated\" class=\"wp-image-105157\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica.png 1186w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-300x67.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1024x227.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-768x170.png 768w\" sizes=\"auto, (max-width: 1186px) 100vw, 1186px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In this scenario, the number of Mondays will always be the count of days in the date range divided by seven:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT (DATEDIFF(DAY, @StartDate, @EndDate) + 1) \/ 7;<\/pre>\n\n\n\n<p>This can also be simplified to:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT DATEDIFF(WEEK, @StartDate, @EndDate) \/ 7;<\/pre>\n\n\n\n<p>The other two scenarios are:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>The starting day of the week is less than or equal to the ending day of the week.<\/li>\n\n\n\n<li>The starting day of the week is greater than the ending day of the week.<\/li>\n<\/ol>\n<\/div>\n\n\n<p>For each of these, the count of full weeks is computed as it was above and 1 is added to it if the remaining days happen to include a Monday.<\/p>\n\n\n\n<p>Scenario #1 looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1178\" height=\"259\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio.png\" alt=\"A calendar with a few days of the week\n\nDescription automatically generated with medium confidence\" class=\"wp-image-105158\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio.png 1178w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-300x66.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1024x225.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-768x169.png 768w\" sizes=\"auto, (max-width: 1178px) 100vw, 1178px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>If the start day (2) is less than or equal to the end day (5), which it is, then whether a Monday exists in that range depends solely on if the start day is less than or equal to 1 or not. Since it is, we can add one and the total number of Mondays is 3, and given by the following T-SQL:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT @MondayCount = DATEDIFF(WEEK, @StartDate, @EndDate) \/ 7 +\nCASE WHEN @DayOfWeekStart &gt; 1 THEN 0 ELSE 1 END;<\/pre>\n\n\n\n<p>Alternatively, if the start day (2) is greater than the end day (5), then the extra Monday would not have been added on, and the count would have been two, rather than three.<\/p>\n\n\n\n<p>There are two more problems to solve here:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>A generic solution for any day of the week<\/li>\n\n\n\n<li>A generic solution for any combination of days of the week<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The first is an extension of the solution tackled above. Instead of assuming that Monday is the day to check for, that day becomes a variable that we can call <code>@DayToCheck<\/code>. For example, if the <code>@DayToCheck<\/code> was Saturday, then the visualization would look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1182\" height=\"255\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1.png\" alt=\"A calendar with a few days of the week\n\nDescription automatically generated with medium confidence\" class=\"wp-image-105159\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1.png 1182w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1-300x65.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1-1024x221.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-calendar-with-a-few-days-of-the-week-descriptio-1-768x166.png 768w\" sizes=\"auto, (max-width: 1182px) 100vw, 1182px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>With the Monday assumption gone, the resulting math becomes this:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>Number of full 7-day weeks starting with <code>@StartDate<\/code><\/li>\n\n\n\n<li>If the remainder days include <code>@DayToCheck<\/code>, then add 1.<\/li>\n<\/ol>\n<\/div>\n\n\n<p>The one mathematical issue to solve first is the fact that the start date and end date can be before or after each other. The simplest way to handle this situation is to iterate through each additional day\u2026but\u2026we are trying our best to avoid iteration here, so that option will be discarded.<\/p>\n\n\n\n<p>To solve this problem in a way that can also be applied to a scenario where we want to check any number of days-of-the-week, we will consider a bitmap-based solution. Imagine that there is a 7-bit bitmap that indicates the days we are searching for:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1181\" height=\"71\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-9.png\" alt=\"\" class=\"wp-image-105160\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-9.png 1181w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-9-300x18.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-9-1024x62.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-9-768x46.png 768w\" sizes=\"auto, (max-width: 1181px) 100vw, 1181px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The days are backwards so that the values will appear forwards. In other words, the first bit (2<sup>0<\/sup>) is Sunday, the second bit (2<sup>1<\/sup>) is Monday, and so on. If the goal is to validate how many Saturdays are in a date span, then the bitmap representation for this input would be:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1183\" height=\"74\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-10.png\" alt=\"\" class=\"wp-image-105161\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-10.png 1183w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-10-300x19.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-10-1024x64.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-10-768x48.png 768w\" sizes=\"auto, (max-width: 1183px) 100vw, 1183px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The binary number 1000000 is equal to decimal 64.<\/p>\n\n\n\n<p>Why use a bitmap? Isn\u2019t that complex and hard to understand? It certainly isn\u2019t as simple as the iteration code was, but the next step will help make this a bit clearer.<\/p>\n\n\n\n<p>Consider how the day count is calculated: We start with the number of full 7-day weeks in the date span and then add to it depending on what the remainder days contain. Therefore, we can also create a bitmap for the remainder that indicates the days within it.<\/p>\n\n\n\n<p>Consider the visual representation of the problem that we are tackling here:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1181\" height=\"259\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1.png\" alt=\"A screenshot of a calendar\n\nDescription automatically generated\" class=\"wp-image-105162\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1.png 1181w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1-300x66.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1-1024x225.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/a-screenshot-of-a-calendar-description-automatica-1-768x168.png 768w\" sizes=\"auto, (max-width: 1181px) 100vw, 1181px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>There are 2 complete weeks of 7 days, starting from @StartDate, which is a Monday. There are then four leftover days. These remainder days can be summarized in a bitmap as well:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1182\" height=\"137\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-12.png\" alt=\"\" class=\"wp-image-105163\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-12.png 1182w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-12-300x35.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-12-1024x119.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-12-768x89.png 768w\" sizes=\"auto, (max-width: 1182px) 100vw, 1182px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The resulting number in binary is 11110, or 30 in decimal.<\/p>\n\n\n\n<p>With these two bitmaps, the real magic trick can be performed: Overlap the two binary numbers and perform a bitwise AND against them:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1217\" height=\"53\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-13.png\" alt=\"\" class=\"wp-image-105164\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-13.png 1217w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-13-300x13.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-13-1024x45.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-13-768x33.png 768w\" sizes=\"auto, (max-width: 1217px) 100vw, 1217px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>By comparing the days-to-be-checked with the remainder, any overlap can be calculated using the single operation above. In this scenario, the lack of overlap confirms that the date range provided does not contain any extra Saturdays within the remainder days. As a huge bonus, this process works for any combination of days-to-check and any combination of remainder days.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-creating-the-function-in-t-sql\">Creating the Function in T-SQL<\/h3>\n\n\n\n<p>With the binary math out of the way, a solution can be built in T-SQL that accomplishes this task for all possible inputs. The following function accepts a start date, end date, and a bit for each day of the week and will return the total number of times the days-to-check appear within the date range:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE FUNCTION dbo.DaysWithinDateRangeCount\n(   @StartDate DATE,\n    @EndDate DATE,\n    --pick as many days of the week as you wish to count\n    @Monday BIT,\n    @Tuesday BIT,\n    @Wednesday BIT,\n    @Thursday BIT,\n    @Friday BIT,\n    @Saturday BIT,\n    @Sunday BIT)\nRETURNS INTEGER\nAS\nBEGIN\n       --set up the bitmap for the days in the parameters.\n    DECLARE @DayBitmap INT = CAST(@Saturday AS INT) * 64 +\n                                CAST(@Friday AS INT) * 32 + \n                                CAST(@Thursday AS INT) * 16 +\n                                CAST(@Wednesday AS INT) * 8 +\n                                CAST(@Tuesday AS INT) * 4 + \n                                CAST(@Monday AS INT) * 2 + \n                                CAST(@Sunday AS INT) * 1;\n\n    -- Total days in the search range.\n    DECLARE @DayCountTotal INT = DATEDIFF(DAY, @startDate, @endDate) + 1; \n\n    -- Total number of complete weeks in the search range.\n    DECLARE @WeekCountFull INT = @DayCountTotal \/ 7; \n\n    -- Additional days leftover in the search range.\n    DECLARE @Remainder INT = @DayCountTotal % 7; \n\n    -- Number of total days to check within the search range.\n    DECLARE @DaysToCheckCount INT = BIT_COUNT(@DayBitmap); \n\n    -- The day of week for the last day in the search range.\n    DECLARE @LastDay INT = DATEPART(WEEKDAY, @endDate); \n\n    DECLARE @RemainderBitMap INT =\n        CASE WHEN @remainder = 0 THEN 0\n         WHEN @remainder = 1 THEN POWER(2, @LastDay - 1)\n         WHEN @remainder = 2 THEN POWER(2, @LastDay - 1) \n                     + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2))\n         WHEN @remainder = 3 THEN POWER(2, @LastDay - 1) \n                     + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2)) \n                     + POWER(2, IIF(@LastDay - 2 &lt; 1, @LastDay + 4, @LastDay - 3))\n         WHEN @remainder = 4 THEN POWER(2, @LastDay - 1) \n                     + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2)) \n                     + POWER(2, IIF(@LastDay - 2 &lt; 1, @LastDay + 4, @LastDay - 3)) \n                     + POWER(2, IIF(@LastDay - 3 &lt; 1, @LastDay + 3, @LastDay - 4))\n         WHEN @remainder = 5 THEN POWER(2, @LastDay - 1) \n                     + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2)) \n                     + POWER(2, IIF(@LastDay - 2 &lt; 1, @LastDay + 4, @LastDay - 3)) \n                     + POWER(2, IIF(@LastDay - 3 &lt; 1, @LastDay + 3, @LastDay - 4)) \n                     + POWER(2, IIF(@LastDay - 4 &lt; 1, @LastDay + 2, @LastDay - 5))\n         WHEN @remainder = 6 THEN POWER(2, @LastDay - 1) \n                     + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2)) \n                     + POWER(2, IIF(@LastDay - 2 &lt; 1, @LastDay + 4, @LastDay - 3)) \n                     + POWER(2, IIF(@LastDay - 3 &lt; 1, @LastDay + 3, @LastDay - 4)) \n                     + POWER(2, IIF(@LastDay - 4 &lt; 1, @LastDay + 2, @LastDay - 5)) \n                     + POWER(2, IIF(@LastDay - 5 &lt; 1, @LastDay + 1, @LastDay - 6))\n               --no remainder = 7, because that is a full week already\n    END\n    RETURN (@WeekCountFull * @DaysToCheckCount) \n                        + BIT_COUNT(@RemainderBitMap &amp; @DayBitmap);\nEND;\n<\/pre><\/div>\n\n\n\n<p><em>Note: this function will only work as is in SQL Server 2022 due to the use of the BIT_COUNT function. In <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-bit_count-and-an-alternative\/\">this article<\/a>, there is a function that you can use to replace the BIT_COUNT function if you are using a previous version of SQL Server.<\/em><\/p>\n\n\n\n<p>The bitwise math may seem a bit dizzying (and it wasn\u2019t simple to write!), but it is the same basic math repeated over-and-over. The steps taken in the function are as follows:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>Create a bitmap for the days that are to be checked for.<\/li>\n\n\n\n<li>Assign variables that count days, weeks, the remainder, and the day of the last date in the date range.<\/li>\n\n\n\n<li>Create the bitmap for the remainder days.<\/li>\n\n\n\n<li>Return the result, which is calculated as:<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>The number of full 7-day weeks times the number of days to check, plus<\/li>\n\n\n\n<li>The count of bits left from the bitwise AND of the remainder and days-to-check.<\/li>\n<\/ol>\n<\/div><\/li>\n<\/ol>\n<\/div>\n\n\n<p>Whereas <code>@DayBitMap<\/code> is somewhat easy to calculate, as the days of the week to check for are entered explicitly as input variables to the stored procedure, <code>@RemainderBitMap<\/code> does require a bit more logic.<\/p>\n\n\n\n<p>The complexity is in taking a number and determining the remainder (base 7) and converting that into an identically formatted day-of-week bitmap that can be compared directly to @DayBitMap. A bitmap is just a sequence of bits, each that represent a different power of 2, all added up.<\/p>\n\n\n\n<p>For example, if the remainder were 3, then there are three leftover days that need to be mapped to their corresponding days-of-the-week:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">         WHEN @remainder = 3 THEN POWER(2, @LastDay - 1) \n                + POWER(2, IIF(@LastDay = 1, 6, @LastDay - 2)) \n                + POWER(2, IIF(@LastDay - 2 &lt; 1, @LastDay + 4, \n                                                @LastDay - 3))<\/pre>\n\n\n\n<p>The embedded <code>IIF<\/code> conditional is used to check for when the day of week wraps around from 7 back to 1. This ensures that we do not continue counting days of the week 8, 9, 10, etc&#8230;and instead wrap around from Saturday (last day of the week) back to Sunday (first day of the week). The <code>POWER<\/code> function raises each subsequent power of 2 based on the day of week, adding the results to generate the desired bitmap.<\/p>\n\n\n\n<p>The function can easily be tested for the example above:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT dbo.DaysWithinDateRangeCount('12\/2\/2024', '12\/19\/2024', 0, 0, 0, 0, 0, 1, 0) \n                                                                          AS Result;<\/pre>\n\n\n\n<p>The result comes back with the expected value:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"153\" height=\"71\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-14.jpeg\" alt=\"\" class=\"wp-image-105165\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-14.jpeg 153w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-14-150x71.jpeg 150w\" sizes=\"auto, (max-width: 153px) 100vw, 153px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Note too that executing the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT dbo.DaysWithinDateRangeCount('1\/1\/2024', '6\/17\/2024', 1, 0, 0, 0, 0, 0, 0) \n                                                                        AS Result;<\/pre>\n\n\n\n<p>Will return the same 25 instances as the original example.<\/p>\n\n\n\n<p>Consider the same date range, but we would like to check for Monday, Wednesday, and Friday. This time, the days to check are as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1180\" height=\"141\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-15.png\" alt=\"\" class=\"wp-image-105166\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-15.png 1180w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-15-300x36.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-15-1024x122.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-15-768x92.png 768w\" sizes=\"auto, (max-width: 1180px) 100vw, 1180px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The remainder can be expressed as this binary value (same as before):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1183\" height=\"140\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-16.png\" alt=\"\" class=\"wp-image-105167\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-16.png 1183w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-16-300x36.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-16-1024x121.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-16-768x91.png 768w\" sizes=\"auto, (max-width: 1183px) 100vw, 1183px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The bitwise AND of these numbers is <code>0101010 &amp; 0011110 = 0001010<\/code><\/p>\n\n\n\n<p>The result for days between each date is:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>3 days to check times 2 full weeks = 6 days<\/li>\n\n\n\n<li>Add to this the 2 overlapping days within the remainder (from the bitwise math above)<\/li>\n\n\n\n<li>The result is 8<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The function can be tested to validate this answer:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT dbo.DaysWithinDateRangeCount('12\/2\/2024', '12\/19\/2024', 1, 0, 1, 0, 1, 0, 0) \n                                                                         AS Result;<\/pre>\n\n\n\n<p>The result returned is:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"157\" height=\"71\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-17.jpeg\" alt=\"\" class=\"wp-image-105168\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>From here, an arbitrarily random complex problem can be solved, such as occurrences of Sunday and Wednesday from 1\/17\/2010 through 12\/17\/2025:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT dbo.DaysWithinDateRangeCount('1\/17\/2010', '12\/17\/2025', 0, 0, 1, 0, 0, 0, 1) \n                                                                          AS Result;<\/pre>\n\n\n\n<p>The result that is returned is:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"153\" height=\"71\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-18.jpeg\" alt=\"\" class=\"wp-image-105169\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-18.jpeg 153w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/01\/word-image-105151-18-150x71.jpeg 150w\" sizes=\"auto, (max-width: 153px) 100vw, 153px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>We can (for fun of course!) confirm this result manually:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>1\/17\/2010 is a Sunday.<\/li>\n\n\n\n<li>12\/17\/2025 is a Wednesday.<\/li>\n\n\n\n<li>There are 830 full weeks from the start date to the end date.<\/li>\n\n\n\n<li>There are 4 remainder days: Sunday, Monday, Tuesday, and Wednesday.<\/li>\n\n\n\n<li>The result is given by 830 * 2 + 2 = 1662<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Without even executing this code, you should immediately be able to see that it will execute far faster than the looping code because it is all simple scalar math operations. No matter the distance from start to finish, and no matter which days you are counting, it will be as fast for very short distances between dates or far faster when the date values are farther apart.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>There are many possible ways to solve the problem presented in this article. There are also many other date-math problems that can be solved in similar ways. The goal was to think a bit creatively (and mathematically) and consider other ways to get the correct result with fewer iterative steps.<\/p>\n\n\n\n<p>The beauty of re-solving a problem in this manner is that the computing power to execute this function is similar, regardless of the parameters chosen. Other common solutions to a problem like this are iterative or contain many steps, increasing their cost.<\/p>\n\n\n\n<p>The added fun is in thinking up something new and different. This strategy can most certainly be used elsewhere in coding to solve similar problems where an overlap of data sets needs to be evaluated.<\/p>\n\n\n\n<p>Have you encountered any other problems like this &#8211; where creative, unusual, or interesting solutions saved the day?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are many functions and tools available to database professionals that can solve data math challenges, regardless of complexity. A recent problem came across my desk that called for not only a valid solution, but one that performs as optimally as possible when included in a scalar User-Defined-Function (UDF). This left me asking: How often&#8230;&hellip;<\/p>\n","protected":false},"author":329827,"featured_media":105172,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,143531],"tags":[4151],"coauthors":[101655],"class_list":["post-105151","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-featured","category-t-sql-programming-sql-server","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105151","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/329827"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=105151"}],"version-history":[{"count":14,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105151\/revisions"}],"predecessor-version":[{"id":105280,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/105151\/revisions\/105280"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/105172"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=105151"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=105151"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=105151"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=105151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}