Count on a column with a conditional statement in SQL? - sql

I want a table as this:
cycle_end_date | count(user_id) | count(repaid_user_id) | event_ts | repayment_ts
15th Jan | 20 | 15 | 23rd Jan | 25th Jan
15th Jan | 30 | 30 | 24th Jan | 24th Jan
I am using the following query:
select
date(cycle_end_date) as cycle_end_date
,date(payload_event_timestamp + interval '5 hours 30 minutes') as event_date
,d.payment_date
,count( s.user_id) as count
,count(case s.user_id when d.payment_date < event_date then 1 else 0 end) as repaid_users
d.payment_date is taken from another query -
case when ots_created_at is null then
current_date else date(ots_created_at + interval ' 5 hours 30 minutes')
end as payment_date
this is giving me an error : ProgrammingError: operator does not exist: character varying = boolean
Basically I want the count of users who repaid on the day event was triggered. which is basically if event date is equal to repayment date we can find out that these many users paid on that event date. How to find this?

You have attempted to use a "short form" of case expression which starts like this:
case s.user_id when d.payment_date
For this format to be valid the syntax after when assumes an equal comparison (e.g. when d.payment_date assumes =) and in this short form it can only be an equality operator that is assumed. For more complex comparisons (less than, not equal, etc.) it is necessary to use the longer format of a case expression, like this:
case when d.payment_date < event_date then user_id end
Note that the aggregate function COUNT() increments for every non-null value, so to avoid counting any unwanted conditions do NOT return any value using else. e.g.
count(case when d.payment_date < event_date then user_id end) as col1
or, if you prefer to be more explicit in the query code, use else to return NULL which will not be counted, e.g.
count(case when d.payment_date < event_date then user_id else NULL end) as col1
nb: Instead of returning a column value you could use a constant like this:
count(case when d.payment_date < event_date then 1 end) as col1

Related

SQL - Aggregate column based on filters

My data has an ID, date, and points_earned field. Each row represents the points earned by the ID for that date.
I want to create something similar to this:
ID | Points_Earned_January | Points_Earned_February | Points_Earned_March
The result should be GROUP BY ID. Points_Earned_January should give a SUM of all the points_earned that fall in January for that ID.
How can I do this?
You can use conditional aggregation:
select id,
(case when date >= '2020-02-01' and date < '2020-03-01' then points else 0 end) as points_feb,
(case when date >= '2020-03-01' and date < '2020-04-01' then points else 0 end) as points_mar
from t
group by id;

SQL Report - query multiple tables

I have been working on a Stats page in APEX and currently have the following report query:
select to_char(DATELOGGED,'Month - YYYY') as Month,
COUNT(*) as "Total Calls",
SUM(case when CLOSED is null then 1 else null end) as "Open",
COUNT(case CLOSED when 'Y' then 1 else null end) as "Closed",
SUM(case when EXTREF is null then 0 else 1 end) as "Referred",
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as "SLA Met %"
from IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'Month - YYYY')
order by MIN (DATELOGGED) desc
I wish to add the sum of DURATION from a different table:
select
"START_TIME",
DECODE(DURATION,null,'Open',((select extract( minute from DURATION )
+ extract( hour from DURATION ) * 60
+ extract( day from DURATION ) * 60 * 24
from dual)||' minutes')) DURATION
from "IT_DOWNTIME"
The IT_DOWNTIME table uses START_TIME (varchar2) as the date identifier, the IT_SUPPORT_CALLS uses DATELOGGED (DATE) as date identifier.
The current output for IT_DOWNTIME is for example:
08-FEB-2019 - 30 Minutes
20-FEB-2019 - 15 Minutes
I would like the report SUM and group IT_DOWNTIME and add this into the existing report.
Hope this makes sense.
Please let me know if I missed any information that would help to resolve this.
Many thanks
Thanks for that, much appreciated. Unfortunately it doesn't return any data from IT_DOWNTIME.
I'm guessing the different date formats doesn't help, hope this clears things up a bit:
These are the columns in IT_DOWNTIME that are of interest:
START_TIME ( VARCHAR2(30) )
DURATION ( INTERVAL DAY(2) TO SECOND(6) )
Example of current IT_DOWNTIME output without formatting:
START_TIME
06-JUL-2016 11:05
DURATION
+00 00:35:00.000000
Example of current IT_SUPPORT_CALLS output without formatting:
DATELOGGED
06/07/2016
Something like this will probably do it, but there has been some guesswork as to your column names etc:
SELECT *
FROM
(
SELECT
to_char(DATELOGGED,'MON-YYYY') as Month,
COUNT(*) as Total_Calls,
SUM(case when CLOSED is null then 1 else null end) as case_Open,
COUNT(case CLOSED when 'Y' then 1 else null end) as case_Closed,
SUM(case when EXTREF is null then 0 else 1 end) as case_Referred,
round((COUNT(case SLA_MET when 'Y' then 1 else null end)/COUNT(case CLOSED when 'Y' then 1 else null end)*100),2) as percent_SLA_met
FROM IT_SUPPORT_CALLS
GROUP BY to_char(DATELOGGED,'MON-YYYY')
) calls
LEFT JOIN
(
SELECT
SUBSTR(START_TIME, 4) as down_month,
SUM(extract(minute from DURATION) +
extract(hour from DURATION) * 60 +
extract(day from DURATION) * 60 * 24
) || 'minutes' as total_down_mins
FROM IT_DOWNTIME
WHERE duration is not null
GROUP BY SUBSTR(START_TIME, 4)
) downs
ON calls.month = downs.down_month
Changed your date formatting of the first query to be MON-YYYY to make it align with what you claim is the formatting of the varchar2 date of the second query (dd-mon-yyy), and substringed the date to remove the day, leaving just the month
Edit:
Ok, so since you've posted some different example data from IT_DOWNTIME I see the problem: there's a time on the date also. Your first sample data didn't contain this time, it was just a date (as a string) so I was doing...
SUBSTR('01-JAN-1970', 4)
...to reduce the day date to a month date ('JAN-1970') and this was intended to align with the stuff going on in the other table ( to_date() with a format of 'non-yyyy' )
Now we know that there is a time in there too, of course it won't align because...
SUBSTR('01-JAN-1970 12:34', 4)
...produces 'JAN-1970 12:34' and this will then not match to anything from the other table (which will be just 'JAN-1970' without the time), so the left join means that nulls will be output
The solution is to change the SUBSTR call so it cuts 8 characters, starting at position 4:
SUBSTR(start_time, 4, 8)
This will remove the day and the time, leaving just the month-year that we need. You'll need to make the change in two places in the query above..
Apologies for the delay on replying to this. However, that is working perfectly Caius, thanks very much! So to be complete, had to change your above code to:
SUBSTR(START_TIME, 4, 8) as down_month,
and
GROUP BY SUBSTR(START_TIME, 4, 8)

Dropping Data into Aggregate Buckets by Date and Category

I'm trying to display aggregate counts of open and closed IT tickets by date and category.
Parent table consists of the following columns:
Alert_ID Alert_Open_Date Alert_Closed_Date
I'd like my end result to resemble the following, where I have
A. A date within any specified date range,
B. total number of alerts that still showed open as of that date (Outstanding_Alerts),
C. total number of alerts that were opened on that date (New_Alerts),
D. total number of the new alerts that were closed on that date (Closed_New_Alerts), and
E. combined number of alerts, both new and old, that were closed on that date (Closed_Total):
Date Outstanding_Alerts New_Alerts Closed_New_Alerts Closed_Total
6/1/2018 20 10 5 7
6/2/2018 23 20 8 10
6/3/2018 33 13 10 15
etc. # # # #
I was thinking of something like the following conceptual query to accomplish this, but I'm stumbling over the logic to get the results I'd like. Regardless of wording I can't seem to get the buckets correct. Some columns remain blank when they should be populated, for example. Any help is appreciated.
SELECT DISTINCT
alert_date
, SUM(OOA) AS Outstanding_Open_Alerts
, SUM(NOA) AS New_Open_Alerts
, SUM(CNA*NOA) AS Closed_New_Alerts
, SUM(CT) AS Total_Closed_Alerts
, SUM(CNA+NOA-CT) AS Remaining_Alerts --optional column
FROM
(SELECT
TRUNC(open_date) AS Alert_Date
, CASE WHEN alert_date < trunc(SYSDATE)-1 AND closed_date IS NULL THEN 1 ELSE 0
END AS OOA --old open alerts
, CASE WHEN alert_date > trunc(SYSDATE)-1 THEN 1 ELSE 0
END AS NOA --new open alerts
, CASE WHEN closed_date >= trunc(SYSDATE)-1 THEN 1 ELSE 0
END AS CNA --closed new alerts
, CASE WHEN closed_date < trunc(SYSDATE)-1 THEN 1 ELSE 0
END AS CT --closed total
FROM sys_alerts)
GROUP BY alert_date;
If I'm following your description, to get the numbers for an arbitrary date supplied as some_date you want something like:
SELECT
some_date AS Alert_Date
, COUNT(CASE WHEN open_date < some_date
AND (closed_date IS NULL OR closed_date > some_date
THEN alert_id END) AS Current_Open_Alerts
, COUNT(CASE WHEN open_date >= some_date
AND open_date < some_date + 1
THEN alert_id END AS New_Alerts
, COUNT(CASE WHEN open_date >= some_date
AND open_date < some_date + 1
AND closed_date < some_date + 1
THEN alert_id END AS Closed_New_Alerts
, COUNT(CASE WHEN closed_date >= some_date
AND closed_date < some_date + 1
THEN alert_id END AS Closed_Total
, COUNT(CASE WHEN open_date < some_date + 1
AND (closed_date IS NULL OR closed_date >= some_date + 1
THEN alert_id END) AS Alerts_Remaining
FROM sys_alerts
GROUP BY some_date
I've used a count rather than a sum, which relies on the fact that count ignores nulls - and left the default from the case expresssions as null. It doesn't need a sunquery really so I've removed that.
It seems like you want all data for a range of dates, so you can generate that in an inline view or CTE and then join to your real table, so some_date becomes wach individual date from that generated range. E.g. to get the last 30 days something like:
FROM (
SELECT sysdate - level AS some_date
FROM dual
CONNECT BY level <= 30
LEFT JOIN sys_alerts
ON closed_date IS NULL OR closed_date >= some_date
GROUP BY some_date
ORDER BY some_date
or you could do something with cross apply or a partitioned outer join. Hopefully this gives you a starting point though.

Time difference between two date fields

I have a date field names "dts" in string format. I want to find out all the records based on their time differences (in hours). The run event time should be greater than or equal to eat event.
The output should be:
Convert timestamps to seconds, then subtract, divide result by 3600 to get hours, use case+count to count by ranges, something like this:
select count(case when diff_hrs >24 then 1 end) as more_24,
count(case when diff_hrs <=24 then 1 end) as less_than_24,
...
count(case when diff_hrs >=2 and diff_hrs <=3 then 1 end) as hrs_2_to_3,
...
from
(
select
abs(unix_timestamp(dts) - unix_timestamp(dts-eat)))/60/60 as diff_hrs
from table
)s;

SQL query to conditionally sum based on moving date window

I'm trying to figure out some sliding window stats on my users. I have a table with a user, and columns such as created_at and verified_at. For each month, I'd like to find out how many users registered (a simple group by date_trunc of the created_at), and then of those people, how many verified within my sliding window (call it 60 days).
I'd like to do a SQL query that gives me something like:
Month | Registered | Verified in 60 days
Jan 2009 | 1543 | 107
Feb 2009 | 2000 | 250
I'm using postgresql. I starting looking at sum(case...), but I don't know if I can get my case to be dependent on the date_trunc somehow.
This doesn't work, of course, but here's the idea:
SELECT DATE_TRUNC('month', created_at) as month,
COUNT(*) as registered,
SUM(CASE WHEN verified_at < month+60 THEN 1 ELSE 0 END) as verified
FROM users
GROUP BY DATE_TRUNC('month', created_at)
SELECT COUNT(created_at) AS registered,
SUM(CASE WHEN verified_at <= created_at + '60 day'::INTERVAL THEN 1 ELSE 0 END) AS verified
FROM generate_series(1, 20) s
LEFT JOIN
users
ON created_at >= '2009-01-01'::datetime + (s || ' month')::interval
AND created_at < '2009-01-01'::datetime + (s + 1 || ' month')::interval
GROUP BY
s
perhaps you could union together the different months.
select sum(whatever), 'january' from user where month = 'january'
union all
select sum(whatever), 'february' from user where month = 'february'
...
SELECT
MONTH,
COUNT(*) AS Registered,
SUM (CASE WHEN datediff(day,reg_date,ver_date) < 60 THEN 1 ELSE 0) as 'Verified in 60 //days datediff is an MSSQL function amend for postgresql'
FROM
TABLE
GROUP BY
MONTH