How can I get the total? - sql

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);

Related

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

DB2 use of labeled duration not valid with multiple date intervals

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).

How I can group by and count in PostgreSQL to prevent empty cells in result

I have the table in PostgreSQL DB
Need to calculate SUM of counts for each event_type (example for 4 and 1)
When I use query like this
SELECT account_id, date,
CASE
WHEN event_type = 1 THEN SUM(count)
ELSE null
END AS shows,
CASE
WHEN event_type = 4 THEN SUM(count)
ELSE null
END AS clicks
FROM widgetstatdaily WHERE account_id = 272 AND event_type = 1 OR event_type = 4 GROUP BY account_id, date, event_type ORDER BY date
I receive this table
With <null> fields. It's because I have event_type in select and I need to GROUP BY on it.
How I can make query to receive grouped by account_id and date result without null's in cells? Like (first row)
272 2018-03-28 00:00:00.000000 57 2
May be I can group it after receiving result
You need conditional aggregation and some other fixes. Try this:
SELECT account_id, date,
SUM(CASE WHEN event_type = 1 THEN count END) as shows,
SUM(CASE WHEN event_type = 4 THEN count END) as clicks
FROM widgetstatdaily
WHERE account_id = 272 AND
event_type IN (1, 4)
GROUP BY account_id, date
ORDER BY date;
Notes:
The CASE expression should be an argument to the SUM().
The ELSE NULL is redundant. The default without an ELSE is NULL.
The logic in the WHERE clause is probably not what you intend. That is fixed using IN.
try its
SELECT account_id, date,
SUM(CASE WHEN event_type = 1 THEN count else 0 END) as shows,
SUM(CASE WHEN event_type = 4 THEN count else 0 END) as clicks
FROM widgetstatdaily
WHERE account_id = 272 AND
event_type IN (1, 4)
GROUP BY account_id, date
ORDER BY date;

Week interval query starting on mondays

FIDDLE
I need to do a JasperReport. what I need to display is the total number of accounts processes, broken down into weekly intervals with the number of activated and declined accounts.
For the weekly interval query I got thus far:
SELECT *
FROM account_details
WHERE DATE date_opened = DATE_ADD(2014-01-01, INTERVAL(1-DAYOFWEEK(2014-01-01)) +1 DAY)
This seems to be correct, but not POSTGRES correct. It keeps complaining about the 1-DAYOFWEEK. Here is what I will hopefully achieve:
UPDATE
It is pretty ugly, but I dont know of any better. Id does the job though. But dont know if it can be re-factored to look better at least. I also dont know how to handle division by zero at the moment.
SELECT to_char(d.day, 'YYYY/MM/DD - ') || to_char(d.day + 6, 'YYYY/MM/DD') AS Month
, SUM(CASE WHEN LOWER(situation) LIKE '%active%' THEN 1 ELSE 0 END) AS Activated
, SUM(CASE WHEN LOWER(situation) LIKE '%declined%' THEN 1 ELSE 0 END) AS Declined
, SUM(CASE WHEN LOWER(situation) LIKE '%declined%' OR LOWER(situation) LIKE '%active%' THEN 1 ELSE 0 END) AS Total
, to_char( 100.0 *( (SUM(CASE WHEN LOWER(situation) LIKE '%active%' THEN 1 ELSE 0 END)) / (SUM(CASE WHEN LOWER(situation) LIKE '%declined%' OR LOWER(situation) LIKE '%active%' THEN 1 ELSE 0 END))::real) , '99.9') AS percent_activated
, to_char( 100.0 *( (SUM(CASE WHEN LOWER(situation) LIKE '%declined%' THEN 1 ELSE 0 END)) / (SUM(CASE WHEN LOWER(situation) LIKE '%declined%' OR LOWER(situation) LIKE '%active%' THEN 1 ELSE 0 END))::real) , '99.9') AS percent_declined
FROM (
SELECT day::date
FROM generate_series('2014-08-01'::date, '2014-09-14'::date, interval '1 week') day
) d
JOIN account_details a ON a.date_opened >= d.day
AND a.date_opened < d.day + 6
GROUP BY d.day;
SELECT to_char(d.day, 'YYYY/MM/DD" - "')
|| to_char(d.day + 6, 'YYYY/MM/DD') AS week
, count(situation ILIKE '%active%' OR NULL) AS activated
, ...
FROM (
SELECT day::date
FROM generate_series('2014-08-11'::date
, '2014-09-14'::date
, '1 week'::interval) day
) d
LEFT JOIN account_details a ON a.date_opened >= d.day
AND a.date_opened < d.day + 7 -- 7, not 6!
GROUP BY d.day;
Related answers:
Weekly total sums
Calculate working hours between 2 dates in PostgreSQL
Best way to count records by arbitrary time intervals in Rails+Postgres
More about counting specific values:
For absolute performance, is SUM faster or COUNT?
SQL Query to Transpose Column Counts to Row Counts
Aside: You would typically use an enum or a look-up table and just store an ID for situation, not a lengthy text redundantly.

SQL sum of column value, unique per user per day

I have a postgres table that looks like this:
id | user_id | state | created_at
The state can be any of the following:
new, paying, paid, completing, complete, payment_failed, completion_failed
I need a statement that returns a report with the following:
sum of all paid states by date
sum of all completed states by date
sum of all new, paying, completing states by date with only one per user per day to be counted
sum of all payment_failed, completion_failed by date with only one per user per day to be counted
So far I have this:
SELECT
DATE(created_at) AS date,
SUM(CASE WHEN state = 'complete' THEN 1 ELSE 0 END) AS complete,
SUM(CASE WHEN state = 'paid' THEN 1 ELSE 0 END) AS paid
FROM orders
WHERE created_at BETWEEN ? AND ?
GROUP BY DATE(created_at)
A sum of the in progress and failed states is easy enough by adding this to the select:
SUM(CASE WHEN state IN('new','paying','completing') THEN 1 ELSE 0 END) AS in_progress,
SUM(CASE WHEN state IN('payment_failed','completion_failed') THEN 1 ELSE 0 END) AS failed
But i'm having trouble figuring out how to make only one per user_id per day in_progress and failed states to be counted.
The reason I need this is to manipulate the failure rate in our stats, as many users who trigger a failure or incomplete order go on to trigger more which inflates our failure rate.
Thanking you in advance.
SELECT created_at::date AS the_date
,SUM(CASE WHEN state = 'complete' THEN 1 ELSE 0 END) AS complete
,SUM(CASE WHEN state = 'paid' THEN 1 ELSE 0 END) AS paid
,COUNT(DISTINCT CASE WHEN state IN('new','paying','completing')
THEN user_id ELSE NULL END) AS in_progress
,COUNT(DISTINCT CASE WHEN state IN('payment_failed','completion_failed')
THEN user_id ELSE NULL END) AS failed
FROM orders
WHERE created_at BETWEEN ? AND ?
GROUP BY created_at::date
I use the_date as alias, since it is unwise (while allowed) to use the key word date as identifier.
You could use a similar technique for complete and paid, one is as good as the other there:
COUNT(CASE WHEN state = 'complete' THEN 1 ELSE NULL END) AS complete
Try something like:
SELECT
DATE(created_at) AS date,
SUM(CASE WHEN state = 'complete' THEN 1 ELSE 0 END) AS complete,
SUM(CASE WHEN state = 'paid' THEN 1 ELSE 0 END) AS paid,
COUNT(DISTINCT CASE WHEN state IN('new','paying','completing') THEN user_id ELSE NULL END) AS in_progress,
COUNT(DISTINCT CASE WHEN state IN('payment_failed','completion_failed') THEN user_id ELSE NULL END) AS failed
FROM orders
WHERE created_at BETWEEN ? AND ?
GROUP BY DATE(created_at);
The main idea - COUNT (DISTINCT ...) will count unique user_id and wont count NULL values.
Details: aggregate functions, 4.2.7. Aggregate Expressions
The whole query with same style counts and simplified CASE WHEN ...:
SELECT
DATE(created_at) AS date,
COUNT(CASE WHEN state = 'complete' THEN 1 END) AS complete,
COUNT(CASE WHEN state = 'paid' THEN 1 END) AS paid,
COUNT(DISTINCT CASE WHEN state IN('new','paying','completing') THEN user_id END) AS in_progress,
COUNT(DISTINCT CASE WHEN state IN('payment_failed','completion_failed') THEN user_id END) AS failed
FROM orders
WHERE created_at BETWEEN ? AND ?
GROUP BY DATE(created_at);