DB2 use of labeled duration not valid with multiple date intervals - sql

I'm trying to refactor a MySQL query to run on DB2/iSeries and I'm getting the error Use of labeled duration not valid.
Looking at the documentation I feel like the usage below should be working.
Am I missing something?
SELECT
IFNULL(SUM(CASE WHEN CURDATE() BETWEEN n.start_date AND n.expire_date
THEN 1 ELSE 0 END), 0) AS current,
IFNULL(SUM(CASE WHEN CURDATE() - 365 DAY BETWEEN n.start_date AND n.expire_date
THEN 1 ELSE 0 END), 0) AS prior,
IFNULL(SUM(CASE WHEN '2018-12-31' - 7 DAY BETWEEN n.start_date AND n.expire_date
THEN 1 ELSE 0 END), 0) AS full
FROM salesnumbers;

The issue is likely your date intervals. Try using CURRENT DATE instead of CURDATE(). Also, you may list date intervals +/- some amount directly in DB2.
SELECT
COUNT(CASE WHEN CURRENT DATE BETWEEN n.start_date AND n.expire_date
THEN 1 END) AS current,
COUNT(CASE WHEN CURRENT DATE - 1 YEAR BETWEEN n.start_date AND n.expire_date
THEN 1 END) AS prior,
COUNT(CASE WHEN DATE('2018-12-31') - 7 DAY BETWEEN n.start_date AND n.expire_date
THEN 1 END) AS full
FROM salesnumbers;
Note that I replaced your conditional sums with conditional counts. This leaves the code slightly more terse, because we don't have to spell out an explicit ELSE condition (the default being NULL).

Related

Repeated select statement for retention analysis

I am working on retention analysis for a subscription product using a temporary table that logs all customer entitlements. Each line in the table has:
customer_id, produc_id, start_date, expiration_date, product_serial_number, ...
Customer_id is unique. If a customer renews the product at expiration date, new expiration date will be updated to the table daily.
Usually at the monthly analysis, I count customer_id, then group by YYYY-MM (extracted from start_date) as the base of the cohort. Then I put in a sum & case statement such as:
sum(case when expiration_date >= ADD_MONTHS(start_date,1) then 1 else 0 end) as 'T+1M'
sum(case when expiration_date >= ADD_MONTHS(start_date,2) then 1 else 0 end) as 'T+2M'
....
I hardcode every "sum + case when" line for each period I want to check for retention. It works well for monthly. But now I want to look closer at daily level for a customer behavior for 60 days before expiration date. I wonder if there is anyway more efficient than writing 60 lines such as:
sum(case when opt_out_date >= expiration_date-1) then 1 else 0 end) as 'OO_T-1'
sum(case when opt_out_date >= expiration_date-2) then 1 else 0 end) as 'OO_T-2'
....
sum(case when opt_out_date >= expiration_date-60) then 1 else 0 end) as 'OO_T-60'
opt_out_date is the date that a user decides to cancel the auto-billing feature.This analysis is to check right before expiration date, how many users manually cancel their auto-billing.
The environment is Teradata
Thanks alot.
Sample data and expected result added as recommended
Sample data
Expected result from query

SELECT data grouped by WEEK in SQL

I have a working query (ORACLE SQL) that gives me gallons grouped by store number, with gallons summed by type and a percentage column as well. Each store number has a different conversion date from which I sum up the data -
SELECT StoreNbr,
SUM(CASE WHEN(ClrntSys IN ('844', '84448')) THEN Gallons ELSE 0 END) AS Gallons_844,
SUM(CASE WHEN(ClrntSys ='GIC') THEN Gallons ELSE 0 END) AS Gallons_GIC,
SUM(CASE WHEN(ClrntSys IN ('844', '84448', 'GIC')) THEN Gallons ELSE 0 END) AS Total_Gallons,
CONCAT(CAST((SUM(CASE WHEN(ClrntSys ='GIC') THEN Gallons ELSE 0 END) /
SUM(CASE WHEN(ClrntSys IN ('844', '84448', 'GIC')) THEN Gallons ELSE 0 END)) AS DECIMAL (5,2)) * 100, '%') AS Percent_GIC
FROM MQ_CDS_NETTRAN
WHERE ClrntSys IN ('844', '84448', 'GIC')
AND ((CostCenter = '701104' AND LastTranDate >= DATE '2020-03-10')
OR (CostCenter = '701109' AND LastTranDate >= DATE '2020-03-04')
OR (CostCenter = '701257' AND LastTranDate >= DATE '2020-03-12'))
GROUP BY StoreNbr
ORDER BY StoreNbr;
I now need to also sum it up by week, with Sunday being the first day of each week, but I'm having trouble understanding how DATEPART works. Just so I could get a better idea, I tried only to sum up the data for last week using DATEPART examples I'm seeing online, but this doesn't work. It's giving me "invalid identifier DATEPART". -
SELECT DATEPART(week, 5/17/20) AS weekTotal,
StoreNbr,
SUM(CASE WHEN(ClrntSys IN ('844', '84448')) THEN Gallons ELSE 0 END) AS Gallons_844,
SUM(CASE WHEN(ClrntSys ='GIC') THEN Gallons ELSE 0 END) AS Gallons_GIC,
SUM(CASE WHEN(ClrntSys IN ('844', '84448', 'GIC')) THEN Gallons ELSE 0 END) AS Total_Gallons,
CONCAT(CAST((SUM(CASE WHEN(ClrntSys ='GIC') THEN Gallons ELSE 0 END) /
SUM(CASE WHEN(ClrntSys IN ('844', '84448', 'GIC')) THEN Gallons ELSE 0 END)) AS DECIMAL (5,2)) * 100, '%') AS Percent_GIC
FROM MQ_CDS_NETTRAN
WHERE ((CostCenter = '701104' AND LastTranDate >= DATE '2020-03-10')
OR (CostCenter = '701109' AND LastTranDate >= DATE '2020-03-04')
OR (CostCenter = '701257' AND LastTranDate >= DATE '2020-03-12'))
GROUP BY DATEPART(week, 5/17/20), StoreNbr
ORDER BY StoreNbr;
What I really need, however, is each week's data (with Sunday being the beginning of each week) summed up separately going back to each store's conversion date. Is it possible to do that? Is there something else besides DATEPART that would work better?
Sorry - just noticed that you said Oracle SQL, and my first answer was for SQL Server! The reason you are getting an error is that DATEPART is not an Oracle function. Instead, you can simply do math on the dates, using a known sunday (prior to a known first date in the DB table) as an anchor date:
SELECT
'30-DEC-2018' as "Known Sunday,
trunc((sysdate - to_date('30-DEC-2018')) / 7) as "Week Num",
to_date('30-DEC-2018')
+ (trunc((sysdate - to_date('30-DEC-2018')) / 7) * 7)"
FROM
dual

How to calculate time

I made a table data like below and I want to filter time with category hour -8 and +8.
I made a query like this but wrong result
select resolved_by, count(*) as total,
count (resolution_time > HOUR (resolution_time -8)) as total_target,
count (resolution_time > HOUR (resolution_time +8)) as total_untarget
from all_ticket_closed
where resolved_by = 'Oktadika.Riptono'
group by resolved_by;
For the result, total_target should be 32 and total_untarget 10. how to query it.
Thanks in advance
Probably you may require CASE expression and SUM function for aggregation
SUM (CASE WHEN resolution_time > HOUR THEN (resolution_time -8) ELSE 0 END) as total_target,
SUM (CASE WHEN resolution_time > HOUR THEN (resolution_time +8) ELSE 0 END) as total_untarget

Converting dates into weekdays then correlating it and summing it

The query is simple but not functioning the way I want it,
I am trying to check the date I inspected is the correct day I am checking against.
Input
SELECT TO_CHAR(date '1982.03.09', 'DAY'),
(CASE When lower(TO_CHAR(date '1982.03.09', 'DAY')) like lower('TUESDAY')
then 1 else 0 end)
Output
The answer should have been 1 for the case statement.
I added lower to check if it had to something with the capitals
Reason
The reason why I use a case statement is because when a student has an afterschool activity on monday, I want to place either 1 or 0 in the table and calculate the sum of how many students have afterschool acitivity on monday and so on.
Need eventually
I am doing this so that I can create a table of the week with the number of children doing aftershool activities for each day.
Any help regarding fixing my query would be greatly appreciated!
Thanks
For whatever reason there are spaces behind the TUESDAY to_char() produces. You can trim() them away. But instead of relying on a string representation (that probably might change when the locale changes) you should better use extract() to get the day of the week in numerical representation, 0 for Sunday, 1 for Monday and so on.
SELECT to_char(DATE '1982.03.09', 'DAY'),
CASE
WHEN trim(to_char(DATE '1982.03.09', 'DAY')) = 'TUESDAY' THEN
1
ELSE
0
END,
CASE extract(dow FROM DATE '1982.03.09')
WHEN 2 THEN
1
ELSE
0
END;
I'm a personal fan of extract (<datepart> from <date>) in lieu of to_char for problems like this.
Based on the output you are trying to achieve, I might also recommend a poor man's pivot table:
select
student_id,
max (case when extract (dow from activity_date) = 1 then 1 else 0 end) as mo,
max (case when extract (dow from activity_date) = 2 then 1 else 0 end) as tu,
max (case when extract (dow from activity_date) = 3 then 1 else 0 end) as we,
max (case when extract (dow from activity_date) = 4 then 1 else 0 end) as th,
max (case when extract (dow from activity_date) = 5 then 1 else 0 end) as fr
from activities
where activity_date between :FROM_DATE and :THRU_DATE
group by
student_id
Normally this would be a good use case for filter (where, but that would leave null values on date/student records where there is no activity. Depending on how you render your output, that may or may not be okay (Excel would handle it fine).
select
student_id,
max (1) filter (where extract (dow from activity_date) = 1) as mo,
max (1) filter (where extract (dow from activity_date) = 2) as tu,
max (1) filter (where extract (dow from activity_date) = 3) as we,
max (1) filter (where extract (dow from activity_date) = 4) as th,
max (1) filter (where extract (dow from activity_date) = 5) as fr
from activities
group by
student_id

How can I get the total?

I could not get the exact query to get the over all total of the total table. i want to get the total of each date in call_time table. here's my query:
SELECT call_type, channel, call_time,
count (CASE WHEN upper(status) = upper('no answer') THEN 1 ELSE NULL END) AS cnt_no_answer,
count (CASE WHEN upper(status) = upper('answered') THEN 1 ELSE NULL END) AS cnt_answer,
count (status) AS cnt_total
FROM app_account.cc_call
WHERE channel = 'DAHDI/i1/'
AND call_time BETWEEN ('30-DEC-2013') AND ('04-JAN-2014')
GROUP BY call_type, channel, call_time;
Some output of that query:
CALL_TYPE CHANNEL CALL_TIME CNT_NO_ANSWER CNT_ANSWERED CNT_TOTAL
LANDLINE DAHDI/i1/ 03-JAN-14 1 0 1
MOBILE-SUN DAHDI/i1/ 03-JAN-14 0 1 1
MOBILE-SUN DAHDI/i1/ 03-JAN-14 1 0 1
LANDLINE DAHDI/i1/ 03-JAN-14 1 0 1
LANDLINE DAHDI/i1/ 03-JAN-14 1 0 1
LANDLINE DAHDI/i1/ 03-JAN-14 1 0 1
MOBILE-SUN DAHDI/i1/ 02-JAN-14 1 0 1
MOBILE-SUN DAHDI/i1/ 02-JAN-14 0 1 1
LANDLINE DAHDI/i1/ 02-JAN-14 0 1 1
LANDLINE DAHDI/i1/ 02-JAN-14 1 0 1
MOBILE-SMART DAHDI/i1/ 02-JAN-14 1 0 1
My excepted Output:
CALL_TIME CNT_NO_ANSWER CNT_ANSWERED
03-JAN-14 27 10
02-JAN-14 48 20
Please help me.
Thank you!
Use something like the following:
SELECT CALL_TYPE, CHANNEL, TRUNC(CALL_TIME)
, COUNT (CASE UPPER(STATUS)
WHEN UPPER('no answer') THEN 1
ELSE NULL
END) AS CNT_NO_ANSWER
, COUNT (CASE UPPER(STATUS)
WHEN UPPER('answered') THEN 1
ELSE NULL
END) AS CNT_ANSWER
, COUNT (STATUS) AS CNT_TOTAL
FROM APP_ACCOUNT.CC_CALL
WHERE CHANNEL = 'DAHDI/i1/'
AND CALL_TIME BETWEEN TO_DATE('30-DEC-2013')
AND TO_DATE('04-JAN-2014')
GROUP BY CALL_TYPE, CHANNEL, TRUNC(CALL_TIME);
The major change I have made is TRUNC(CALL_TIME). Oracle stores dates as datetime values, which have dates as well as time values. Hence, when you use GROUP BY ..., CALL_TIME, ..., what really happens is that the grouping is done for the datetime values, not date values. Only the calls which were made on the exact time accurate to a fraction of a second will be grouped together, which is not the expected behavior. Hence use GROUP BY TRUNC(CALL_DATE) when you have to show the grouping by day.
EDIT:
To get the overall total for each day, you have already used COUNT(STATUS) AS CNT_TOTAL in your query! It would give you the total number of calls if the column is a not null and status is recorded for each call. If this column contains null values, I would suggest you use COUNT(*) AS CNT_TOTAL as it would count all the rows without regards to constraints on columns.
As far as the "for each day" part, TRUNC(datetime) function can truncate datetime values from their year down to their minute. This means, if you want to get the number of calls, or any other statistics, each year then you can simply use TRUNC(call_time, 'YYYY'). On the other hand, if you want call statistics for each hour, you can use TRUNC(call_time, 'HH') or TRUNC(call_time, 'HH24'). Same goes for a minute.
But beware, unless you use a TO_CHAR function to display dates, the front-end dev tools like Toad or SQL Developer display datetime values in the DD-MON-YYYY format, discarding the time information. This is what got you in the first place. Hence, if you group by truncating datetimes to an hour or a minute, and even though the results are correct, you will see repeated date in DD-MON-YYYY format. Hence, don't get confused.
For further reading on TRUNC, I would suggest Oracle Docs AND this link to techonthenet.com. For TO_CHAR, Oracle Docs here has detailed and easy to understand explanation.
Try this:
SELECT CALL_TYPE, CHANNEL, TRUNC(CALL_TIME)
,COUNT (CASE WHEN UPPER(STATUS) = UPPER('no answer') THEN 1 END) AS CNT_NO_ANSWER
,COUNT (CASE WHEN UPPER(STATUS) = UPPER('answered') THEN 1 END) AS CNT_ANSWER
,COUNT (STATUS) AS CNT_TOTAL
FROM APP_ACCOUNT.CC_CALL
WHERE CHANNEL = 'DAHDI/i1/'
AND CALL_TIME BETWEEN ('30-DEC-2013') AND ('04-JAN-2014')
GROUP BY CALL_TYPE, CHANNEL, TRUNC(CALL_TIME);
If CALL_TIME contains time value and you want to GROUP BY each date, you should trunc the CALL_TIME to its date.
To get the day-wise count you need to group with CALL_TIME. Try like this,
SELECT call_type,
channel,
trunc(call_time),
count (CASE WHEN upper(status) = upper('no answer') THEN 1 ELSE NULL END) AS cnt_no_answer,
count (CASE WHEN upper(status) = upper('answered') THEN 1 ELSE NULL END) AS cnt_answer
FROM happ_account.cc_call
WHERE channel = 'DAHDI/i1/'
AND call_time BETWEEN to_date('30-DEC-2013', 'DD-MON-YYYY') AND to_date('04-JAN-2014', 'DD-MON-YYYY')
GROUP BY call_type, channel, trunc(call_time);