Get the last time before the call - SQL Query - sql

I have here a set of 2 tables that I need to bash.
First table is the list of time and date the customer contacted us, its not unique.
The next table is the escalated call they made to us.
What I need to do is to show the date and time before the escalated call.
I can do simple left join based on customer ID, but having issue on the getting the last call.
Hope that I can get answers + explanation that I can use moving forward.
Here's my code so far:
Select a.customer id, a.contact_time, b.date of contact time as last_contact
from escalated_call a
left join all calls b on a.customer id = b.customer ID

Just Use a Where Clause
Select a.customerid,
a.contact_time,
b.DateOfContactTime as last_contact
from escalated_call AS a LEFT JOIN Calls AS b on a.customerID = b.customerID
WHERE a.contact_time < b.DateOfContactTime

You just need an aggregate max here, you can also do it with a correlated subquery but it’s probably not worth it.
You may need to correct your column names, I’ve just guessed you have underscored instead of the spaces
Select a.customer_id
,a.contact_time
,max(b.date_of_contact_time) as last_contact
from escalated_call a
left join all_calls b
on a.customer_id = b.customer_ID
Group by a.customer_id, a.contact_time

From Oracle 12, you can use a LATERAL join and return the FIRST ROW ONLY:
SELECT ec.*, ac.dt
FROM escalated_calls ec
LEFT OUTER JOIN LATERAL (
SELECT ac.dt
FROM all_calls ac
WHERE ac.customer_id = ec.customer_id
AND ac.dt <= ec.ct
ORDER BY ac.dt DESC
FETCH FIRST ROW ONLY
) ac
ON (1 = 1)
Which, for the sample data:
CREATE TABLE all_calls(customer_id, dt) AS
SELECT 1, DATE '2019-12-24' + INTERVAL '00:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 1, DATE '2019-12-24' + INTERVAL '00:15' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 1, DATE '2019-12-24' + INTERVAL '00:35' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 1, DATE '2019-12-24' + INTERVAL '01:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 2, DATE '2019-12-24' + INTERVAL '00:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 2, DATE '2019-12-24' + INTERVAL '00:15' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 2, DATE '2019-12-24' + INTERVAL '00:35' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 2, DATE '2019-12-24' + INTERVAL '01:00' HOUR TO MINUTE FROM DUAL;
CREATE TABLE escalated_calls (customer_id, ct) AS
SELECT 1, DATE '2019-12-24' + INTERVAL '00:45' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 2, DATE '2019-12-24' + INTERVAL '00:05' HOUR TO MINUTE FROM DUAL;
Outputs:
CUSTOMER_ID
CT
DT
1
2019-12-24 00:45:00
2019-12-24 00:35:00
2
2019-12-24 00:05:00
2019-12-24 00:00:00
db<>fiddle here

You can also use a subquery in the select clause to solve this problem.
SELECT e.*
, ( SELECT max(a.Date_Of_Contact_Time)
FROM all_calls a
WHERE a.customer_id = e.customer_id
AND a.Date_Of_Contact_Time <= e.contact_time
) AS correct_answer
FROM escalated_calls e
;

Related

Value for missing data in SQL

Question for the SQL gurus. I have a table with 3 columns. [Date, Meter, Quality], where there will only be one line per date for each meter. As an example:
SELECT * FROM MyDB WHERE Meter = 'MeterX' AND Date > '1-AUG-2022' AND Date <= '5-AUG-2022' ORDER BY Date;
I would query much larger date ranges so would usually miss if there is a date missing. Is there a way that I can have a value returned in the Quality column like "Missing" if that partiqular day is missing from the database? This means that I also need the missing date in the Date column. I also only have read access, so no creating temp tables to join with.
Thank you.
Use a PARTITIONed OUTER JOIN to a row-generator:
SELECT c.day,
m.meter,
COALESCE(m.quality, 0) AS quality
FROM (
SELECT DATE '2022-08-01' + (LEVEL - 1) AS day
FROM DUAL
CONNECT BY DATE '2022-08-01' + (LEVEL - 1) <= DATE '2022-08-05'
) c
LEFT OUTER JOIN MyDB m
PARTITION BY (m.meter)
ON (c.day <= m."DATE" and m."DATE" < c.day + 1)
WHERE m.Meter = 'MeterX'
ORDER BY c.day;
Which, for the sample data:
CREATE TABLE mydb ("DATE", meter, quality) AS
SELECT DATE '2022-08-01', 'MeterX', 42 FROM DUAL UNION ALL
SELECT DATE '2022-08-02', 'MeterX', 23 FROM DUAL UNION ALL
SELECT DATE '2022-08-04', 'MeterX', 7 FROM DUAL UNION ALL
SELECT DATE '2022-08-05', 'MeterX', 99 FROM DUAL;
Outputs:
DAY
METER
QUALITY
01-AUG-22
MeterX
42
02-AUG-22
MeterX
23
03-AUG-22
MeterX
0
04-AUG-22
MeterX
7
05-AUG-22
MeterX
99
db<>fiddle here
for postgres this could work for you
with date_dimension as (
SELECT dd::DATE
FROM generate_series
( '2022-08-01'::timestamp
, '2022-08-05'::timestamp
, '1 day'::interval) dd
)
select *
from my_table
left join date_dimension on date_dimension.dd = my_table.Date
where Meter = 'MeterX'
and Date > '2022-08-01'
and Date <= '2022-08-05'
order by Date;

Oracle SQL query to select only records with date earliest on Department Table

If time difference for 'Login_Date' column with records for same Department is within 18 hours then pick only the record with earliest login date.
Below sample data:
Need query for below data:
From Oracle 12, you can use MATCH_RECOGNIZE to do row-by-row processing if you want to exclude all rows that are within 18 hours of the first row of the group:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY department
ORDER BY login_date
ALL ROWS PER MATCH
PATTERN (first_row {- within_18_hours* -} )
DEFINE
within_18_hours AS login_date <= first_row.login_date + INTERVAL '18' HOUR
)
Which, for the sample data:
CREATE TABLE table_name (record_id, department, "USER", login_date) AS
SELECT 1, 'IT', 'xujk', DATE '2022-01-10' + INTERVAL '10' HOUR FROM DUAL UNION ALL
SELECT 2, 'IT', 'jkl', DATE '2022-01-10' + INTERVAL '15' HOUR FROM DUAL UNION ALL
SELECT 3, 'IT', 'xujk', DATE '2022-01-12' + INTERVAL '11' HOUR FROM DUAL UNION ALL
SELECT 4, 'FINANCE', 'mno', DATE '2022-01-10' + INTERVAL '01' HOUR FROM DUAL UNION ALL
SELECT 5, 'FINANCE', 'abc', DATE '2022-01-12' + INTERVAL '15' HOUR FROM DUAL UNION ALL
SELECT 6, 'FINANCE', 'def', DATE '2022-01-12' + INTERVAL '20' HOUR FROM DUAL;
Outputs:
DEPARTMENT
LOGIN_DATE
RECORD_ID
USER
FINANCE
10-JAN-22
4
mno
FINANCE
12-JAN-22
5
abc
IT
10-JAN-22
1
xujk
IT
12-JAN-22
3
xujk
If you want to exclude rows that are within 18 hours of the previous row (and not necessarily within 18 hours of the earliest row of the group) then you can use:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY department
ORDER BY login_date
ALL ROWS PER MATCH
PATTERN (first_row {- within_18_hours* -} )
DEFINE
within_18_hours AS login_date <= PREV(login_date) + INTERVAL '18' HOUR
)
db<>fiddle here
Try this:
Select Record_ID,d.Department,User,d.Login_Date
from data d
inner join ( Select Department,min(Login_Date)
from data
where EXTRACT(HOUR FROM CAST(Login_Date AS timestamp)) <= 18
group by Department,trunc(Login_Date) ) as t
on d.Department= t.Department and d.Login_Date = t.Login_Date

How to avoid using limit query in sql redshift to get last x weeks of data

I have a query below which gives me data for the past 8 weeks and it works fine -
WITH dates
AS (
SELECT (
date_trunc('week', getdate() + INTERVAL '1 day')::DATE - 7 * (
row_number() OVER (
ORDER BY true
) - 1
) - INTERVAL '1 day'
)::DATE AS week_info
FROM data.process LIMIT 8
)
SELECT dates.week_info
,'W' || ceiling(date_part('week', dates.week_info + INTERVAL '1 day')) AS week_number
,COUNT(DISTINCT zeus.client_id) AS PROC
FROM data.active_values zeus
JOIN dates ON zeus.updated_timestamp <= dates.week_info
WHERE zeus.kites_version = (
SELECT MAX(kites_version)
FROM data.active_values f2
WHERE zeus.client_id = f2.client_id
AND zeus.type = f2.type
AND f2.updated_timestamp <= dates.week_info
)
AND zeus.type = 'hello-world'
AND zeus.STATUS = 'CURRENT'
GROUP BY dates.week_info
ORDER BY dates.week_info DESC LIMIT 8
But the problem I have is I am using limit 8 to get the above query working. I am trying to see if there is any way by which we can avoid using limit 8 and just use zeus.updated_timestamp value to get the past 8 weeks data in a similar output format as my current query is giving?
Output is coming like this from above query and I want it to be in this format only:
week_info week_number PROC
--------------------------------
2020-10-25 W44 100
2020-10-18 W43 101
2020-10-11 W42 109
2020-10-04 W41 134
2020-09-27 W40 982
2020-09-20 W39 187
2020-09-13 W38 765
2020-09-06 W37 234
Note:-
updated_timestamp column has full date in it like 2020-10-28 18:56:25:17
So 2 removals of LIMIT requested. The first, in the CTE, can be replaced by adding a WHERE clause in the outer select - "WHERE dates.week_info > 8 weeks ago" (I'll leave it to you to define 8 weeks ago. Also there are more efficient ways to make 8 dates than using a window function and scanning an unneeded table but that is your choice. Changing this will remove the LIMIT / WHERE need all together. Your CTE then looks something like:
select date_trunc('week', getdate() + INTERVAL '1 day')::DATE - (t.num * 7) - 1 as week_info
from (select 0 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) as t (num)
The second LIMIT is coming about because of the inequality in the JOIN clause which is causing a lot of row replication - I hope this is really what you need. There will only be 8 dates coming from the CTE AND having a GROUP BY on this date means that there will only be 8 rows of output. If there are only 8 possible rows there is no reason to have a LIMIT.
EDIT - merged code (untested):
WITH dates
AS (
select date_trunc('week', getdate() + INTERVAL '1 day')::DATE - (t.num * 7) - 1 as week_info
from (select 0 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) as t (num)
)
SELECT dates.week_info
,'W' || ceiling(date_part('week', dates.week_info + INTERVAL '1 day')) AS week_number
,COUNT(DISTINCT zeus.client_id) AS PROC
FROM data.active_values zeus
JOIN dates ON zeus.updated_timestamp <= dates.week_info
WHERE zeus.kites_version = (
SELECT MAX(kites_version)
FROM data.active_values f2
WHERE zeus.client_id = f2.client_id
AND zeus.type = f2.type
AND f2.updated_timestamp <= dates.week_info
)
AND zeus.type = 'hello-world'
AND zeus.STATUS = 'CURRENT'
GROUP BY dates.week_info
ORDER BY dates.week_info DESC
EDIT 2 - attempt to address correlated subquery issue:
If I understand correctly the where clause in question is just trying to ensure that only client_ids with values that match on kite_version are counted. A more direct (and less error prone) way to get this is to calculate the subgroup max directly. The below code attempts to do this but I don't have your data nor your business intent so this is an example of a better way to attack this type of requirement.
WITH dates
AS (
select date_trunc('week', getdate() + INTERVAL '1 day')::DATE - (t.num * 7) - 1 as week_info
from (select 0 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) as t (num)
),
active_values_plus AS (
SELECT client_id, updated_timestamp, type, status, kites_version, MAX(kites_version) OVER (PARTITION BY client_id, type) AS max_kites_version
FROM data.active_values
)
SELECT dates.week_info
,'W' || ceiling(date_part('week', dates.week_info + INTERVAL '1 day')) AS week_number
,COUNT(DISTINCT zeus.client_id) AS PROC
FROM active_values_plus zeus
JOIN dates ON zeus.updated_timestamp <= dates.week_info
WHERE zeus.kites_version = zeus.max_kites_version
AND zeus.type = 'hello-world'
AND zeus.STATUS = 'CURRENT'
GROUP BY dates.week_info
ORDER BY dates.week_info DESC

get count of records in every hour in the last 24 hour

i need number of records in each hour in the last 24 hours, i need my query to show 0 if there are no records in any of the particular hour for that day, i am just able to get data for hours that are in table.
SELECT TRUNC(systemdate,'HH24') + (trunc(to_char(systemdate,'mi')/10)*10)/24/60 AS date1,
count(*) AS txncount
FROM transactionlog
GROUP BY TRUNC(systemdate,'HH24') + (trunc(to_char(systemdate,'mi')/10)*10)/24/60 order by date1 desc;
result:
What should i do to get data in each hour of the last 24 hours?
Expected data:
record count in each hour for last 24 hours , starting from current date time.. if no record exist in that particular hour, 0 is shown.
The following might be what you need. It seems to work when I run it against the all_objects view.
WITH date_range
AS (SELECT TRUNC(sysdate - (rownum/24),'HH24') as the_hour
FROM dual
CONNECT BY ROWNUM <= 1000),
the_data
AS (SELECT TRUNC(created, 'HH24') as cr_ddl, count(*) as num_obj
FROM all_objects
GROUP BY TRUNC(created, 'HH24'))
SELECT TO_CHAR(dr.the_hour,'DD/MM/YYYY HH:MI AM'), NVL(num_obj,0)
FROM date_range dr LEFT OUTER JOIN the_data ao
ON ao.cr_ddl = dr.the_hour
ORDER BY dr.the_hour DESC
The 'date_range' generates a record for each hour over the past 24.
The 'the_data' does a count of the number of records in your target table based on the date truncated to the hour.
The main query then outer joins the two of them showing the date and the count from the sub-query.
I prefer both parts of the query in their own CTE because it makes the actual query very obvious and 'clean'.
In terms of your query you want this;
WITH date_range
AS (SELECT TRUNC(sysdate - (rownum/24),'HH24') as the_hour
FROM dual
CONNECT BY ROWNUM <= 24),
the_data
AS (SELECT TRUNC(systemdate, 'HH24') as log_date, count(*) as num_obj
FROM transactionlog
GROUP BY TRUNC(systemdate, 'HH24'))
SELECT TO_CHAR(dr.the_hour,'DD/MM/YYYY HH:MI AM'), NVL(trans_log.num_obj,0)
FROM date_range dr LEFT OUTER JOIN the_data trans_log
ON trans_log.log_date = dr.the_hour
ORDER BY dr.the_hour DESC
You could use this:
WITH transactionlog AS
(
SELECT TO_DATE('03/05/2018 01:12','dd/mm/yyyy hh24:mi') AS systemdate, 60 AS value
FROM dual UNION ALL
SELECT TO_DATE('03/05/2018 01:32','dd/mm/yyyy hh24:mi'), 35 FROM dual UNION ALL
SELECT TO_DATE('03/05/2018 09:44','dd/mm/yyyy hh24:mi'), 31 FROM dual UNION ALL
SELECT TO_DATE('03/05/2018 08:56','dd/mm/yyyy hh24:mi'), 24 FROM dual UNION ALL
SELECT TO_DATE('03/05/2018 08:02','dd/mm/yyyy hh24:mi'), 98 FROM dual
)
, time_range AS
(
SELECT TRUNC(sysdate, 'hh24') - 23/24 + (ROWNUM - 1) / 24 AS time1
FROM all_objects
WHERE ROWNUM <= 24
)
SELECT TO_CHAR(r.time1, 'mm/dd/yyyy hh:mi AM') AS date1,
COUNT(t.systemdate) AS txncount
FROM time_range r
LEFT JOIN transactionlog t
ON r.time1 = TRUNC(t.systemdate, 'hh24') --+ 1/24
GROUP BY r.time1
ORDER BY r.time1;
If 01:12 AM means 02:00 AM in result, then omit the comment code.
Reference: Generating Dates between two date ranges_AskTOM
Edited: For OP, you only need this:
WITH time_range AS
(
SELECT TRUNC(sysdate, 'hh24') - 23/24 + (ROWNUM - 1) / 24 AS time1
FROM all_objects
WHERE ROWNUM <= 24
)
SELECT TO_CHAR(r.time1, 'mm/dd/yyyy hh:mi AM') AS date1,
COUNT(t.systemdate) AS txncount
FROM time_range r
LEFT JOIN transactionlog t
ON r.time1 = TRUNC(t.systemdate, 'hh24') --+ 1/24
GROUP BY r.time1
ORDER BY r.time1;
You need to write a last 24 hours calendar table,then LEFT JOIN calendar table by Original table.
count(t.systemdate) need to count t.systemdate because t.systemdate might be NULL
connect by create last 24 hours calendar table
on clause TO_CHAR(t.systemdate,'YYYY/MM/DD hh24','nls_language=american') make sure the dateformat language are the same.
You can try this.
WITH Hours as
(
select sysdate + (level/24) dates
from dual
connect by level <= 24
)
SELECT TO_CHAR(h.dates,'YYYY-MM-DD HH24') AS dateHour, count(t.systemdate) AS totlecount
FROM Hours h
LEFT JOIN transactionlog t
on TO_CHAR(t.systemdate,'YYYY/MM/DD hh24','nls_language=american')
= TO_CHAR(h.dates,'YYYY/MM/DD hh24','nls_language=american')
GROUP BY h.dates
ORDER BY h.dates
sqlfiddle:http://sqlfiddle.com/#!4/73db7/2
CTE Recursive Version
You can also use CTE Recursive to write a calendar table
WITH Hours(dates,i) as
(
SELECT sysdate,1
FROM DUAL
UNION ALL
SELECT sysdate + (i/24),i+1
FROM Hours
WHERE i<24
)
SELECT TO_CHAR(h.dates,'YYYY-MM-DD HH24') AS dateHour, count(t.systemdate) AS totlecount
FROM Hours h
LEFT JOIN transactionlog t
on TO_CHAR(t.systemdate,'YYYY/MM/DD hh24','nls_language=american')
= TO_CHAR(h.dates,'YYYY/MM/DD hh24','nls_language=american')
GROUP BY h.dates
ORDER BY h.dates
sqlfiddle:http://sqlfiddle.com/#!4/73db7/7

SQL: Return '0' for a row if it doesn't exist

I have a SQL query which displays count, date, and time.
This is what the output looks like:
And this is my SQL query:
select
count(*),
to_char(timestamp, 'MM/DD/YYYY'),
to_char(timestamp, 'HH24')
from
MY_TABLE
where
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by
to_char(timestamp, 'MM/DD/YYYY'), to_char(timestamp, 'HH24')
Now, in COUNT column, I want to display 0 if the count doesn't exist for that hour. So on 3/2/2016 at 8am, the count was 6. Then at 9am the count was 0 so that row didn't get displayed. I want to display that row. And at 10am & 11am, the counts are displayed then it just goes to next day.
So how do I display count of 0? I want to display 0 count for each day every hour doesn't matter if it's 0 or 6 or whatever. Thanks :)
Use a partition outer join:
SELECT m.day,
h.hr,
COALESCE( freq, 0 ) AS freq
FROM ( SELECT LEVEL - 1 AS hr
FROM DUAL
CONNECT BY LEVEL <= 24
) h
LEFT OUTER JOIN
( SELECT COUNT(*) AS freq,
TO_CHAR( "timestamp", 'mm/dd/yyyy' ) AS day,
EXTRACT( HOUR FROM "timestamp" ) AS hr
FROM MY_TABLE
WHERE "timestamp" >= TIMESTAMP '2016-03-01 00:00:00'
GROUP BY
TO_CHAR( "timestamp", 'mm/dd/yyyy' ),
EXTRACT( HOUR FROM "timestamp" )
) m
PARTITION BY ( m.day, m.hr )
ON ( m.hr = h.hr );
Use a cte to generate numbers for all the hours in a day. Then cross join the result with all the possible dates from the table. Then left join on the cte which has all date and hour combinations, to get a 0 count when a row is absent for a particular hour.
with nums(n) as (select 1 from dual
union all
select n+1 from nums where n < 24)
,dateshrscomb as (select n,dt
from nums
cross join (select distinct trunc(timestamp) dt from my_table
where timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
) alldates
)
select count(trunc(m.timestamp)), d.dt, d.n
from dateshrscomb d
left join MY_TABLE m on to_char(m.timestamp, 'HH24') = d.n
and trunc(m.timestamp) = d.dt
and m.timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by d.dt, d.n
with cteHours(h) as (select 0 from dual
union all
select h+1 from cteHours where h < 24)
, cteDates(d) AS (
SELECT
trunc(MIN(timestamp)) as d
FROM
My_Table
WHERE
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
UNION ALL
SELECT
d + 1 as d
FROM
cteDates
WHERE
d + 1 <= (SELECT trunc(MAX(timestamp)) FROM MY_TABLE)
)
, datesNumsCross (d,h) AS (
SELECT
d, h
FROM
cteDates
CROSS JOIN cteHours
)
select count(*), to_char(d.d, 'MM/DD/YYYY'), d.h
from datesNumsCross d
LEFT JOIN MY_TABLE m
ON d.d = trunc(m.timestamp)
AND d.h = to_char(m.timestamp, 'HH24')
group by d.d, d.h
#VPK is doing a good job at answering, I just happened to be writing this at the same time as his last edit to generate a date hour cross join. This solution differs from his in that it will get all dates between your desired max and min. Where as his will get only the dates within the table so if you have a day missing completely it would not be represented in his but would in this one. Plus I did a little clean up on the joins.
Here is one way to do that.
Using Oracle's hierarchical query feature and level psuedo column, generate the dates and hours.
Then do an outer join of above with your data.
Need to adjust the value of level depending upon your desired range (This example uses 120). Start date needs to be set as well. It is ( trunc(sysdate, 'hh24')-2/24 ) in this example.
select nvl(c1.cnt, 0), d1.date_part, d1.hour_part
from
(
select
to_char(s.dt - (c.lev)/24, 'mm/dd/yyyy') date_part,
to_char(s.dt - (c.lev)/24, 'hh24') hour_part
from
(select level lev from dual connect by level <= 120) c,
(select trunc(sysdate, 'hh24')-2/24 dt from dual) s
where (s.dt - (c.lev)/24) < trunc(sysdate, 'hh24')-2/24
) d1
full outer join
(
select
count(*) cnt,
to_char(timestamp, 'MM/DD/YYYY') date_part,
to_char(timestamp, 'HH24') hour_part
from
MY_TABLE
where
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by
to_char(timestamp, 'MM/DD/YYYY'), to_char(timestamp, 'HH24')
) c1
on d1.date_part = c1.date_part
and d1.hour_part = c1.hour_part