I have a table called maximo_products and have a field called product_date.
I need to display all distinct dates which are not in in maximo_products table in MON-YYYY format
between Jan-2014 and Dec-2014.
How can do this?
SQLFiddle with table structure and records.
Sample output (dates which are not available in table)
MAR-2014
APR-2014
JUN-2014
JUL-2014
SEP-2014
To get the missing months, you need to generate all the months. The following uses a simple formulation for getting 12 months. It then uses not in to figure out which have no values:
with mons as (
select rownum r, add_months('01-JAN-2014', rownum - 1) as mon
from dual
connect by rownum <= 12
)
select *
from mons
where not exists (select 1
from maximo_products mp
where to_char(mp.product_date, 'YYYY-MM') =
to_char(mons.mon, 'YYYY-MM')
)
order by r;
EDIT:
You can move the definition of mons into a subquery:
select *
from (select rownum r, add_months('01-JAN-2014', rownum - 1) as mon
from dual
connect by rownum <= 12
) mons
where not exists (select 1
from maximo_products mp
where to_char(mp.product_date, 'YYYY-MM') =
to_char(mons.mon, 'YYYY-MM')
)
order by r;
Related
I need to get the data that generates count of total ID by date between date_active and date_end using date ranges for each. If the dates are crossing each other the ID will adding up. here is the data I have right now,
TABLE CONTRACT:
ID DATE_ACTIVE DATE_END
1 05-FEB-13 08-NOV-13
1 21-DEC-18 06-OCT-19
2 05-FEB-13 27-JAN-14
3 05-FEB-13 07-NOV-13
4 06-FEB-13 02-NOV-13
4 25-OCT-14 13-APR-16
TABLE CALENDAR:
DT
05-FEB-13
06-FEB-13
07-FEB-13
08-FEB-13
09-FEB-13
..-DEC-19
what I want out is basically like this:
DT COUNT(ID)
05-FEB-13 3
06-FEB-13 4
07-FEB-13 4
08-FEB-13 4
09-FEB-13 4
10-FEB-13 4
....
03-NOV-13 3
....
08-NOV-13 2
09-NOV-13 1
....
28-JAN-14 0
....
25-OCT-14 1
....
13-APR-16 1
14-APR-16 0
....
21-DEC-18 1
....
06-OCT-19 1
07-OCT-19 0
....
....
And here is my query to get that result
with contract as (
select * from contract
where id in ('1','2','3','4')
)
,
cal as
(
select TRUNC (SYSDATE - ROWNUM) dt
from dual
connect by rownum < sysdate - to_date('05-FEB-13')
)
select aa.dt,count(distinct bb.id)id from cal aa
left join contract bb on aa.dt >= bb.date_active and aa.dt<= bb.date_end
group by aa.dt
order by 1
but the problem is I have 6 mio of ID and if I use this kind of query, the result maybe will take forever, and I'm having a hard times to figured out how to get the result with different query. It will be my pleasure if somebody can help me out of this. Thank you so much.
If you group your events by date_active and date_end, you will get the numbers of events which have started and ended on each separate day.
Not a lot of days have passed between 2013 and 2019 (about 2 000), so the grouped resultsets will be relatively short.
Now that you have the two groups, you can notice that the number of events on each given date is the number of events which have started on or before this date, minus the number of events which have finished on or before this date (I'm assuming the end dates are non-inclusive).
In other words, the number of events on every given day is:
The number of events on the previous date,
plus the number of events started on this date,
minus the number of events ended on this date.
This can be easily done using a window function.
This will require a join between the calendar table and the two groups, but fortunately all of them are relatively short (thousands of records) and the join would be fast.
Here's the query: http://sqlfiddle.com/#!4/b21ce/5
WITH cal AS
(
SELECT TRUNC (to_date('01-NOV-13') - ROWNUM) dt
FROM dual
CONNECT BY
rownum < to_date('01-NOV-13')- to_date('01-FEB-13')
),
started_on AS
(
SELECT date_active AS dt, COUNT(*) AS cnt_start
FROM contract
GROUP BY
date_active
),
ended_on AS
(
SELECT date_end AS dt, COUNT(*) AS cnt_end
FROM contract
GROUP BY
date_end
)
SELECT dt,
SUM(COALESCE(cnt_start, 0) - COALESCE(cnt_end, 0)) OVER (ORDER BY dt) cnt
FROM cal c
LEFT JOIN
started_on s
USING (dt)
LEFT JOIN
ended_on e
USING (dt)
(I used a fixed date instead of SYSDATE to keep the resultset short, but the idea is the same)
This query requires that the calendar starts before the earliest event, otherwise every result will be off by a fixed amount, the number of events before the beginning of the calendar.
You can replace the fixed date in the calendar condition with (SELECT MIN(date_active) FROM contract) which is instant if date_active is indexed.
Update:
If your contract dates can overlap and you want to collapse multiple overlapping contracts into a one continuous contract, you can use window functions to do so.
WITH cal AS
(
SELECT TRUNC (to_date('01-NOV-13') - ROWNUM) dt
FROM dual
CONNECT BY
rownum <= to_date('01-NOV-13')- to_date('01-FEB-13')
),
collapsed_contract AS
(
SELECT *
FROM (
SELECT c.*,
COALESCE(LAG(date_end_effective) OVER (PARTITION BY id ORDER BY date_active), date_active) AS date_start_effective
FROM (
SELECT c.*,
MAX(date_end) OVER (PARTITION BY id ORDER BY date_active) AS date_end_effective
FROM contract c
) c
) c
WHERE date_start_effective < date_end_effective
),
started_on AS
(
SELECT date_start_effective AS dt, COUNT(*) AS cnt_start
FROM collapsed_contract
GROUP BY
date_start_effective
),
ended_on AS
(
SELECT date_end_effective AS dt, COUNT(*) AS cnt_end
FROM collapsed_contract
GROUP BY
date_end_effective
)
SELECT dt,
SUM(COALESCE(cnt_start, 0) - COALESCE(cnt_end, 0)) OVER (ORDER BY dt) cnt
FROM cal c
LEFT JOIN
started_on s
USING (dt)
LEFT JOIN
ended_on e
USING (dt)
http://sqlfiddle.com/#!4/adeba/1
The query might seem bulky, but that's to make it more efficient, as all these window functions can be calculated in a single pass over the table.
Note however that this single pass relies on the table being sorted on (id, date_active) so an index on these two fields is crucial.
Firstly, row_number() over (order by id,date_active) analytic function is used in order to generate unique ID values those will be substituted in
connect by level <= ... and prior id = id syntax to get unpivoted hierarchical data :
with t0 as
(
select row_number() over (order by id,date_active) as id, date_active, date_end
from contract
), t1 as
(
select date_active + level - 1 as dt
from t0
connect by level <= date_end - date_active + 1
and prior id = id
and prior sys_guid() is not null
)
select dt, count(*)
from t1
group by dt
order by dt
Demo
I need a query to get the list of students attended there class for atleast 15 days in a month for continuous 4 months.
table maybe like
studentid monthyear attendance
1 Apr2018 16
1 May2018 23
1 Jun2018 18
1 Jul2018 16
1 Aug2018 25
2 Apr2018 2
2 May2018 15
and so on...
Db fiddle
Try this query:
select #rn := 0;
select studentid from (
select studentid, month(dt) - (#rn := #rn + 1) grp from (
select * ,
str_to_date(concat('01 ', insert(monthyear, 4, 0, ' ')), '%d %M %Y') dt
from tbl
where attendance >= 15 --only those records, where attenadnce is at least 15
) a where year(dt) = 2018 --particular year
order by studentid,dt
) a group by studentid,grp having count(*) >= 4
Demo - I exapnded your data with some more cases :)
The idea is simple - if student has attended for some consecutive months, consecutive months would increment by one, just like row number, so I used difference between months and row numbers - for consecutive months, the difference should be constant, so it's enought to group by that difference and take those groups, where count is >= 4 :)
UPDATE
For SQL Server:
select studentid from (
select studentid, month(dt) - row_number() over (order by studentid, dt) grp from (
select * ,
cast(concat('01 ', stuff(monthyear, 4, 0, ' ')) as date) dt
from tbl
where attendance >= 15 --only those records, where attenadnce is at least 15
) a where year(dt) = 2018 --particular year
) a group by studentid, grp having count(*) >= 4
SQL Server demo
In general, a simple selft join that would catch the difference of months would suffice
In this case, a conversion of the column monthyear is required in the join command itself
The query, without the conversion :
SELECT t1.studentid, count(*) as cnt
FROM
table t1
INNER JOIN table t2 ON t1.studentid = t2.studentid AND
t2.attendance >= 15
AND t1.monthyear BETWEEN t2.monthyear AND (t2.monthyear - 3)
WHERE
t1.attendance >= 15
GROUP BY
studentid
HAVING
count(*) >=4
The conversion is as follows:
STR_TO_DATE(
CONCAT(SUBSTR(t1.monthyear,1, LENGTH(t1.monthyear) - 4),' ', RIGHT(t1.monthyear, 4), %M %Y)
so the query should be:
SELECT t1.studentid, count(*) as cnt
FROM
table t1
INNER JOIN table t2 ON t1.studentid = t2.studentid AND
t2.attendance >= 15
AND STR_TO_DATE(
CONCAT(SUBSTR(t1.monthyear,1, LENGTH(t1.monthyear) - 4),' ', RIGHT(t1.monthyear, 4), %M %Y) BETWEEN STR_TO_DATE(
CONCAT(SUBSTR(t2.monthyear,1, LENGTH(t2.monthyear) - 4),' ', RIGHT(t2.monthyear, 4), %M %Y) AND DATE_SUB(STR_TO_DATE(
CONCAT(SUBSTR(t2.monthyear,1, LENGTH(t2.monthyear) - 4),' ', RIGHT(t2.monthyear, 4), %M %Y), INTERVAL 3 MONTH)
WHERE
t1.attendance >= 15
GROUP BY
studentid
HAVING
count(*) >=4
I think this is the simplest method:
select distinct studentid
from (select t.*, cast(monthyear as date) as my,
lag(cast(monthyear as date), 3) over (partition by studentid order by cast(monthyear as date)) as prev_my
from tbl t
where attendance >= 15
) t
where prev_my = dateadd(month, -3, my);
Here is a db<>fiddle.
The logic is pretty simple:
Only consider rows that satisfy the attendance criterion.
Use LAG() to look at the 3rd record in past.
If all months meet the attendance criterion, then this will be exactly 3 months before.
The select distinct is because you want students, not the specific periods.
I have the following statement to extract the date, hour and number of users from a table in a Teradata DB . . .
SELECT
CAST(end_time AS DATE) AS end_date,
EXTRACT(HOUR FROM end_time) AS end_hour,
COUNT(users) AS total_users
FROM table
GROUP BY end_date, end_hour
When using the extract() function, my resultset contains missing hours where there is no activity by users over a 24 hour period... I'm wondering is there any technique to account for these missing hours in my resultset?
I can't creat a lookup table to reference as I don't have the necessary permissions to create a table on this DB.
Any help would be appreciated!
sys_calendar.calendar to generate the requested dates (change the range as needed)
WITH RECURSIVE to generate the hours
with recursive cte_hours (hr)
as
(
select 0 from (select 1) t(c)
union all select hr + 1 from cte_hours where hr < 23
)
select c.calendar_date as dt
,h.hr as hr
,zeroifnull(t.total_users) as total_users
from sys_calendar.calendar as c
cross join cte_hours as h
left join (select cast(end_time as date) as end_date
,extract(hour from end_time) as end_hour
,count(users) as total_users
from mytable t
group by end_date
,end_hour
) t
on t.end_date = c.calendar_date
and t.end_hour = h.hr
where c.calendar_date between current_date - 10 and current_date
order by dt,hr
;
For #GordonLinoff
select 0
0
select 1
1
select 0
union all
select 1
[3888] A SELECT for a UNION,INTERSECT or MINUS must reference a table.
select 0 from (select 1 as c) t
union all
select 1 from (select 1 as c) t
0
1
or
select 0 from (select 1) t(c)
union all
select 1 from (select 1) t(c)
0
1
If you want all hours from all days in the database, then you can generate the rows using cross join and then use left join to bring in results:
SELECT d.end_date,
EXTRACT(HOUR FROM end_time) AS end_hour,
COUNT(t.users) AS total_users
FROM (select distinct CAST(end_time AS DATE) AS end_date from table) d CROSS JOIN
(select distinct EXTRACT(HOUR FROM end_time) AS end_hour from table) h LEFT JOIN
table t
ON t.end_date = d.end_date and t.end_hour = d.end_hour
GROUP BY e.end_date, h.end_hour;
If all hours are not represented, you can use an explicit list:
SELECT d.end_date,
EXTRACT(HOUR FROM end_time) AS end_hour,
COUNT(t.users) AS total_users
FROM (select distinct CAST(end_time AS DATE) AS end_date from table) d CROSS JOIN
(select * from (select 0 as end_hour) t UNION ALL
select * from (select 1 as end_hour) t UNION ALL
. . .
) h LEFT JOIN
table t
ON t.end_date = d.end_date and t.end_hour = d.end_hour
GROUP BY e.end_date, h.end_hour;
I want to select with an oracle sql statement the records with a 6 month time interval.
Example
01/06/2011 AMOUNT
01/12/2011 AMOUNT
01/06/2012 AMOUNT
01/12/2012 AMOUNT
And so on
How can I do this with oracle sql?
select ADD_MONTHS(trunc(sysdate), (rownum - 1) * 6) some_date
from dual
connect by level <= 5;
SOME_DATE
-----------
18.04.2014
18.10.2014
18.04.2015
18.10.2015
18.04.2016
WITH got_r_num AS
(
SELECT t.* -- OR WHATEVER YOU WANT
, DENSE_RANK () OVER ( PARTITION BY TRUNC (created_date, 'MONTH')
ORDER BY TRUNC (created_date) -- DESC
) AS r_num
FROM test_table
WHERE MOD ( MONTHS_BETWEEN ( TRUNC (SYSDATE)
, TRUNC (created_date)
)
, 6
) = 0
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num = 1
;
Have a look here please .
If you need to sum of all records of 6 months in the AMOUNT field:
You can subquery with sum function and query with CONNECT BY LEVEL
SELECT x AS l_date,
(
SELECT sum(your_data)
FROM your_table
WHERE table_date >= x
AND table_date < add_months(x,6)
)AMOUNT
FROM(
SELECT add_months(to_date('01/06/2011','dd/mm/yyyy'),(LEVEL-1)*6) x
FROM dual
CONNECT BY LEVEL <= 4
);
I have a table with a column "Date". The date will be displayed in a calendar in a cyclic form. For example the records date will be shown in the calendar in a certain day each week till a specific date (let's say TerminationDate). To summarize in my table I have the Date and the TerminationDate columns like this:
Table:
Title | Date | TerminationDate
------------------------------
t1 | d1 | td1
and I want to achieve something like this:
From query:
Title | Date | TerminationDate
------------------------------
t1 | d1+7 | td1
t1 | d1+14| td1
t1 | d1+21| td1
.................... till Date < TerminationDate
Does anyone have any idea how to achieve this in Oracle?
This should do the trick
select distinct title, date + ( level * 7 ), termination_date
from table
connect by date + ( level * 7 ) < termination_date
EDIT:
Forget about above query, since the rows must be connected only with itself there has to be
connect_by prior title = title
but that means a loop must be created. Unfortunately Oracle connect by clause throws an error if there is a loop whatsoever. Even if you use
date + ( level * 7 ) < termination_date
Oracle still stops execution immediately where it detects a loop at runtime. Using nocycle returns the result, but that returns only the first record which is date + 7
ANSWER:
So i had to approach to the problem in a different way
select t.*, date + (r * 7) as the_date
from table t,
(select rownum as r
from dual
connect by level < (select max(weeks) --max date interval as weeks to be used to repeat each row as much, if you know the max weeks difference you can use the constant number instead of this sub-query
from (select ceil((termination_date - date) / 7) as weeks
from table ))
)
where r < ceil((termination_date - date) / 7)
Let me know is there is any conufsion or performance problem
I have not tested the query ,but it should work like as you need
SELECT t1, d1 + (7 * LEVEL), termination_date
FROM tab
WHERE d1 + (7 * LEVEL) < termination_date
CONNECT BY LEVEL <= CEIL( (termination_date - d1) / 7);
EDIT
SELECT DISTINCT t1,dt,termination_date
FROM(
SELECT t1, d1 + (7 * LEVEL) dt, termination_date
FROM tab
WHERE d1 + (7 * LEVEL) < termination_date
CONNECT BY LEVEL <= CEIL( (termination_date - d1) / 7)
);
Here's one more way to do it
SELECT
*,
date + ( ROWNUM * 7 ) as modified_date
FROM (
SELECT
title,
date,
termination_date
FROM
table
) WHERE date + ( ROWNUM * 7 ) < termination_date