Get missing months from date for each and every id - greenplum

I have a table which contains, memberid and date. I want to display for each and every member get the missing months from date.
Here is the input table and expected table.
I have tried creating a temp table to generate series from min month to max month, but I am failing to join the two tables.
Can anyone help me in doing this.
Here is the code I have tried so, far.
Extracting dates and storing it in new column
create temp table temp_table as select *, date_part('month', premiumpadidate) from training.premium distributed by(memberid);
Generate series
create temp table temp_table_series as
(select * from generate_series(cast((select min(date_part) from temp_table group by (memberid)) as integer), cast((select max(date_part) from temp_table group by (memberid)) as integer) )
)
distributed by (generate_series)
I am not understanding how to join the two tables to get the missing months of each and every employee.
Please help me in doing this

This is easily done with a function, but you could acomplish in a single select. Here is what I did to help you. I simulated your data with a union, and added a "102" member:
select 101 as id, '2016-01-01'::date as paidDate union
select 101 as id, '2016-02-01'::date as paidDate union
select 101 as id, '2016-04-01'::date as paidDate union
select 101 as id, '2016-08-01'::date as paidDate union
select 101 as id, '2016-11-01'::date as paidDate union
select 102 as id, '2016-01-01'::date as paidDate union
select 102 as id, '2016-02-01'::date as paidDate union
select 102 as id, '2016-04-01'::date as paidDate union
select 102 as id, '2016-08-01'::date as paidDate union
select 102 as id, '2016-11-01'::date as paidDate
You should look a it as your table.
Then, with your table I did 12 "cases", in order to check the month. I put a "0" value if the month is found, and the month value if not, like this:
select t.id, extract(month from t.paidDate),
case when extract(month from t.paidDate) = 1 then 0 else 1 end as m1,
case when extract(month from t.paidDate) = 2 then 0 else 2 end as m2,
case when extract(month from t.paidDate) = 3 then 0 else 3 end as m3,
case when extract(month from t.paidDate) = 4 then 0 else 4 end as m4,
case when extract(month from t.paidDate) = 5 then 0 else 5 end as m5,
case when extract(month from t.paidDate) = 6 then 0 else 6 end as m6,
case when extract(month from t.paidDate) = 7 then 0 else 7 end as m7,
case when extract(month from t.paidDate) = 8 then 0 else 8 end as m8,
case when extract(month from t.paidDate) = 9 then 0 else 9 end as m9,
case when extract(month from t.paidDate) = 10 then 0 else 10 end as m10,
case when extract(month from t.paidDate) = 11 then 0 else 11 end as m11,
case when extract(month from t.paidDate) = 12 then 0 else 12 end as m12
from (
select 101 as id, '2016-01-01'::date as paidDate union
select 101 as id, '2016-02-01'::date as paidDate union
select 101 as id, '2016-04-01'::date as paidDate union
select 101 as id, '2016-08-01'::date as paidDate union
select 101 as id, '2016-11-01'::date as paidDate union
select 102 as id, '2016-01-01'::date as paidDate union
select 102 as id, '2016-02-01'::date as paidDate union
select 102 as id, '2016-04-01'::date as paidDate union
select 102 as id, '2016-08-01'::date as paidDate union
select 102 as id, '2016-11-01'::date as paidDate
) t
After that, we need to discard the months that exists in the data. I did that using a group by id, gettin the MIN month. This will discard the months that exists (because we zeroed them in the case), and will maintain the months that not exist. Like this:
select id,
min(m1) as m1, min(m2) as m2, min(m3) as m3, min(m4) as m4, min(m5) as m5, min(m6) as m6,
min(m7) as m7, min(m8) as m8, min(m9) as m9, min(m10) as m10, min(m11) as m11, min(m12) as m12
from
(
select t.id, extract(month from t.paidDate),
case when extract(month from t.paidDate) = 1 then 0 else 1 end as m1,
case when extract(month from t.paidDate) = 2 then 0 else 2 end as m2,
case when extract(month from t.paidDate) = 3 then 0 else 3 end as m3,
case when extract(month from t.paidDate) = 4 then 0 else 4 end as m4,
case when extract(month from t.paidDate) = 5 then 0 else 5 end as m5,
case when extract(month from t.paidDate) = 6 then 0 else 6 end as m6,
case when extract(month from t.paidDate) = 7 then 0 else 7 end as m7,
case when extract(month from t.paidDate) = 8 then 0 else 8 end as m8,
case when extract(month from t.paidDate) = 9 then 0 else 9 end as m9,
case when extract(month from t.paidDate) = 10 then 0 else 10 end as m10,
case when extract(month from t.paidDate) = 11 then 0 else 11 end as m11,
case when extract(month from t.paidDate) = 12 then 0 else 12 end as m12
from
(
select 101 as id, '2016-01-01'::date as paidDate union
select 101 as id, '2016-02-01'::date as paidDate union
select 101 as id, '2016-04-01'::date as paidDate union
select 101 as id, '2016-08-01'::date as paidDate union
select 101 as id, '2016-11-01'::date as paidDate union
select 102 as id, '2016-01-01'::date as paidDate union
select 102 as id, '2016-02-01'::date as paidDate union
select 102 as id, '2016-04-01'::date as paidDate union
select 102 as id, '2016-08-01'::date as paidDate union
select 102 as id, '2016-11-01'::date as paidDate
) t
) t
group by t.id
You could you it already as your result to execute whatever rules you have, but lets clean it a little bit more, by removing the zeros, like this:
select id,
case when m1 = 0 then null else m1 end, case when m2 = 0 then null else m2 end,
case when m3 = 0 then null else m3 end, case when m4 = 0 then null else m4 end,
case when m5 = 0 then null else m5 end, case when m6 = 0 then null else m6 end,
case when m7 = 0 then null else m7 end, case when m8 = 0 then null else m8 end,
case when m9 = 0 then null else m9 end, case when m10 = 0 then null else m10 end,
case when m11 = 0 then null else m11 end, case when m12 = 0 then null else m12 end
from
(
select id,
min(m1) as m1, min(m2) as m2, min(m3) as m3, min(m4) as m4, min(m5) as m5, min(m6) as m6,
min(m7) as m7, min(m8) as m8, min(m9) as m9, min(m10) as m10, min(m11) as m11, min(m12) as m12
from
(
select t.id, extract(month from t.paidDate),
case when extract(month from t.paidDate) = 1 then 0 else 1 end as m1,
case when extract(month from t.paidDate) = 2 then 0 else 2 end as m2,
case when extract(month from t.paidDate) = 3 then 0 else 3 end as m3,
case when extract(month from t.paidDate) = 4 then 0 else 4 end as m4,
case when extract(month from t.paidDate) = 5 then 0 else 5 end as m5,
case when extract(month from t.paidDate) = 6 then 0 else 6 end as m6,
case when extract(month from t.paidDate) = 7 then 0 else 7 end as m7,
case when extract(month from t.paidDate) = 8 then 0 else 8 end as m8,
case when extract(month from t.paidDate) = 9 then 0 else 9 end as m9,
case when extract(month from t.paidDate) = 10 then 0 else 10 end as m10,
case when extract(month from t.paidDate) = 11 then 0 else 11 end as m11,
case when extract(month from t.paidDate) = 12 then 0 else 12 end as m12
from
(
select 101 as id, '2016-01-01'::date as paidDate union
select 101 as id, '2016-02-01'::date as paidDate union
select 101 as id, '2016-04-01'::date as paidDate union
select 101 as id, '2016-08-01'::date as paidDate union
select 101 as id, '2016-11-01'::date as paidDate union
select 102 as id, '2016-01-01'::date as paidDate union
select 102 as id, '2016-02-01'::date as paidDate union
select 102 as id, '2016-04-01'::date as paidDate union
select 102 as id, '2016-08-01'::date as paidDate union
select 102 as id, '2016-11-01'::date as paidDate
) t
) t
group by
t.id
) t
That's it, hope that it helps a little bit.
Regards

Related

I need 2 count columns in the same query in ORACLE

I'm trying to get the unique number of invoices a company has received and sent out using 2 count() functions. In invoices table there are two columns that are references to the same company id (one is id of a company that is sending an invoice and the other one is id of a company that is receiving an invoice)
This is the code I tried using:
SELECT K.ID,K.NAME,K.CITY, COUNT(*) AS NUM_OF_INVOICES_SENT, COUNT(*) AS NUM_OF_INVOICES_RECEIVED
FROM COMPANY K LEFT JOIN INVOICE F ON F.COMP_SNEDING = K.ID
GROUP BY K.NAME,K.ID,K.CITY
This is for a school project so I am in no means well versed in sql/oracle
actual data invoices:
actual data company:
desired outcome with given actual data:
Here's one option; it doesn't use count, but sum with case expression.
Sample data:
SQL> with
2 invoice (id, amount, comp_sending, comp_receiving) as
3 (select 1, 2000 , 1, 2 from dual union all
4 select 2, 28250, 3, 2 from dual union all
5 select 3, 8700 , 4, 1 from dual union all
6 select 4, 20200, 5, 3 from dual union all
7 select 5, 21500, 3, 4 from dual
8 ),
9 company (id, name, city, state) as
10 (select 1, 'Microsoft', 'Redmond' , 'Washington' from dual union all
11 select 2, 'Ubisoft' , 'Paris' , 'France' from dual union all
12 select 4, 'Starbucks', 'Seattle' , 'Washington' from dual union all
13 select 5, 'Apple' , 'Cupertino', 'California' from dual union all
14 select 3, 'Nvidia' , 'Cupertino', 'California' from dual
15 )
Query begins here:
16 select c.id, c.name,
17 sum(case when c.id = i.comp_sending then 1 else 0 end) cnt_sent,
18 sum(case when c.id = i.comp_receiving then 1 else 0 end) cnt_received
19 from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
20 group by c.id, c.name
21 order by c.id;
ID NAME CNT_SENT CNT_RECEIVED
---------- --------- ---------- ------------
1 Microsoft 1 1
2 Ubisoft 0 2
3 Nvidia 2 1
4 Starbucks 1 1
5 Apple 1 0
SQL>
You can use COUNT if you replace the 0 in the CASE expressions with NULL. So #Littlefoot's query becomes
select c.id, c.name,
COUNT(case when c.id = i.comp_sending then 1 else NULL end) cnt_sent,
COUNT(case when c.id = i.comp_receiving then 1 else NULL end) cnt_received
from company c left join invoice i on c.id in (i.comp_sending, i.comp_receiving)
group by c.id, c.name
order by c.id;
This works because COUNT counts only those rows which have a non-NULL value in the expression which is being counted.
db<>fiddle here

Oracle query to keep looking until value is not 0 anymore

I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>

Oracle - creating a group based on group spacing

How to make a query that will create groups that have a space between them greater than "n"?
Data:
01-01-2000
02-01-2000
03-01-2000
06-01-2000
07-01-2000
19-02-2001
10-01-2002
11-01-2002
I would like to get a result for the interval between records, e.g. 2 days:
DATE GROUP
01-01-2000 1
02-01-2000 1
03-01-2000 1
06-01-2000 2
07-01-2000 2
19-02-2001 3
10-01-2002 4
11-01-2002 4
For 10 days:
01-01-2000 1
02-01-2000 1
03-01-2000 1
06-01-2000 1
07-01-2000 1
19-02-2001 2
10-01-2002 3
11-01-2002 3
Another example with integers:
with x as (
select 1 as A from dual
union all
select 2 as A from dual
union all
select 3 as A from dual
union all
select 10 as A from dual
union all
select 20 as A from dual
union all
select 22 as A from dual
union all
select 33 as A from dual
union all
select 40 as A from dual
union all
select 50 as A from dual
union all
select 100 as A from dual
union all
select 101 as A from dual
union all
select 102 as A from dual
) select A
from x;
I need to create groups for a value increase of more than 3:
Example result:
1 1
2 1
3 1
10 2
20 3
22 3
33 4
40 5
50 6
100 7
101 7
102 7
Here is one way to do it
CREATE TABLE TEST (
DATE_IN DATE
);
INSERT INTO TEST VALUES (TO_DATE('01-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('02-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('03-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('06-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('07-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('19-02-2001','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('10-01-2002','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('11-01-2002','DD-MM-YYYY'));
--HERE IS AN EXAMPLE FOR 1 DAY. Just change the value in the > 1 TO >10
--if you want to create a group if there is a gap of more than 10days
SELECT DATE_IN, SUM(NEW_GROUP) OVER ( ORDER BY DATE_IN) AS GROUPE FROM (
SELECT
DATE_IN,
CASE WHEN DATE_IN - LAG(DATE_IN,1,TO_DATE('01-01-1900','DD-MM-YYYY')) OVER ( ORDER BY DATE_IN) > 1 THEN 1 ELSE 0 END AS NEW_GROUP
FROM TEST
)
-- Result
DATE_IN GROUPE
2000-01-01T00:00:00Z 1
2000-01-02T00:00:00Z 1
2000-01-03T00:00:00Z 1
2000-01-06T00:00:00Z 2
2000-01-07T00:00:00Z 2
2001-02-19T00:00:00Z 3
2002-01-10T00:00:00Z 4
2002-01-11T00:00:00Z 4
Example with integer:
with x as (
select 1 as A from dual
union all
select 2 as A from dual
union all
select 3 as A from dual
union all
select 10 as A from dual
union all
select 20 as A from dual
union all
select 22 as A from dual
union all
select 33 as A from dual
union all
select 40 as A from dual
union all
select 50 as A from dual
union all
select 100 as A from dual
union all
select 101 as A from dual
union all
select 102 as A from dual
) SELECT A, SUM(NEW_GROUP) OVER ( ORDER BY A) AS GROUPE FROM (
SELECT
A,
CASE WHEN A - LAG(A,1,1) OVER ( ORDER BY A) > 5 THEN 1 ELSE 0 END AS NEW_GROUP
FROM X
)
order by A;

How to combine 2 select statements in oracle

i have table test2.it contains
ID
1
4
5
10
now i found missing numbers in this sequence.with this query
SELECT min_ID - 1 + level mn FROM
( SELECT MIN(ID) min_ID , MAX(ID) max_ID FROM test2 )
CONNECT BY level <= max_ID - min_ID + 1 minus SELECT ID FROM test2
output is:
MN
---
2
3
6
7
8
9
now i want to combine these 2 columns.I am unable to do this please help me.
i want output like
1 2
4 3
7 5
10 6
8
9
Oracle Setup:
CREATE TABLE test2 (id) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 10 FROM DUAL;
Query:
WITH bounds ( mn, mx ) AS (
SELECT MIN( id ), MAX( id ) FROM test2
),
missing (id, rn) AS (
SELECT id, ROWNUM
FROM (
SELECT mn + LEVEL AS id
FROM bounds
CONNECT BY LEVEL < MX - MN
MINUS
SELECT id
FROM test2
)
),
existing ( id, rn ) AS (
SELECT id, ROWNUM
FROM test2
)
SELECT e.id, m.id
FROM existing e
FULL OUTER JOIN
missing m
ON ( e.rn = m.rn );
Output
ID ID
---------- ----------
1 2
4 3
5 6
10 7
9
8

Subselect in oracle

I'm struggling with a subselect in oracle. I want to include the latest price from another table.
Here is my current attempt:
SELECT tab1.*
(select price from
old_prices
where part_no=tab1.article_no
order by valid_from desc) as old_price,
FROM articles tab1
order by article_no
The sub select returns several rows which I think is the problem. But I do not know how to limit the number of rows in Oracle.
SQL> create table articles (article_no,name)
2 as
3 select 1, 'PEN' from dual union all
4 select 2, 'PAPER' from dual
5 /
Table created.
SQL> create table old_prices (part_no,valid_from,price)
2 as
3 select 1, date '2008-01-01', 10 from dual union all
4 select 1, date '2009-01-01', 11 from dual union all
5 select 1, date '2010-01-01', 12 from dual union all
6 select 1, date '2011-01-01', 13 from dual union all
7 select 2, date '2010-01-01', 89.95 from dual union all
8 select 2, date '2011-01-01', 94.95 from dual union all
9 select 2, date '2012-01-01', 99.95 from dual
10 /
Table created.
SQL> select a.article_no
2 , max(a.name) keep (dense_rank last order by p.valid_from) name
3 , max(p.price) keep (dense_rank last order by p.valid_from) price
4 from articles a
5 , old_prices p
6 where a.article_no = p.part_no
7 group by a.article_no
8 /
ARTICLE_NO NAME PRICE
---------- ----- ----------
1 PEN 13
2 PAPER 99.95
2 rows selected.
Regards,
Rob.
If it's the latest price you're after:
SELECT tab1.*, p.price old_price
FROM articles tab1
, old_prices p
where p.part_no = tab1.article_no
and valid_from = (
select MAX(valid_from)
from old_prices p2
where p2.part_no = p.part_no
)
order by article_no
I want to include the lastest price
I presume you mean latest.
OK, well that's a bit of a problem to start with, there are several ways of doing this:
SELECT o.price
FROM old_prices o
WHERE o.part_no=&part_no
AND o.ondate=(SELECT MAX(o2.ondate)
FROM old_prices o2
WHERE o2.part_no=&part_no);
Seems the most obvious choice but its rather innefficient.
You could try....
SELECT ilv.price
FROM (SELECT o.price
FROM old_price o
WHERE o.part_no=&part_no
ORDER BY ondate DESC) ilv
WHERE rownum=1;
Or....
SELECT TO_NUMBER(
SUBSTR(
MAX(TO_CHAR(o.ondate, 'YYYYMMDDHH24MISS') || price)
, 15)
) as latest_price
FROM old_price o
WHERE o.part_no=&part_no;
To limit rows use ROWNUM < 10. This is a pseudocolumn returning the row number of each line of your resultset.
EDIT:
You need to add another subselect query (hope this is the right place for your need)
SELECT tab1.*
select (
(select price from old_prices
where part_no=tab1.article_no order by valid_from desc
) as x
where rownum = 1
) as old_price
FROM articles tab1
order by article_no
SELECT tab1.*
(select
price
from (
SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
where rn =1
and tab1.article_no = P.part_no
) as old_price
FROM articles tab1
order by article_no
more efficient would be
SELECT
tab1.*
, P.price
FROM
articles tab1
, ( SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
WHERE
P.part_no(+) = tab1.article_no
P.rn(+) = 1
;
with old_prices as(
select level * 15 price ,
mod (level ,5) part_no , --this is just to create a grouping type partno
(sysdate - level ) valid_from
from dual
connect by level < 100)
,
articles as(
select level ,
mod(level , 5 ) article_no ,
(sysdate + level) someOtherDateField
From dual
connect by level < 5
)
SELECT tab1.* ,
old_price.*
from articles tab1
left join
(
select price,
part_no ,
valid_from ,
rank() over(partition by part_no order by valid_from desc) rk
from old_prices
) old_price
on tab1.article_no = old_price.part_no
and old_price.rk = 1
order by article_no ;
Here's another way!
LEVEL ARTICLE_NO SOMEOTHERDATEFIELD PRICE PART_NO VALID_FROM RK
---------------------- ---------------------- ------------------------- ---------------------- ---------------------- ------------------------- ----------------------
1 1 25/05/11 07:30:54 15 1 23/05/11 07:30:54 1
2 2 26/05/11 07:30:54 30 2 22/05/11 07:30:54 1
3 3 27/05/11 07:30:54 45 3 21/05/11 07:30:54 1
4 4 28/05/11 07:30:54 60 4 20/05/11 07:30:54 1

Resources