Using MM in Oracle [duplicate] - sql

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.

Related

ORACLE SQL QUERY SYSDATE

I need help with my query
select distinct count(item_number), creation_date
from EGP_SYSTEM_ITEMS_B ,
all I need to count the item number every month
for example
3-9-2020 count:29700
4-9-2020 count:29600
5-9-2020 Count:30000
and get the all date for the month and the previous month from creation_id or sysdate any of them
thanks
To count the number per month, you would aggregate by the month:
select trunc(creation_date, 'MON') as yyyymm, count(*)
from EGP_SYSTEM_ITEMS_B
group by trunc(creation_date, 'MON');
Your question is not entirely clear; perhaps you're trying to get
select TRUNC(CREATION_DATE), count(item_number)
from EGP_SYSTEM_ITEMS_B
WHERE TRUNC(CREATION_DATE)
IN (ADD_MONTHS(TRUNC(SYSDATE), -1),
TRUNC(SYSDATE),
ADD_MONTHS(TRUNC(SYSDATE), 1))
GROUP BY TRUNC(CREATION_DATE)
EDIT
Apparently OP wants a running monthly count, so something like:
WITH cteLimits (START_DATE, END_DATE)
AS (SELECT ADD_MONTHS(TRUNC(SYSDATE), -2), ADD_MONTHS(TRUNC(SYSDATE), -1) - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT ADD_MONTHS(TRUNC(SYSDATE), -1), TRUNC(SYSDATE) - INTERVAL '1' DAY FROM DUAL UNION ALL
SELECT TRUNC(SYSDATE), ADD_MONTHS(TRUNC(SYSDATE), 1) - INTERVAL '1' DAY FROM DUAL),
cteDay_totals
AS (SELECT TRUNC(CREATION_DATE) AS CREATION_DATE,
COUNT(*) AS DAY_TOTAL
FROM EGP_SYSTEM_ITEMS_B
GROUP BY TRUNC(CREATION_DATE))
SELECT l.START_DATE,
l.END_DATE,
SUM(d.DAY_TOTAL) AS MONTH_TOTAL
FROM cteLimits l
INNER JOIN cteDay_totals d
ON d.CREATION_DATE BETWEEN l.START_DATE AND l.END_DATE
GROUP BY l.START_DATE,
l.END_DATE

how to get record count per day in last week (7 days) oracle

hello i am using the following sql query to get records count per hour in last 24 hours:
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 where merchantcode='merc0003'
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 ;
I am trying to get record count per day in oracle for last 7 days .. can some one guide what i may do to get last 7 days by changing above query?
i tried following to get last 7 days, but no gain as yet:
WITH date_range
AS
(SELECT TRUNC(sysdate - (7)) as the_hour
FROM dual
CONNECT BY ROWNUM <= 7),
the_data
AS (SELECT TRUNC(systemdate, 'HH24') AS log_date, count(*) AS num_obj
FROM transactionlog where merchantcode='merc0003'
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 ;
Your mistake is you are not adding days; you are retrieving the same day seven times. Add level (or level - 1, whichever you want) to the day:
SELECT TRUNC(sysdate) - 7 + level AS the_hour
FROM dual
CONNECT BY ROWNUM <= 7
I'm surprised your initial CTE even worked. I had not heard of using CONNECT BY together with ROWNUM (although, having done some Googling, I understand that it works under some conditions. Still, I'm convinced that it's a misuse). To get a date range of the last seven days you'll want something like the following:
WITH date_range AS (
-- If you want to include today's date, add +1 to the date below
SELECT TRUNC(SYSDATE - LEVEL) AS the_date
FROM dual
CONNECT BY LEVEL <= 7
)
Putting this together with your existing query:
WITH date_range AS (
SELECT TRUNC(SYSDATE - LEVEL) AS the_date
FROM dual
CONNECT BY LEVEL <= 7
), the_data AS (
-- You still had 'HH24' here in the call to TRUNC()!
SELECT TRUNC(systemdate) AS log_date, count(*) AS num_obj
FROM transactionlog
WHERE merchantcode = 'merc0003'
GROUP BY TRUNC(systemdate)
)
SELECT dr.the_date, COALESCE(trans_log.num_obj, 0)
FROM date_range dr LEFT JOIN the_data trans_log
ON dr.the_date = trans_log.log_date
ORDER BY dr.the_date DESC;
Incidentally, you can just use aliases for your CTEs; you don't need to give them full names only to alias them again.

fails in user acceptance

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.

Date conversion issue in sql

I would like to convert the payment day field from AUTO_TABLE into a day of the month. The payment day is stored in string format and the first nine days are stored as single character. I want to append a '0' string before the payment day for day 1-9 and convert the resulting string into 'DD'. The query worked for two months, but Oracle throws an error stating "invalid Month" when I attempt to convert the string into a date. How can I convert the payment day into two character decimals and proceed to concatenate day with the current month and year? Thanks for your help.
Select case when Payment_Day <> to_char(sysdate, 'dd')
then Payment_Day
end as Payment_day,
Payment_Day2,
trunc(sysdate) - 8 as DateEdit2,
trunc(sysdate) - 15 DateEdit1
From(
Select case when Payment_Day2 > trunc(sysdate)
then Payment_day2 - 31
else Payment_Day2 end as Payment_Day2,
Payment_Day, theSysdate as theSysdate
From(
Select distinct to_date(Payment_Day2, 'MM/DD/YYYY') as Payment_Day2,
Payment_Day, theSysdate
From(
Select thePIDM,
to_char(DateEdit, 'MM') || '/' || to_char(Payment_Day, '00') || '/' || to_char(sysdate, 'YYYY') as Payment_Day2,
to_char(Payment_Day) as Payment_Day, Trunc(theSysdate) theSysdate
From (
Select distinct PIDM as thePIDM,
to_char(Payment_Day) as Payment_Day,
trunc(sysdate) as DateEdit,
to_char(sysdate, 'DD') as theSysdate
from AUTO_TABLE
Group by PIDM, to_char(Payment_Day)
)
)
Order by Payment_Day2
)
Order by Payment_Day2
)
The query worked for two months. Yes you were lucky the run it in July and August, both having 31 days.
The problem is in the line
to_char(DateEdit, 'MM') || '/' || to_char(Payment_Day, '00') || '/' || to_char(sysdate, 'YYYY') as Payment_Day2,
which mix the payment_day with the current month (from sysdate). This leads to invalid dates such as 09/ 31/2015.
The remedy is in reducing the payment day to the last day of the current month
-- instead of
-- to_char(Payment_Day) as Payment_Day,
-- limit the payment day to the last day of the current month
to_char(least(to_number(Payment_Day),CAST(to_char(LAST_DAY(sysdate),'dd') AS INT))) as Payment_Day,

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.