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.
Related
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.
create table Minutes(Minute varchar2(5));
create table orders(OrderID varchar(54), Orderplaced TIMESTAMP ,
Ordercompleted TIMESTAMP);
insert into orders
VALUES
('#1',TO_TIMESTAMP('2018-01-15 00:12:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 00:12:42', 'YYYY-MM-DD HH24:MI:SS'));
insert into orders
VALUES
('#2',TO_TIMESTAMP('2018-01-15 01:15:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 02:56:20', 'YYYY-MM-DD HH24:MI:SS'));
insert into orders
VALUES
('#3',TO_TIMESTAMP('2018-01-15 01:20:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 03:00:20', 'YYYY-MM-DD HH24:MI:SS'));
insert into Minutes (Minute)
select to_char(trunc(sysdate) + interval '1' minute * (level - 1),
'HH24:MI') as minute
from dual
connect by level <= 1440;
select a.Minute, nvl(count(b.OrderID),0) as orders
from Minutes a
left join orders b
on a.Minute between to_char(cast( b.Orderplaced as date),'hh24:mi:ss') and
to_char(cast( b.Ordercompleted as date),'hh24:mi:ss')
where
a.Minute <= (select to_char(cast (sysdate as date),'hh24:mi:ss') from dual)
group by a.Minute
order by 1;
The processing time is too long and the result is undelivered as well.
It works fine with Integration testing. Please have a look once.
I ran your code, works OK for those test tables. However, I'd suggest a slight modification.
you don't have to CAST values from ORDERS table
Even worse is to CAST SYSDATE AS DATE - SYSDATE is a function that
returns DATE data type anyway
there's no need to select it from DUAL - you can use it "as is"
COUNT will return 0 even if there's nothing to return, so you can
omit NVL function
Here's the modified SELECT statement:
SELECT a.minute, COUNT (b.orderid) AS orders
FROM minutes a
LEFT JOIN orders b
ON a.minute BETWEEN TO_CHAR (b.orderplaced, 'hh24:mi:ss')
AND TO_CHAR (b.ordercompleted, 'hh24:mi:ss')
WHERE a.minute <= TO_CHAR (SYSDATE, 'hh24:mi:ss')
GROUP BY a.minute
ORDER BY 1;
What does it mean for you? I don't know. As I said, it works OK. Explain plan says that it performs full table scan of both MINUTES and ORDERS tables, so - if there's zillion rows in those tables, it might make a difference.
Consider creating indexes on columns you use; as you extract only time from the ORDERS table, those two would be function-based ones.
CREATE INDEX i1_min
ON minutes (minute);
CREATE INDEX i2_plac
ON orders (TO_CHAR (orderplaced, 'hh24:mi:ss'));
CREATE INDEX i3_compl
ON orders (TO_CHAR (ordercompleted, 'hh24:mi:ss'));
Then try again; hopefully, you'll see some improvement.
You said you're trying to get "the number of orders count per minute on a particular day", and later clarified that should be the current day. Your query is only looking at times - converted to strings - so it's looking at the same time slot across all records in your orders table. Really you want to restrict the found orders to the day you're interested in. Presumably your UAT environment just has much more data, across more days, than you created in IT.
You could just add a filter to restrict it to orders placed today:
select a.Minute, nvl(count(b.OrderID),0) as orders
from Minutes a
left join orders b
on a.Minute between to_char(cast( b.Orderplaced as date),'hh24:mi:ss') and
to_char(cast( b.Ordercompleted as date),'hh24:mi:ss')
and b.Orderplaced > trunc(sysdate) -- added this filter
where
a.Minute <= (select to_char(cast (sysdate as date),'hh24:mi:ss') from dual)
group by a.Minute
order by 1;
though you don't need any of the casting or subquery or nvl(), as #Littlefoot mentioned, so can simplify that a bit to:
select a.Minute, count(b.OrderID) as orders
from Minutes a
left join orders b
on a.Minute between to_char(b.Orderplaced,'hh24:mi:ss') and
to_char(b.Ordercompleted,'hh24:mi:ss')
and b.Orderplaced > trunc(sysdate)
where a.Minute <= to_char(sysdate,'hh24:mi:ss')
group by a.Minute
order by 1;
You're still doing a lot of conversions and comparing strings rather than dates/timestamps. It might be simpler to generate the minutes for that specific day in a CTE instead of a permanent table, and join using those values as well, without doing any further data conversions
with minutes (minute) as (
select cast(trunc(sysdate) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= (sysdate - trunc(sysdate)) * 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate) as timestamp)
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
I've included rows with no ordercompleted date, though it isn't clear if you want to count those.
You could also join on just the orderplaced date being today, which looks a bit odd, and do a conditional count:
with minutes (minute) as (
select cast(trunc(sysdate) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= (sysdate - trunc(sysdate)) * 1440
)
select to_char(m.minute, 'HH24:MI') as minute,
count(case when o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
then o.orderid end) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate) as timestamp)
group by m.minute
order by m.minute;
Either way this assumes you have an index on orderplaced.
Look at the execution plans for your original query and these options and any others suggested, and test with realistic data, to see which is the best approach for your actual data and requirements.
To look for records for a different, full, day, change the sysdate references to a date/timestamp literal like timestamp '2018-01-15 00:00:00' or something relative like trunc(sysdate-1), and include an end-date on the orderplaced; and remove the end-time filter in the CTE; e.g.:
with minutes (minute) as (
select cast(trunc(sysdate - 1) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate - 1) as timestamp)
and o.orderplaced < cast(trunc(sysdate - 1) as timestamp) + interval '1' day
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
or
with minutes (minute) as (
select timestamp '2018-01-15 00:00:00' + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= timestamp '2018-01-15 00:00:00'
and o.orderplaced < timestamp '2018-01-16 00:00:00'
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
If you want to include rows where the placed and completed times are in the same minute, but still otherwise want to exclude rows from the minute they were placed, you'll need a bit more logic; maybe something like:
with minutes (minute) as (
select timestamp '2018-01-15 00:00:00' + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= timestamp '2018-01-15 00:00:00'
and o.orderplaced < timestamp '2018-01-16 00:00:00'
and ((trunc(o.ordercompleted, 'MI') > trunc(o.orderplaced, 'MI')
and o.orderplaced <= m.minute)
or (trunc(o.ordercompleted, 'MI') = trunc(o.orderplaced, 'MI')
and o.orderplaced < m.minute + interval '1' minute))
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
If you need further refinements you'll need to modify the clauses to suit, which might need a bit of experimentation.
Oracle SQL:
I been trying to get this some snippet of query working. When you run it, it prompts you for an hour, and displays no records. I don’t want the query to have a static hour as it will need to be run 4 times a day.
So from the ‘&date’ input, I want it to show data for the past 24 hours. Is that possible?
dt_time = timestamp(6) field
select distinct to_char(dt_ time,'dd/mm/yyyy hh24'), fault_description
from order
where to_char(dt_time,'hh24') <= '&date' -24
order by to_char(dt_ time,'dd/mm/yyyy hh24');
Example, if you enter 10 (when query executed) it will show the data from 10(:00) through to 10(:00)next day
[Hope there is enough info for someone to answer, please]
Use a combination of between and interval:
where dt_time between
to_date(&date, 'ddmmyyyy hh24') and
to_date(&date, 'ddmmyyyy hh24') + interval '24' hour
You'll need to pass/parse the input date as a full date and not only the hour part, in order to prevent unexpected results.
You can use NUMTODSINTERVAL in a CTE to get the hour as user input.
WITH t_hour( h ) AS
( SELECT NUMTODSINTERVAL(&d,'HOUR' ) h FROM DUAL
)
SELECT DISTINCT TO_CHAR(dt_time,'dd/mm/yyyy hh24'),
fault_description
FROM ORDER
CROSS JOIN t_hour
WHERE dt_time BETWEEN TRUNC(SYSDATE) + h AND TRUNC(SYSDATE) + 1 + h
ORDER BY 1;
You need to make use of SYSDATE as you are fetching last one day data.
Also if you add or subtract number with a date column the offset will be number of days, not number of hours. So you need to use &date/24
Try this:
select distinct to_char(dt_time,'dd/mm/yyyy hh24'), fault_description
from order
where dt_time - &date / 24 between TRUNC(sysdate-1) and TRUNC(sysdate)
order by to_char(dt_ time,'dd/mm/yyyy hh24');
Please comment.
WHERE DATEDIFF(SYSDATE,DT_TIME) * 24 < &DATE AND DT_TIME < SYSDATE
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
This question already has an answer here:
Using IW and MM in Oracle
(1 answer)
Closed 9 years ago.
I don't know what's wrong with my query. I just put 'MM' in my query to get the monthly result. But when I run it, it gives me a daily result in 365 days, instead of monthly result. Please help me.
Here's my query:
SELECT 'Data'
|| ',' || TO_CHAR(d.dtime_day, 'MM/dd/yyyy')
|| ',' || NVL(o.cnt_opened, 0) --as cnt_opened
|| ',' || NVL(c.cnt_closed, 0) --as cnt_closed
FROM owner_dwh.dc_date d
LEFT JOIN (
SELECT
TRUNC(t.create_time, 'MM') AS report_date,
count(*) AS cnt_opened
FROM app_account.otrs_ticket t
WHERE t.create_time BETWEEN SYSDATE - 365 AND SYSDATE
GROUP BY TRUNC(t.create_time, 'MM')
) o ON d.dtime_day = o.report_date
LEFT JOIN (
SELECT
TRUNC(t.close_time, 'MM') AS report_date,
count(*) AS cnt_closed
FROM app_account.otrs_ticket t
WHERE t.close_time BETWEEN SYSDATE - 365 AND SYSDATE
GROUP BY TRUNC(t.close_time, 'MM')
) c ON d.dtime_day = c.report_date
WHERE d.dtime_day BETWEEN SYSDATE - 365 AND SYSDATE
ORDER BY d.dtime_day;
Result:
Data,01/25/2013,0,0
Data,01/26/2013,0,0
Data,01/27/2013,0,0
Data,01/28/2013,0,0
Data,01/29/2013,0,0
Data,01/30/2013,0,0
Your initial query against DC_DATE is getting every date in the last 365 days. If you ran just that part:
SELECT 'Data'
||','||TO_CHAR(D.DTIME_DAY,'MM/dd/yyyy')
FROM OWNER_DWH.DC_DATE d
WHERE d.DTIME_DAY BETWEEN SYSDATE -365 AND SYSDATE
ORDER BY D.DTIME_DAY;
... you would expect to get 365 rows returned.
The subqueries you are outer-joining to are only going to return a summary count for the first day of each month. So when you join you will get an actual value (which could be zero) on the first of each month, but always zero on every other date. You could avoid that just by adjusting your where clause, e.g.:
WHERE d.DTIME_DAY BETWEEN SYSDATE -365 AND SYSDATE
AND d.DTIME_DAY = TRUNC(s.DTIME_DAY, 'MM')
The that will only show you the first of each month, and the outer joins will still show dates with zero values if there is no matching data from the subqueries.