How to group by dates into quarterly manner - sql

Hi I have table with column ID and DATES I want to make another column which store dates group by quarterly.
For example
Table
ID DATES
123 5/1/2005
123 7/1/2001
123 4/1/2003
123 2/1/2002
123 6/1/2005
123 6/1/2004
expected output:
ID DATES QUATER
123 5/1/2005 Q2-2005
123 7/1/2001 Q3-2001
123 4/1/2003 Q2-2003
123 2/1/2002 Q1-2002
123 6/1/2005 Q2-2005
123 6/1/2004 Q2-2004

You can do this with to_char(), like so:
with sample_data as (select 123 id, to_date('05/01/2005', 'mm/dd/yyyy') dt from dual union all
select 123 id, to_date('07/01/2001', 'mm/dd/yyyy') dt from dual union all
select 123 id, to_date('04/01/2003', 'mm/dd/yyyy') dt from dual union all
select 123 id, to_date('02/01/2002', 'mm/dd/yyyy') dt from dual union all
select 123 id, to_date('06/01/2005', 'mm/dd/yyyy') dt from dual union all
select 123 id, to_date('06/01/2004', 'mm/dd/yyyy') dt from dual)
---- end of mimicking a table called sample_data that has your input data in it. See SQL below:
select id,
dt,
TO_CHAR(sd.dt, '"Q"q-yyyy') qtr
from sample_data sd;
ID DT QTR
---------- ---------- -------
123 05/01/2004 Q2-2005
123 07/01/2001 Q3-2001
123 04/01/2003 Q2-2003
123 02/01/2002 Q1-2002
123 06/01/2005 Q2-2005
123 06/01/2004 Q2-2004

Related

Month counts between dates

I have the below table. I need to count how many ids were active in a given month. So thinking I'll need to create a row for each id that was active during that month so that id can be counted each month. A row should be generated for a term_dt during that month.
active_dt term_dt id
1/1/2018 101
1/1/2018 5/15/2018 102
3/1/2018 6/1/2018 103
1/1/2018 4/25/18 104
Apparently this is a "count number of overlapping intervals" problem. The algorithm goes like this:
Create a sorted list of all start and end points
Calculate a running sum over this list, add one when you encounter a start and subtract one when you encounter an end
If two points are same then perform subtractions first
You will end up with list of all points where the sum changed
Here is a rough outline of the query. It is for SQL Server but could be ported to any RDBMS that supports window functions:
WITH cte1(date, val) AS (
SELECT active_dt, 1 FROM #t AS t
UNION ALL
SELECT COALESCE(term_dt, '2099-01-01'), -1 FROM #t AS t
-- if end date is null then assume the row is valid indefinitely
), cte2 AS (
SELECT date, SUM(val) OVER(ORDER BY date, val) AS rs
FROM cte1
)
SELECT YEAR(date) AS YY, MONTH(date) AS MM, MAX(rs) AS MaxActiveThisYearMonth
FROM cte2
GROUP BY YEAR(date), MONTH(date)
DB Fiddle
I was toying with a simpler query, that seemed to do the trick, for Oracle:
with candidates (month_start) as (
select to_date ('2018-' || column_value || '-01','YYYY-MM-DD')
from
table
(sys.odcivarchar2list('01','02','03','04','05',
'06','07','08','09','10','11','12'))
), sample_data (active_dt, term_dt, id) as (
select to_date('01/01/2018', 'MM/DD/YYYY'), null, 101 from dual
union select to_date('01/01/2018', 'MM/DD/YYYY'),
to_date('05/15/2018', 'MM/DD/YYYY'), 102 from dual
union select to_date('03/01/2018', 'MM/DD/YYYY'),
to_date('06/01/2018', 'MM/DD/YYYY'), 103 from dual
union select to_date('01/01/2018', 'MM/DD/YYYY'),
to_date('04/25/2018', 'MM/DD/YYYY'), 104 from dual
)
select c.month_start, count(1)
from candidates c
join sample_data d
on c.month_start between d.active_dt and nvl(d.term_dt,current_date)
group by c.month_start
order by c.month_start
An alternative solution would be to use a hierarchical query, e.g.:
WITH your_table AS (SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, NULL term_dt, 101 ID FROM dual UNION ALL
SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, to_date('15/05/2018', 'dd/mm/yyyy') term_dt, 102 ID FROM dual UNION ALL
SELECT to_date('01/03/2018', 'dd/mm/yyyy') active_dt, to_date('01/06/2018', 'dd/mm/yyyy') term_dt, 103 ID FROM dual UNION ALL
SELECT to_date('01/01/2018', 'dd/mm/yyyy') active_dt, to_date('25/04/2018', 'dd/mm/yyyy') term_dt, 104 ID FROM dual)
SELECT active_month,
COUNT(*) num_active_ids
FROM (SELECT add_months(TRUNC(active_dt, 'mm'), -1 + LEVEL) active_month,
ID
FROM your_table
CONNECT BY PRIOR ID = ID
AND PRIOR sys_guid() IS NOT NULL
AND LEVEL <= FLOOR(months_between(coalesce(term_dt, SYSDATE), active_dt)) + 1)
GROUP BY active_month
ORDER BY active_month;
ACTIVE_MONTH NUM_ACTIVE_IDS
------------ --------------
01/01/2018 3
01/02/2018 3
01/03/2018 4
01/04/2018 4
01/05/2018 3
01/06/2018 2
01/07/2018 1
01/08/2018 1
01/09/2018 1
01/10/2018 1
Whether this is more or less performant than the other answers is up to you to test.

Only returning rows where date is greatest and one addition critieria

I have the following data
PET_REF XDATE TYPE
123 01/01/2017 OBJ
123 01/01/2017 OBJ
123 01/01/2017 OBJ
123 02/01/2017 LVE
456 01/01/2017 OBJ
456 01/01/2017 LVE
456 02/01/2017 OBJ
Is it possible to only return rows for PET_REF where the latest (by XDATE) TYPE is not LVE
So, for the data above, the output should be
PET_REF XDATE TYPE
456 01/01/2017 OBJ
456 01/01/2017 LVE
456 02/01/2017 OBJ
Use FIRST_VALUE analytic function
Select * from
(
select PET_REF, XDATE, TYPE, First_Value(TYPE)over(Partition by PET_REF order by XDATE desc) as Latest_Type
from yourtable
)a
Where Latest_Type <> 'LVE'
SQLFIDDLE DEMO
One way of solving this is to try putting them in a subquery.
SELECT *
FROM t
WHERE c1 IN (
SELECT c1
FROM t
WHERE (c1,c2) IN (SELECT c1, MAX(c2)
FROM t
GROUP BY 1)
AND c3 <> 'LVE');
Here's one option:
SQL> with test (pet_ref, xdate, type) as
2 (select 123, date '2017-01-01', 'obj' from dual union all
3 select 123, date '2017-01-01', 'obj' from dual union all
4 select 123, date '2017-01-01', 'obj' from dual union all
5 select 123, date '2017-01-02', 'lve' from dual union all --
6 select 456, date '2017-01-01', 'obj' from dual union all
7 select 456, date '2017-01-01', 'lve' from dual union all --
8 select 456, date '2017-01-02', 'obj' from dual
9 ),
10 inter as
11 (select pet_ref, type,
12 rank() over (partition by pet_ref order by xdate desc) rnk
13 from test
14 )
15 select * from test t
16 where t.pet_ref not in (select i.pet_ref from inter i
17 where i.rnk = 1
18 and i.type = 'lve');
PET_REF XDATE TYP
---------- ---------- ---
456 02/01/2017 obj
456 01/01/2017 lve
456 01/01/2017 obj
SQL>
Easier way to do this just by using order by :
SELECT *
FROM datatable
WHERE PET_REF LIKE (SELECT MAX(PET_REF) FROM datatable)
ORDER BY XDATE ASC, TYPE DESC;
Try the SQLFiddle

Query to obtain start and end dates from table with only start dates

I have a table with dates and values, something like:
START_DATE VALUE
------------ -----
01-JAN-2015 A
01-MAR-2015 B
01-AUG-2015 (null)
15-AUG-2015 A
01-SEP-2015 C
01-JAN-2016 B
01-JUN-2016 C
Each start_date represents the date when the value changed.
I'm trying to obtain an output that includes the end date as the next date (in chronological order) minus one day, that is:
START_DATE END_DATE VALUE
---------- ---------- -----
01-JAN-2015 28-FEB-2015 A
01-MAR-2015 31-JUL-2015 B
01-AUG-2015 14-AUG-2015 (null)
15-AUG-2015 31-AUG-2015 A
01-SEP-2015 31-DEC-2015 C
01-JAN-2016 31-MAY-2016 B
01-JUN-2016 (null) C
Is there a query I can use to obtain the start and end date for each interval?... maybe using hierarchical queries?
Here, an excerpt I'm using during development that can save some time:
with my_table
as(
select to_date('01-JAN-2015') start_date,'A' value from dual
union
select to_date('01-MAR-2015') start_date,'B' value from dual
union
select to_date('01-AUG-2015') start_date,'' value from dual
union
select to_date('15-AUG-2015') start_date,'A' value from dual
union
select to_date('01-SEP-2015') start_date,'C' value from dual
union
select to_date('01-JAN-2016') start_date,'B' value from dual
union
select to_date('01-JUN-2016') start_date,'C' value from dual
)
select ...
select start_date, lead(start_date) over (order by start_date) - 1 as end_date, value
from my_table
;
Try this
WITH A AS (SELECT ROWNUM AS RN , A.* FROM SALESNEW A)
SELECT X.START_DATE, Y.START_DATE-1 AS END_DATE, X.VALUE FROM A X , A Y
WHERE (CASE WHEN X.RN>=1 THEN X.RN+1 END) = Y.RN(+);

Finding missing dates in a sequence

I have following table with ID and DATE
ID DATE
123 7/1/2015
123 6/1/2015
123 5/1/2015
123 4/1/2015
123 9/1/2014
123 8/1/2014
123 7/1/2014
123 6/1/2014
456 11/1/2014
456 10/1/2014
456 9/1/2014
456 8/1/2014
456 5/1/2014
456 4/1/2014
456 3/1/2014
789 9/1/2014
789 8/1/2014
789 7/1/2014
789 6/1/2014
789 5/1/2014
789 4/1/2014
789 3/1/2014
In this table, I have three customer ids, 123, 456, 789 and date column which shows which month they worked.
I want to find out which of the customers have gap in their work.
Our customers work record is kept per month...so, dates are monthly..
and each customer have different start and end dates.
Expected results:
ID First_Absent_date
123 10/01/2014
456 06/01/2014
To get a simple list of the IDs with gaps, with no further details, you need to look at each ID separately, and as #mikey suggested you can count the number of months and look at the first and last date to see if how many months that spans.
If your table has a column called month (since date isn't allowed unless it's a quoted identifier) you could start with:
select id, count(month), min(month), max(month),
months_between(max(month), min(month)) + 1 as diff
from your_table
group by id
order by id;
ID COUNT(MONTH) MIN(MONTH) MAX(MONTH) DIFF
---------- ------------ ---------- ---------- ----------
123 8 01-JUN-14 01-JUL-15 14
456 7 01-MAR-14 01-NOV-14 9
789 7 01-MAR-14 01-SEP-14 7
Then compare the count with the month span, in a having clause:
select id
from your_table
group by id
having count(month) != months_between(max(month), min(month)) + 1
order by id;
ID
----------
123
456
If you can actually have multiple records in a month for an ID, and/or the date recorded might not be the start of the month, you can do a bit more work to normalise the dates:
select id,
count(distinct trunc(month, 'MM')),
min(trunc(month, 'MM')),
max(trunc(month, 'MM')),
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1 as diff
from your_table
group by id
order by id;
select id
from your_table
group by id
having count(distinct trunc(month, 'MM')) !=
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1
order by id;
Oracle Setup:
CREATE TABLE your_table ( ID, "DATE" ) AS
SELECT 123, DATE '2015-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-06-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-05-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-04-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-11-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-10-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-03-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-03-01' FROM DUAL;
Query:
SELECT ID,
MIN( missing_date )
FROM (
SELECT ID,
CASE WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
= ADD_MONTHS( "DATE", 1 ) THEN NULL
WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
IS NULL THEN NULL
ELSE ADD_MONTHS( "DATE", 1 )
END AS missing_date
FROM your_table
)
GROUP BY ID
HAVING COUNT( missing_date ) > 0;
Output:
ID MIN(MISSING_DATE)
---------- -------------------
123 2014-10-01 00:00:00
456 2014-06-01 00:00:00
You could use a Lag() function to see if records have been skipped for a particular date or not.Lag() basically helps in comparing the data in current row with previous row. So if we order by DATE, we could easily compare and find any gaps.
select * from
(
select ID,DATE_, case when DATE_DIFF>1 then 1 else 0 end comparison from
(
select ID, DATE_ ,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;
This groups all the entries by id, and then arranges the records by date. If a customer is always present, there would not be a gap in his date. So anyone who has a date difference greater than 1 had a gap. You could tweak this as per your requirement.
EDIT : Just observed that you are storing data in mm/dd/yyyy format, when I closely observed above answers.You are storing only first date of every month. So, the above query can be tweaked as :
select * from
(
select ID,DATE_,PREV_DATE,last_day(PREV_DATE)+1 ABSENT_DATE, case when DATE_DIFF>31 then 1 else 0 end comparison from
(
select ID, DATE_ ,LAG(DATE_,1) OVER (PARTITION BY ID ORDER BY DATE_) PREV_DATE,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;

SQL Oracle Query self query

I am trying to figure out how to populate the below NULL values with 1.245 for dates from 07-OCT-14 to 29-SEP-14 then from 26-SEP-14 to 28-JUL-14 it will be 1.447.
This means if the date is less than or equal to the given date then use the value of max effective date which is less than the given date
We could select the last available index_ratio value for given security_alias and effective date <=p.effective_date , so in other words we will need to modify the sql to return from the subquery the index ratio value identified for the maximum available effective date assuming that this effective date is less or equal position effective date
How to populate the value ?
select ab.security_alias,
ab.index_ratio,
ab.effective_date
from securitydbo.security_analytics_fi ab
where ab.security_alias = 123627
order by ab.effective_date desc
Below should be the output
Assuming I understand your requirements correctly, I think the analytic function LAST_VALUE() is what you're after. E.g.:
with sample_data as (select 1 id, 10 val, to_date('01/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('02/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('03/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('04/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 20 val, to_date('05/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 21 val, to_date('06/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('07/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('08/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 31 val, to_date('09/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, null val, to_date('10/08/2015', 'dd/mm/yyyy') dt from dual union all
select 1 id, 42 val, to_date('11/08/2015', 'dd/mm/yyyy') dt from dual)
select id,
last_value(val ignore nulls) over (partition by id order by dt) val,
dt
from sample_data
order by id, dt desc;
ID VAL DT
---------- ---------- ----------
1 42 11/08/2015
1 31 10/08/2015
1 31 09/08/2015
1 21 08/08/2015
1 21 07/08/2015
1 21 06/08/2015
1 20 05/08/2015
1 10 04/08/2015
1 10 03/08/2015
1 10 02/08/2015
1 10 01/08/2015