0

I cannot figure out where I've messed this up. Full code:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

INSERT INTO @Table 
    SELECT 
        MIN(dt), MAX(dt), w
    FROM
        (SELECT 
             dt, year(dt) y, DATEPART(week, dt) w
         FROM
             (SELECT 
                  @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
              FROM 
                  sys.columns s1 
              CROSS JOIN
                  sys.columns s2) q
         WHERE 
             dt BETWEEN @start_date AND @end_date) a
GROUP BY 
    y, w

I've narrowed down where the error is happening. It happens here:

SELECT 
    dt, year(dt) y, DATEPART(week, dt) w
FROM
    (SELECT 
         @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
     FROM 
         sys.columns s1 
     CROSS JOIN
         sys.columns s2) q
WHERE 
    dt BETWEEN @start_date AND @end_date

If I run just this part I get the error:

Arithmetic overflow error converting expression to data type datetime.

I've tried adding in some CONVERT here and there trying to find where it's messing up, but I can not figure it out.

I have seen a couple of others who had a similar problem, but neither this one or this one have any solution that is helpful to me.

I'm new to the set based SQL programming, I'm self-taught in loops so this is out of my depth. Any help is appreciated.

9
  • do you expect @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) to add a day? Commented Sep 22, 2017 at 19:21
  • @scsimon it does. If I run just that inner select I get a list of every day of the year starting at the @Start_Date and continuing till I stop the query. Commented Sep 22, 2017 at 19:22
  • 2
    I know what it does, i was asking if you expect that. It doesn't work on date but will work on datetime but i'd suggest sticking to dateadd() for explicit reasons and code management. Many people misuse this. Commented Sep 22, 2017 at 19:23
  • 1
    i ran your code, and gave a link to an online version above, so with that snippet it isn't reproducible. The syntaxt would be dateadd(day, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1),@start_date) since you need the RN from the table it seems Commented Sep 22, 2017 at 19:41
  • 1
    Try and test the different portions of it. Do a separate select statement for each of the parts of @start_date + DATEADD(day, 1, @start_date. Obviously, the first part will just display whatever @start_date is, but what does the second portion do? You are trying to add a day to it and then add it back to itself currently. Also, you know where the issue is, and you also know where it is not (inside the select statement). Get rid of the where clause, does that get rid of it? No? Replace the select with just select *. How about now? Code is small so get rid of or isolate to find it. Commented Sep 22, 2017 at 19:43

1 Answer 1

0

ROW_NUMBER() returns a bigint value but adding days to a date is limited to int; so if the number of rows produced by the cross join exceeds the maximum value of an int you will produce that error message.

The number of rows in sys.columns only needs to exceed the square root of 2,147,483,647 (>=46,341) for the Cartesian product used in your code to produce that error. I was able to reproduce the same error on a much smaller sys.columns table by repeating the cross joins. e.g:

  SELECT 
      @start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
  FROM 
      sys.columns s1 
  CROSS JOIN
      sys.columns s2
  CROSS JOIN
      sys.columns s3
  CROSS JOIN
      sys.columns s4

One way to avoid the problem would be instead of adding row_number() to a date in the inner query, just return row_number() instead and use the where clause to avoid the error, e.g:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

INSERT INTO @Table 
    SELECT 
        MIN(dt), dateadd(day,7,MIN(dt)), w
    FROM
        (SELECT 
             dateadd(day,q.i,@start_date) dt, year(dateadd(day,q.i,@start_date)) y, DATEPART(week, dateadd(day,q.i,@start_date)) w
         FROM (
             SELECT 
                  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 i
              FROM 
                  sys.columns s1 
              CROSS JOIN
                  sys.columns s2
             ) q
         WHERE 
             i <= datediff(day,@start_date,@end_date) 
         ) q2
GROUP BY 
    y, w
;

Note there are many alternatives, particularly as you are really only want week ranges. You could use a permanent "tally table" instead of sys.columns, or use repeated cross joins of a 10 row CTE to create the wanted rows.

The accepted answer to this question Tally Table to insert missing dates between two dates? SQL is an example of using a permanent table of numbers (aka "tally table"). Also note by doing this you could add 7 days per row thus avoiding the need to group the days into weeks.

Here is an example using a repeated cross join of a CTE:

DECLARE @start_date DATETIME, @end_date DATETIME
DECLARE @Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)

SET @start_date = '2017-01-01'
SET @end_date =   '2017-12-31'

;WITH
cteDigits AS (
    SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
    SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
cteTally AS (
    SELECT [100s].digit * 100 + [10s].digit * 10 + [1s].digit AS i
    FROM cteDigits [1s]
    CROSS JOIN cteDigits [10s]
    CROSS JOIN cteDigits [100s]
)
SELECT
      dateadd(day,q.i*7,@start_date) startDate
    , dateadd(day,(q.i*7)+7,@start_date) endDate
    , year(dateadd(day,q.i*7,@start_date)) y
    , DATEPART(week, dateadd(day,q.i*7,@start_date)) w

FROM cteTally q
WHERE dateadd(day,(q.i*7)+7,@start_date) <= @end_date
ORDER BY q.i
Sign up to request clarification or add additional context in comments.

1 Comment

Could the downvoter please identify what I have failed to explain? The question is why did they get a specific error message. The cause of that error message is that the Cartesian product produced by the original query is larger than the maximum size of an int in MSSQL. This is easy to reproduce. I have also identified a couple of ways to avoid the problem. No sure what I have missed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.