Compare dates from two queries PL/SQL - sql

I have two queries , the first gets some data back from my table, the second query displays all half hour times between two specified dates. Is there a way of comparing the date results from query 1 from query 2 and merge the two results together when the date from query 2 doesn't exist in query 1 result.
I'll attach a little diagram to show what I mean.
Query 1:
SELECT
reading_date,
reading_value
FROM DCM_READING
WHERE reading_date BETWEEN TO_DATE('17-NOV-2019' || ' 000000', 'DD-MON-YYYY HH24MISS')
AND TO_DATE('19-NOV-2019' || ' 235959', 'DD-MON-YYYY HH24MISS')
ORDER BY reading_date;
Query 2:
select TO_DATE('17-NOV-2019' || ' 000000', 'DD-MON-YYYY HH24MISS') +
( level / 48 ) dt
from dual
connect by level <= ( 48 + ( 48 *
( TO_DATE('19-NOV-2019' || ' 000000', 'DD-MON-YYYY HH24MISS') -
TO_DATE('17-NOV-2019' || ' 000000', 'DD-MON-YYYY HH24MISS') )
)
) ;

You can enumerate the timestamps you want in a CTE, then bring the table with a left join:
with cte (reading_date) as (
select date '2020-11-17' from dual
union all
select reading_date + interval '30' minute
from cte
where reading_date + interval '30' minute < date '2020-11-19'
)
select c.reading_date, d.reading_value
from cte c
left join dcm_reading d on d.reading_date = c.reading_date
order by c.reading_date
I like to use recursive queries rather than Oracle specific connect by syntax, because they are standard SQL - but that's mostly a matter of taste, the logic remains the same.

Related

Get the last time before the call - SQL Query

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
;

Get all date from month - Oracle SQL

I have two parameters MONTH and YEAR, how get all date?
Eg. YEAR = 2021, MONTH = 8
Date
------
01.08.2021
02.08.2021
.....
31.08.2021
I'm a fan of recursive CTEs, because they are standard SQL. In Oracle, you can use one like this:
with cte(dte) as (
select to_date('2020' || '8', 'YYYYMM') -- the two parameters are '2020' and '8'
from dual
union all
select dte + interval '1' day
from cte
where dte < last_day(dte)
)
select *
from cte;
Here is a db<>fiddle.
select dt + level - 1 as date_
from (select to_date(to_char(:year , 'fm0000') ||
to_char(:month, 'fm00'), 'yyyymm') as dt from dual)
connect by level <= add_months(dt, 1) - dt
;
This is almost the same as MT0's answer, with a few minor differences and one that is not entirely minor.
The to_date function assumes a default of first day of the month, so it is not necessary to explicitly concatenate '01' to the year and month (although perhaps doing so makes the code easier to read for beginner programmers). In my opinion, that's just a matter of taste.
I separated the computation of the first day of the month into a subquery. No worries, the optimizer will merge it into the outer query, so there is no efficiency cost - but the code will be easier to maintain.
The non-trivial difference is in the connect by clause. Even though mathematically the formula is equivalent to
dt + level - 1 < add_months(dt, 1)
or, better (still equivalent!)
dt + level <= add_months(dt, 1)
in terms of processing they are not equivalent. If written in the form above (previous line of code), for each value of level, the runtime will perform a date arithmetic calculation followed by a date comparison.
On the other hand, by solving the inequality for level (as I did in my query), the date calculation is performed just once (rather than once for every row), and the comparison is simply level <= some calculated number.
Perhaps in this problem "efficiency" plays no role, but as a matter of good coding, we should "solve for level" whenever possible, for the reason I just gave.
Assuming you pass in the bind variables :year and :month, then you can use a hierarchical query:
SELECT TO_DATE(
TO_CHAR(:year, 'FM0000') || TO_CHAR(:month, 'FM00') || '01',
'YYYYMMDD'
) + LEVEL - 1 AS "Date"
FROM DUAL
CONNECT BY
TO_DATE(
TO_CHAR(:year, 'FM0000') || TO_CHAR(:month, 'FM00') || '01',
'YYYYMMDD'
) + LEVEL - 1
<
ADD_MONTHS(
TO_DATE(
TO_CHAR(:year, 'FM0000') || TO_CHAR(:month, 'FM00') || '01',
'YYYYMMDD'
),
1
)
sqlfiddle here

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

remove rows having more than one records in an INNER JOIN

I need to filter out rows having more than one b.STIME, b.A_PTY_NBR and b.B_PTY_NBR.
SELECT DISTINCT *
FROM star_idd a
INNER JOIN
star_vendor b
ON (
TO_DATE( a."DATES" || a."STIME", 'YYYYMMDDHH24MISS' ) BETWEEN TO_DATE( b."DATES" || b."STIME", 'YYYYMMDDHH24MISS' ) - INTERVAL '12' HOUR AND TO_DATE( b."DATES" || b."STIME", 'YYYYMMDDHH24MISS' ) + INTERVAL '12' HOUR
AND b.B_PTY_NBR = a.TRIM_A_NBR
AND b.A_PTY_NBR = a.TRIM_B_NBR )
Currently, there are two records in table a that matches 1 record in table b. The tendency is, it will create a same record in table b to match another record in table a. I just need to display only one row.
Any help is much appreciated!
Probably you meant to use ROW_NUMBER() function to remove the duplicates like
SELECT * FROM
(
SELECT DISTINCT a.*,
ROW_NUMBER() OVER(PARTITION BY b.STIME ORDER BY b.STIME) rn
FROM star_idd a
INNER JOIN
star_vendor b
ON (
TO_DATE( a."DATES" || a."STIME", 'YYYYMMDDHH24MISS' ) BETWEEN TO_DATE( b."DATES" || b."STIME", 'YYYYMMDDHH24MISS' ) - INTERVAL '12' HOUR AND TO_DATE( b."DATES" || b."STIME", 'YYYYMMDDHH24MISS' ) + INTERVAL '12' HOUR
AND b.B_PTY_NBR = a.TRIM_A_NBR
AND b.A_PTY_NBR = a.TRIM_B_NBR)) xx
WHERE rn = 1;

Oracle range query

I've found this query on this site (slightly modified it though) and it works OK. However,the result set doesn't show the range intervals where count(*) is 0. I tried to use the NVL function along with left joint, but couldn't make it work. Any ideas? (see query below)
Thanks
select
count(*) "# PRs Created",to_char(date_created, 'yyyy-mm-dd') as "Date Created",
to_char(date_created, 'hh24') || ':00:00 - ' || to_char(date_created + 1/24, 'hh24') || ':00:00' RANGE
FROM pr
where date_created between to_date(SYSDATE - 1000)and to_date(SYSDATE)
and 1 * to_char(date_created, 'hh24') between 0 and 24
group by to_char(date_created, 'hh24'), to_char(date_created + 1/24, 'hh24'),to_char(date_created, 'yyyy-mm-dd')
order by to_char(date_created, 'yyyy-mm-dd'), RANGE desc
You won't get a zero entry for non-existent intervals because there's nothing to count. To get this to work, you'll need to outer join to a table of date times:
with dates as (
select trunc(sysdate)-1000 + (rownum-1)/24 dt
from dual
connect by level <= 1000 * 24
)
select dates.dt, count(pr.date_created) from pr, dates
where trunc(pr.date_created (+), 'hh24')= dates.dt
group by dates.dt;
The with clause creates a "table" of data containing every hour from sysdate-1000 and today (the condition in your query). You need to outer join pr to this to see a row for intervals that have no entry in pr.