SQL Error in Casting - sql

I am trying to join date and time fields in order to select the latest status at a certain point in time using the following code...
select max(CONVERT(DATETIME, S.Start_date, 108)
+ ISNULL(STUFF(case when len(S.start_time) < 4 then '0' + S.start_time else S.start_time end,3,0,':'),'23:59') as LATST
, computer_number
, event_key
, exam_key
from CRIS_status S
where cast(Start_date as datetime) <= '17 Jul 2016 23:59:59'
group by computer_number, event_key, exam_key
and i get the following error messgae Msg 242, Level 16, State 3, Line 1...

You are checking Datetime with a Varchar which probably gives error
Try with this...
select max(CONVERT(DATETIME, S.Start_date, 108)
+ ISNULL(STUFF(case when len(S.start_time) < 4 then '0' + S.start_time else S.start_time end,3,0,':'),'23:59') as LATST
, computer_number
, event_key
, exam_key
from CRIS_status S
where cast(Start_date as datetime) <= cast('17 Jul 2016 23:59:59' as datetime)
group by computer_number, event_key, exam_key

Always use unambiguous date format
select max(CONVERT(DATETIME, S.Start_date, 108)
+ ISNULL(STUFF(case when len(S.start_time) < 4 then '0' + S.start_time else S.start_time end,3,0,':')
,'23:59') as LATST
, computer_number
, event_key
, exam_key
from CRIS_status S
where cast(Start_date as datetime) < '20160718'
group by computer_number, event_key, exam_key

Related

Get sum of qty based on hour window

I'm working on a problem to figure out how much qty of product was created and dispatched between certain hours. For example, I need to see how much was created (by created I mean how many orders were created with X qty) between 6pm today and 3pm tomorrow. I'm trying to create a time bin for this but whatever I try isn't working out.
select CREATE_DATE
, CREATE_TIME
, RELEASED_DATE
, RELEASED_TIME
, sum(case
when CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 0)
and CREATE_TIME >= '18:00:00' AND CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 1)
then ORDER_QTY
when CREATE_DATE = DATEADD(DAY, DATEDIFF(DAY, 0, CREATE_DATE), 1)
and CREATE_TIME <= '14:59:59'
then ORDER_QTY
end) as small_window_qty
, sum(ORDER_QTY) as ord_qty
, sum(RELEASED_QTY) as rls_qty
from table
Any help with this would be appreciated. Just need a way to organize the days into the following buckets: Normal Hour Window = Created 6pm to 6pm; Smaller Hour Window = Created 6pm to 3pm; Agreed Upon = Dispatch by 3pm (12am to 3pm)
Edit: Some clarification. What im trying to accomplish is a root cause analysis. We have orders that create every day, and must ship within 2 days of being created. We're trying to figure out why our orders are not shipping on time. So as an RCA, Im trying to dig into the orders, when they were created, when they were dispatched(or released, same thing) and when they shipped. The Hour Window's mentioned above are cutoff times for orders to be created for a certain day. Example:
300 units were created today, and they must ship 2 days from now. I want to see, of that 300 created, how many were created before 3pm, and of that, how much dispatched by 3pm same day. Hope that clarifies things. Not everything that is created must be dispatched the same day, as we have 2 days to ship the orders.
you're leaving a lot of info out so i'm filling in the blanks and making assumptions here. doublecheck that my data types line up with your data types.
create table sales (create_date date, create_time time, released_date date, released_time time, qty int)
insert sales
select '1/14/13','18:45','1/15/13','10:45', 10
union all
select '1/14/13','19:45','1/15/13','12:45', 12
union all
select '1/15/13','19:15','1/16/13','16:45', 6
union all
select '1/15/13','18:00','1/16/13','14:45', 25
union all
select '1/15/13','18:45','1/16/13','15:00', 3
union all
select '1/16/13','19:45','1/17/13','16:30', 1
union all
select '1/16/13','20:45','1/17/13','17:45', 9
union all
select '1/17/13','18:30','1/18/13','18:00', 17
union all
select '1/18/13','18:30','1/19/13','17:15', 15
union all
select '1/18/13','18:45','1/19/13','19:15', 21
with base as
(
select *
, cast(create_date as datetime) + cast(create_time as datetime) as createtime
, cast(released_date as datetime) + cast(released_time as datetime) as releasetime
, datediff(hour,cast(create_date as datetime) + cast(create_time as datetime),cast(released_date as datetime) + cast(released_time as datetime)) as hrs
from sales
),
base2 as
(
select qty
, case
when create_time >= '18:00' and hrs <= 21 then 'small'
when create_time >= '18:00' and hrs <= 24 then 'normal'
else 'outside'
end as orderwindow
, case
when hrs between 6 and 21 then 'pass'
else 'fail'
end as agreedupon
from base
)
select sum(qty) as qty, orderwindow, agreedupon
from base2
group by orderwindow, agreedupon
drop table sales
this should give you the end result of being able to tell how much was created, what window of time it falls into, and if released by 3pm. adjust as needed.
i didn't want the code to get messy and convoluted so i used 2 CTEs.
I am not sure about your expected result and also your input data looks messay and not clear. So I used the data provided by the above answer. It looks you get running sum. If you can review the answer and get back to me if you need more modifications that is good. I suggest you to edit your Question and be more clear with your sample data and expected answer. Add more details to the question.
create table sales (create_date date, create_time time, released_date date, released_time time, qty int)
INSERT INTO sales
select '1/14/13','18:45','1/15/13','10:45', 10
union all
select '1/14/13','19:45','1/15/13','12:45', 12
union all
select '1/15/13','19:15','1/16/13','16:45', 6
union all
select '1/15/13','18:00','1/16/13','14:45', 25
union all
select '1/15/13','18:45','1/16/13','15:00', 3
union all
select '1/16/13','19:45','1/17/13','16:30', 1
union all
select '1/16/13','20:45','1/17/13','17:45', 9
union all
select '1/17/13','18:30','1/18/13','18:00', 17
union all
select '1/18/13','18:30','1/19/13','17:15', 15
union all
select '1/18/13','18:45','1/19/13','19:15', 21
SELECT
create_date
, create_time
, released_date
, released_time
,SUM(QTY) OVER ( PARTITION BY A.[Dispatched Window] ORDER BY released_date ) AS [Sum_qty]
,Qty
FROM
(
SELECT
create_date
, create_time
, released_date
, released_time
, CASE WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '18:00:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), DATEADD(DAY,1, create_date), 112) + ' ' + CONVERT(CHAR(8), '18:00:00.0000000' , 108)) THEN 'Normal Window'
WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '17:59:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), DATEADD(DAY,1, create_date), 112) + ' ' + CONVERT(CHAR(8), '15:00:00.0000000' , 108)) THEN 'Smaller Window'
WHEN released_date BETWEEN CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '00:00:00.0000000' , 108)) AND CONVERT(DATETIME, CONVERT(CHAR(8), create_date, 112) + ' ' + CONVERT(CHAR(8), '15:00:00.0000000' , 108)) THEN 'Aggreed AUpon'
ELSE 'N/A'
END AS [Dispatched Window]
, Qty
FROM dbo.sales
) AS A

Get OrderTotal by hourly for current date gives subquery error :

Here is my SQL query which presently gives hourly order data for current the date. Here I need to count order by following case when condition, if I add this I am getting sub-query error after 11:00 AM, Please anybody help me out to solve problem.
Following SQL query on which I am performing:
DECLARE #intervalMinutes int = 60
DECLARE #schedule table (
myId int primary key identity,
startTime time,
endTime time
)
DECLARE #startTime time= '12:00:00 AM';
DECLARE #endTime time='11:00:00 PM';
;WITH CTE AS
(
SELECT #startTime st
UNION ALL
SELECT DATEADD(MINUTE,#intervalMinutes,st)
FROM cte
WHERE st < DATEADD(MINUTE,#intervalMinutes,st)
)
INSERT INTO #schedule(startTime,endTime)
SELECT st, DATEADD(MINUTE,#intervalMinutes,st)FROM cte
SELECT CONVERT(varchar(10), startTime, 100) + ' - ' + CONVERT(varchar(10), endTime , 100) AS TimeSlots,
ISNULL((SELECT CASE WHEN ISNULL(O.isAfterDiscount,0) != 0 THEN ((O.SubTotal - Total_Discount) + O.Total_Tax)
ELSE ((O.SubTotal + O.Total_Tax) - Total_Discount)
END AS OrderTotal FROM [dbo].[Order] AS O WHERE O.Order_Status='COMPLETED' AND
(O.CreatedDate >= CAST(CONVERT(varchar(20), GETUTCDATE(),101) + ' ' + CONVERT(varchar(8), startTime, 108) as datetime))
AND (O.CreatedDate <= CAST(CONVERT(varchar(20), GETUTCDATE(),101) + ' ' + CONVERT(varchar(8), REPLACE(endTime,'00:00:00.000','23:59:59.999'), 108) as datetime))),0) AS TotalSales
FROM #schedule
The following error comes while execute query:
Msg 512, Level 16, State 1, Line 22
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
The following query results which I need to display:
Please guide me on this.
It means, you have got many values comes from Order table subquery. You have to apply SUM aggregate function to sum over the sales amount from order table.
SELECT CONVERT(VARCHAR(10), startTime, 100) + ' - ' + CONVERT(VARCHAR(10), endTime, 100) AS TimeSlots
,ISNULL((
SELECT SUM(CASE WHEN ISNULL(O.isAfterDiscount, 0) != 0 THEN ((O.SubTotal - Total_Discount) + O.Total_Tax) ELSE ((O.SubTotal + O.Total_Tax) - Total_Discount) END) AS OrderTotal
FROM [dbo].[Order] AS O
WHERE O.Order_Status = 'COMPLETED'
AND (O.CreatedDate >= CAST(CONVERT(VARCHAR(20), GETUTCDATE(), 101) + ' ' + CONVERT(VARCHAR(8), startTime, 108) AS DATETIME))
AND (O.CreatedDate <= CAST(CONVERT(VARCHAR(20), GETUTCDATE(), 101) + ' ' + CONVERT(VARCHAR(8), REPLACE(endTime, '00:00:00.000', '23:59:59.999'), 108) AS DATETIME))
), 0) AS TotalSales
FROM #schedule

simplify a SQL case statement in a case expression

How would I simplify this case statement in T-SQL? It provides the desired result, but it's very unwieldy and hard to read. I have to use the inner case statement to convert a Julian date (aka 6 digit number) into a regular date format.
Basically i'm doing a datediff( getdate(), case statement). Getdate() just returns the time now (ie. 2/27/2020) and the case statement converts a julian date (ie. 123456) into a normal date (ie, 1/1/2020).
Here's the expect output if the query was ran today on Feb 27.
Select CASE
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) < 0
THEN 'Overdue Now'
WHEN Datediff(day, Getdate(), CASE
WHEN a.wadpl = 0
THEN NULL
ELSE Dateadd(d, Substring(Cast(wadpl AS VARCHAR(6)), 4, 3) - 1, CONVERT(DATETIME, CASE
WHEN LEFT(Cast(wadpl AS VARCHAR(6)), 1) = '1'
THEN '20'
ELSE '21'
END + Substring(Cast(wadpl AS VARCHAR(6)), 2, 2) + '-01-01'))
END) <= 30
THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
Here is a easy one to understand, assuming a.wadpl is an integer:
SELECT CASE
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <0 THEN 'Overdue now'
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(DAY, a.wadpl % 1000, DATEADD(YEAR,a.wadpl / 1000,'1899-12-31'))) <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X
or you can simplify by using a subquery (or you can use a WITH):
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(),
DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) Age, *
FROM Table_X) a
This will of course cause you to do this arithmetic for each row, and you can't easily use any indexes. If you were asking about aggregates, then I would suggest doing the opposite, and pre-calculating the dates and use those in your query instead. You might also want to consider putting a persisted computed column on table_x:
ALTER TABLE TABLE_X
ADD wadpl_dt AS
(DATEADD(DAY,wadpl%1000,DATEADD(YEAR,wadpl/1000,'1899-12-31'))) PERSISTED;
Now you can just refer to table_x.wadpl_dt whenever you want the datetime, and your query would become:
SELECT CASE
WHEN Age <0 THEN 'Overdue now'
WHEN Age <= 30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM (
SELECT DATEDIFF(DAY,GETDATE(), wadpl_dt) Age, *
FROM Table_X) a
Here is the easy way to convert a date to what you refer to as the julian date:
SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE())
And this is how you can use it:
DECLARE #overdue int;
DECLARE #next30 int;
SET #overdue = (SELECT (DATEPART(YEAR,GETDATE())-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()));
SET #next30 = (SELECT (DATEPART(YEAR,GETDATE()+30)-1900) * 1000 + DATEPART(DAYOFYEAR, GETDATE()+30));
SELECT CASE
WHEN wadpl < #overdue THEN 'Overdue now'
WHEN wadpl <= #next30 THEN 'Coming due in 01-30 days'
ELSE 'Not Overdue'
END [Overdue Status]
FROM Table_X

How to break datetime in 15 minute interval in sql sever 2014

I have split the below query in 15 minute interval on the basis of Start datetime but this query is not providing the exact result set
as i am expecting.
Below is the example of query i want to execute.
select Date_Stamp,
Case when substring(convert(char(8),starttime,114), 1, 8) between '12:00:01 AM'and '12:15:00 AM' then '0015'
when substring(convert(char(8),starttime,114), 1, 8) between '12:15:01 AM'and '12:30:00 AM' then '0030'
when substring(convert(char(8),starttime,114), 1, 8) between '12:30:01 AM'and '12:45:00 AM' then '0045'
when substring(convert(char(8),starttime,114), 1, 8) between '12:45:01 AM'and '01:00:00 AM' then '0100'
and i want the result as
Date Need result set
12:01 AM '0015'
'12:15:01 '0030'
'12:30:01 '0045'
'12:45:01 '0100'
'01:00:01 '0115'
'01:15:01 '0130'
'01:30:01 '0145'
'01:45:01 '0200'
'02:00:01 '0215'
'02:15:01 '0230'
'02:30:01 '0245'
3:00:00 ' '0015'
'12:30:00 '0030'
'12:45:00 '0045'
'01:00:00 '0100'
'01:15:00 '0115'
'01:30:00 '0130'
'01:45:00 '0145'
'02:00:00 '0200'
'02:15:00 '0215'
'02:30:00 '0230'
'02:45:00 '0245'
Just change #starttime with your column name
DECLARE #starttime datetime = getdate()
SELECT CONCAT(CASE WHEN DATEPART(HH, #starttime) <= 9
THEN '00'+ CAST(DATEPART(HH, #starttime) AS VARCHAR(2))
ELSE '0'+CAST(DATEPART(HH, #STARTTIME) AS VARCHAR(2))
END,
CASE WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 1 AND 15
THEN 15
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 16 AND 30
THEN 30
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 31 AND 45
THEN 45
WHEN DATEPART(MINUTE, #STARTTIME) BETWEEN 46 AND 59 OR DATEPART(MINUTE, #STARTTIME) = 0
THEN 00
END)
You can use this date generator:
DEMO
DECLARE #Break INT = 15
;WITH Numbers (n) as
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1
FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d(n)
)
,Dates as
(
SELECT dt
FROM Numbers
CROSS APPLY
(
VALUES (DATEADD(MINUTE , n, CAST(CAST(GETDATE() AS DATE) AS DATETIME)))
) X(Dt)
WHERE N % #Break = 0
AND CAST(DT AS DATE) = CAST(GETDATE() AS DATE) --Only for today's date
)
SELECT CONVERT(VARCHAR(10),Dt,108) [Time] , REPLACE(CONVERT(VARCHAR(5),ISNULL(Lead(Dt) OVER (ORDER BY Dt) , DATEADD(MINUTE,#Break,Dt)),108), ':','') Grp
FROM Dates
It appears you're using datetime and have only taken the substring of the time. A string cannot be compared to a time, without being casted to the time datatype.
For example:
DECLARE #mytable TABLE (starttime datetime)
INSERT INTO #mytable VALUES ('2018-03-13 00:00:01'), ('2018-03-15 00:00:01')
SELECT * FROM #mytable
select CAST(starttime as time(0)) AS [thetime],
Case when CAST(starttime as time) between '12:00:01 AM'and '12:15:00 AM' then '0015'
when CAST(starttime as time) between '12:15:01 AM'and '12:30:00 AM' then '0030'
when CAST(starttime as time) between '12:30:01 AM'and '12:45:00 AM' then '0045'
when CAST(starttime as time) between '12:45:01 AM'and '01:00:00 AM' then '0100'
END AS [Interval]
FROM #mytable
Produces:
thetime Interval
00:00:01 0015
00:15:01 0030

How to breakdown time policy life period by beginning and end of the months

Picture. Need to be like that
I need to break down each policy life period by months. From the beginning of the policy till the end of that month, then from the beginning till the end of the next month and for each of this time period need to calculate the number of days, so then I can calculate earned premium for each policy.
Please see the picture what do I need to achieve.
[DECLARE #EarnedToDate datetime ='2016-06-30'
;WITH Cte_Policies AS
(
SELECT
PolicyNumber
,TransactionEffectiveDate
,TransactionExpirationDate
,WrittenPremium
,DATEDIFF(DAY,TransactionEffectiveDate,TransactionExpirationDate) AS TotalDays
,CASE
WHEN TransactionEffectiveDate> #EarnedToDate THEN 0 --Policy not yet in effect
WHEN TransactionExpirationDate< #EarnedToDate THEN DATEDIFF(DAY,TransactionEffectiveDate,TransactionExpirationDate)
ELSE DATEDIFF(DAY,TransactionEffectiveDate,#EarnedToDate)
END AS EarnedDays
,CASE
WHEN TransactionEffectiveDate > #EarnedToDate THEN DATEDIFF(DAY,TransactionEffectiveDate,TransactionExpirationDate)
WHEN TransactionExpirationDate < #EarnedToDate THEN 0 -- Policy completed
ELSE DATEDIFF(DAY,#EarnedToDate,TransactionExpirationDate)
END AS UnearnedDays
FROM ##TempTable1
)
SELECT PolicyNumber,
TransactionEffectiveDate as TransactionEffectiveDate,
TransactionExpirationDate as TransactionExpirationDate
--WrittenPremium/TotalDays AS DayPremium,
,SUM(CASE WHEN EarnedDays = 0 THEN 0 ELSE WrittenPremium/TotalDays * EarnedDays END) AS EarnedPremium
,SUM(CASE WHEN UnearnedDays = 0 THEN 0 ELSE WrittenPremium/TotalDays * UnearnedDays END) AS UnearnedPremium
FROM
Cte_Policies where PolicyNumber ='PACA1000238-02'
GROUP BY
TransactionEffectiveDate
,TransactionExpirationDate
--,WrittenPremium/TotalDays
,PolicyNumber][1]
My original snippet answered your question on Twitter, but the following code snippet takes the first one a bit further and provides exactly the result set specified on your question here on StackOverflow...
; WITH Earned_to_date AS (
SELECT Cast('2016-07-01' AS DATE) AS Earned_to_date
), policy_data AS (
SELECT
policy_number
, Cast(text_Effective AS DATE) AS TransactionEffectiveDate
, Cast(text_Expiration AS DATE) AS TransactionExpirationDate
, policy_premium
FROM (VALUES
('p1', '1993-01-01', '1994-12-31', 940.00)
, ('p3', '2011-12-01', '2012-05-31', 485.00)
, ('p5', '2011-12-16', '2012-05-15', 485.00)
, ('p7', '2015-12-16', '2016-11-15', 485.00)
) AS z (policy_number, text_Effective
, text_Expiration, policy_premium)
), digits AS (
SELECT digit
FROM (VALUES (0), (1), (2), (3), (4)
, (5), (6), (7), (8), (9)) AS z2 (digit)
), numbers AS (
SELECT 1000 * d4.digit + 100 * d3.digit + 10 * d2.digit + d1.digit AS number
FROM digits AS d1
CROSS JOIN digits AS d2
CROSS JOIN digits AS d3
CROSS JOIN digits AS d4
), calendar AS (
SELECT
DateAdd(month, number, '1753-01-01') AS month_of
, DateAdd(month, number, '1753-02-01') AS month_after
FROM numbers
), policy_dates AS (
SELECT
policy_number
, CASE
WHEN month_of < TransactionEffectiveDate THEN TransactionEffectiveDate
ELSE month_of
END AS StartRiskMonth
, CASE
WHEN TransactionExpirationDate < month_after THEN TransactionExpirationDate
WHEN Earned_to_date.Earned_to_date < month_after THEN Earned_to_date
ELSE month_after
END AS EndRiskMonth
, DateDiff(day, TransactionEffectiveDate, TransactionExpirationDate) AS policy_days
, policy_premium
FROM policy_data
JOIN calendar
ON (policy_data.TransactionEffectiveDate < calendar.month_after
AND calendar.month_of < policy_data.TransactionExpirationDate)
CROSS JOIN Earned_to_date
WHERE month_of < Earned_to_date
)
SELECT policy_number, StartRiskMonth, EndRiskMonth
, DateDiff(day, StartRiskMonth, EndRiskMonth) AS DaysInMonth
, policy_premium * DateDiff(day, StartRiskMonth, EndRiskMonth) / policy_days
FROM policy_dates
ORDER BY policy_number, StartRiskMonth
As I thought from your tweet, this is pretty simple to code but it takes a bit of work to understand.
WITH raw_data AS (
SELECT
Cast(text_TransactionEffectiveDate AS DATE) AS TransactionEffectiveDate
, Cast(text_TransactionExpirationDate AS DATE) AS TransactionExpirationDate
FROM (VALUES
('1953-01-15', '1992-02-23')
, ('2012-08-12', '2012-08-26')
) AS z (text_TransactionEffectiveDate, text_TransactionExpirationDate )
), policy_dates AS (
SELECT
raw_data.*
, DateAdd(month, DateDiff(month, '1753-01-01', TransactionEffectiveDate), '1753-01-01') AS Policy_Start
, DateAdd(month, DateDiff(month, '1753-01-01', TransactionExpirationDate), '1753-02-01') AS Policy_Expired
FROM raw_data
)
SELECT DateDiff(day, Policy_start, Policy_Expired) AS Policy_days, *
FROM policy_dates
This is a computation that is common in the insurance field. The trick lies in computing the beginning of the Effective Month, and the beginning of the expiration month (after the policy has ended). The trick is that at one point a month later than the others (1753-02-01) is used.
-PatP
Add a CTE or two that breaks the policy effective and expiration dates into distinct months, then re-use your existing code/CTE
Be sure to review all boundary conditions for EarnedDays and UnearnedDays and make sure they line up with your business rules
DECLARE #EarnedToDate DATETIME
SET #EarnedToDate = '2016-06-30'
DECLARE #tblPolicies TABLE
(
PolicyNumber VARCHAR(100)
, PolicyEffectiveDate DATETIME
, PolicyExpirationDate DATETIME
, WP MONEY --WrittenPremium
)
DECLARE #tblPolicyMonths TABLE
(
PolicyNumber VARCHAR(100),
MonthStart DATETIME,
MonthEnd DATETIME
)
DECLARE #CurPos INT
SET #CurPos = 1
WHILE #CurPos < 4000
BEGIN
--Create a bunch of policies
INSERT INTO #tblPolicies
SELECT 'P' + CONVERT(varchar, #CurPos), DATEADD(d, #CurPos, GETDATE()), DATEADD(YY, 1, DATEADD(d, #CurPos, GETDATE())), #CurPos
SET #CurPos = #CurPos + 1
END
DECLARE #LastPolicyDate DATETIME
SET #LastPolicyDate = (SELECT MAX(PolicyExpirationDate) FROM #tblPolicies)
;WITH Cte_All_Dates AS
(
SELECT MIN(PolicyEffectiveDate) DateValue
FROM #tblPolicies
UNION ALL
SELECT DateValue + 1
FROM Cte_All_Dates
WHERE DateValue + 1 < = #LastPolicyDate
)
INSERT INTO #tblPolicyMonths
SELECT P.PolicyNumber
,MIN(DateValue)
,MAX(DateValue)
FROM Cte_All_Dates PD
INNER JOIN #tblPolicies P
ON CONVERT(DATE, PD.DateValue) BETWEEN P.PolicyEffectiveDate AND P.PolicyExpirationDate
GROUP BY P.PolicyNumber
, DATEPART(MM, DateValue)
, DATEPART(YY, DateValue)
OPTION (MAXRECURSION 32767);
SELECT
P.PolicyNumber
,CONVERT(DATE, MonthStart) As StartRiskMonth
,CONVERT(DATE, MonthEnd) AS EndRiskMonth
,WP as WrittenPremium
,DATEDIFF(DAY,MonthStart,MonthEnd)+1 AS TotalDays
,CASE
WHEN MonthStart > #EarnedToDate THEN 0 --Policy not yet in effect
WHEN MonthEnd < #EarnedToDate THEN DATEDIFF(DAY,MonthStart,MonthEnd)+1
ELSE DATEDIFF(DAY,MonthStart,#EarnedToDate)+1
END AS EarnedDays
,CASE
WHEN MonthStart > #EarnedToDate THEN DATEDIFF(DAY,MonthStart,MonthEnd)+1
WHEN MonthEnd < #EarnedToDate THEN 0 -- Policy completed
ELSE DATEDIFF(DAY,#EarnedToDate,MonthEnd)
END AS UnearnedDays
, #EarnedToDate AS 'EarnedToDate'
FROM #tblPolicyMonths PM
INNER JOIN #tblPolicies P
ON PM.PolicyNumber = P.PolicyNumber
ORDER BY PolicyNumber, MonthStart, MonthEnd