Count total and count having in the same query - sql

is there a way to get a total count of rows per {id, date} and the count > 1 per {id, date, columnX} in the same query?
For example, having such a table:
id date columnX
1 2017-04-20 a
1 2017-04-20 a
1 2017-04-18 b
1 2017-04-17 c
2 2017-04-20 a
2 2017-04-20 a
2 2017-04-20 c
2 2017-04-19 b
2 2017-04-19 b
2 2017-04-19 b
2 2017-04-19 b
2 2017-04-19 c
As the result, I wanna get the following table:
id date columnX count>1 count_total
1 2017-04-20 a 2 2
2 2017-04-20 a 2 3
2 2017-04-19 b 4 5
I tried to do it with partition by but receive weird results. I've heard Rollup function might be used but it seems like it's applicable only in legacy SQL, which is not the option for me.

If I understand correctly, you can use window functions:
select id, date, columnx, cnt,
(case when cnt > 1 then cnt else 0 end) as cnt_gt_1,
total_cnt
from (select id, date, columnx, count(*) as cnt
sum(count(*)) over (partition by id, date) as total_cnt
from t
group by id, date, columnx
) x
where cnt > 1;

Another possibility:
SELECT
id,
date,
data.columnX columnX,
data.count_ count_bigger_1,
count_total
FROM(
SELECT
id,
date,
ARRAY_AGG(columnX) data,
COUNT(1) count_total
FROM
`your_table_name`
GROUP BY
id, date
),
UNNEST(ARRAY(SELECT AS STRUCT columnX, count(1) count_ FROM UNNEST(data) columnX GROUP BY columnX HAVING count(1) > 1)) data
You can test it with simulated data:
WITH data AS(
SELECT 1 AS id, '2017-04-20' AS date, 'a' AS columnX UNION ALL
SELECT 1 AS id, '2017-04-20' AS date, 'a' AS columnX UNION ALL
SELECT 1 AS id, '2017-04-18' AS date, 'b' AS columnX UNION ALL
SELECT 1 AS id, '2017-04-17' AS date, 'c' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-20' AS date, 'a' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-20' AS date, 'a' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-20' AS date, 'c' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-19' AS date, 'b' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-19' AS date, 'b' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-19' AS date, 'b' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-19' AS date, 'b' AS columnX UNION ALL
SELECT 2 AS id, '2017-04-19' AS date, 'c' AS columnX
)
SELECT
id,
date,
data.columnX columnX,
data.count_ count_bigger_1,
count_total
FROM(
SELECT
id,
date,
ARRAY_AGG(columnX) data,
COUNT(1) count_total
FROM
data
GROUP BY
id, date
),
UNNEST(ARRAY(SELECT AS STRUCT columnX, count(1) count_ FROM UNNEST(data) columnX GROUP BY columnX HAVING count(1) > 1)) data
This solution avoids the analytical function (which can be quite expensive depending on the input) and scales well to large volumes of data.

I recommend you to add into your example two more below rows
1 2017-04-20 x
1 2017-04-20 x
and check what solutions in two previous answers will give you:
It will be something like below:
id date columnX count>1 count_total
1 2017-04-20 a 2 4
1 2017-04-20 x 2 4
2 2017-04-20 a 2 3
2 2017-04-19 b 4 5
Notice two rows for id=1 and date=2017-04-20 and both having count_total=4
I am not sure if this is what you want - even though you might not even considered this scenario in your question
Anyway, I feel that to support more generic case like above your expectation of output should of be like below
Row id date x.columnX x.countX count_total
1 1 2017-04-20 x 2 4
a 2
2 2 2017-04-20 a 2 3
3 2 2017-04-19 b 4 5
where x is repeated field and each value represents respective columnX with its count
Below query does exactly this
#standardSQL
SELECT id, date,
ARRAY(SELECT x FROM UNNEST(x) AS x WHERE countX > 1) AS x,
count_total
FROM (
SELECT id, date, SUM(countX) AS count_total,
ARRAY_AGG(STRUCT<columnX STRING, countX INT64>(columnX, countX) ORDER BY countX DESC) AS X
FROM (
SELECT id, date,
columnX, COUNT(1) countX
FROM `yourTable`
GROUP BY id, date, columnX
)
GROUP BY id, date
HAVING count_total > 1
)
you can play/test it with dummy data from your question
#standardSQL
WITH `yourTable` AS(
SELECT 1 AS id, '2017-04-20' AS date, 'a' AS columnX UNION ALL
SELECT 1, '2017-04-20', 'a' UNION ALL
SELECT 1, '2017-04-20', 'x' UNION ALL
SELECT 1, '2017-04-20', 'x' UNION ALL
SELECT 1, '2017-04-18', 'b' UNION ALL
SELECT 1, '2017-04-17', 'c' UNION ALL
SELECT 2, '2017-04-20', 'a' UNION ALL
SELECT 2, '2017-04-20', 'a' UNION ALL
SELECT 2, '2017-04-20', 'c' UNION ALL
SELECT 2, '2017-04-19', 'b' UNION ALL
SELECT 2, '2017-04-19', 'b' UNION ALL
SELECT 2, '2017-04-19', 'b' UNION ALL
SELECT 2, '2017-04-19', 'b' UNION ALL
SELECT 2, '2017-04-19', 'c'
)
SELECT id, date,
ARRAY(SELECT x FROM UNNEST(x) AS x WHERE countX > 1) AS x,
count_total
FROM (
SELECT id, date, SUM(countX) AS count_total,
ARRAY_AGG(STRUCT<columnX STRING, countX INT64>(columnX, countX) ORDER BY countX DESC) AS X
FROM (
SELECT id, date,
columnX, COUNT(1) countX
FROM `yourTable`
GROUP BY id, date, columnX
)
GROUP BY id, date
HAVING count_total > 1
)

Related

create date range from day based data

i have following source data...
id date value
1 01.08.22 a
1 02.08.22 a
1 03.08.22 a
1 04.08.22 b
1 05.08.22 b
1 06.08.22 a
1 07.08.22 a
2 01.08.22 a
2 02.08.22 a
2 03.08.22 c
2 04.08.22 a
2 05.08.22 a
and i would like to have the following output...
id date_from date_until value
1 01.08.22 03.08.22 a
1 04.08.22 05.08.22 b
1 06.08.22 07.08.22 a
2 01.08.22 02.08.22 a
2 03.08.22 03.08.22 c
2 04.08.22 05.08.22 a
Is this possible with Oracle SQL? Which functions do I need for this?
Based on the link provided by #astentx, try this solution:
SELECT
id, MIN("date") AS date_from, MAX("date") AS date_until, MAX(value) AS value
FROM (
SELECT
t1.*,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY "date") -
ROW_NUMBER() OVER(PARTITION BY id, value ORDER BY "date") AS rn
FROM yourtable t1
)
GROUP BY id, rn
See db<>fiddle
WITH CTE (id, dateD,valueD)
AS
(
SELECT 1, TO_DATE('01.08.22','DD.MM.YY'), 'a' FROM DUAL UNION ALL
SELECT 1, TO_DATE('02.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 1, TO_DATE('03.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 1, TO_DATE('04.08.22','DD.MM.YY'), 'b'FROM DUAL UNION ALL
SELECT 1, TO_DATE('05.08.22','DD.MM.YY'), 'b'FROM DUAL UNION ALL
SELECT 2, TO_DATE('01.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 2, TO_DATE('02.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 2, TO_DATE('03.08.22','DD.MM.YY'), 'c'FROM DUAL
)
SELECT C.ID,C.VALUED,MIN(C.DATED)AS MIN_DATE,MAX(C.DATED)AS MAX_DATE
FROM CTE C
GROUP BY C.ID,C.VALUED
ORDER BY C.ID
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=47c87d60445ce262cd371177e31d5d63

Get last value from a certain group (Oracle)

I have something like this
Date Group ID
11/01 'A' 1
12/01 'A' 2
13/01 'B' 3
14/01 'B' 4
What i basically want is to get for example the latest from group 'A'
Date Group ID LatestID_from_GROUP_A_ordered_by_recent_date
11/01 'A' 1 2
12/01 'A' 2 2
13/01 'B' 3 2
14/01 'B' 4 2
or at least something like this
Date Group ID LatestID_from_GROUP_A_ordered_by_recent_date
11/01 'A' 1 null
12/01 'A' 2 null
13/01 'B' 3 2
14/01 'B' 4 2
How about this:
with demo (somedate, somegroup, id) as
( select date '2018-01-11', 'A', 1 from dual union all
select date '2018-01-12', 'A', 2 from dual union all
select date '2018-01-13', 'B', 3 from dual union all
select date '2018-01-14', 'B', 4 from dual union all
select date '2018-01-15', 'A', 5 from dual -- example from comments
)
select somedate, somegroup, id
, ( select max(id) keep (dense_rank last order by somedate)
from demo
where somegroup = 'A' ) as last_a
from demo;
SOMEDATE SOMEGROUP ID LAST_A
----------- --------- ---------- ----------
11/01/2018 A 1 5
12/01/2018 A 2 5
13/01/2018 B 3 5
14/01/2018 B 4 5
15/01/2018 A 5 5
Note the max(id) is only a tiebreaker in the event of multiple rows with the last date.
Gordon was almost there.
You want to create a window over your whole query, but only pick the biggest value of 'A':
select
t.*,
max(case when group = 'A' then id end) over (partition by 1) as latest_from_a
from t
'partition by 1' will create a window of your complete result set because it only groups by a single static value: 1.
The logic seems to be:
select t.*,
max(case when group = 'A' then id end) over (order by date) as latest_from_a
from t;
The above gets the cumulative maximum up to each date. If you want the overall maximum:
select t.*,
max(case when group = 'A' then id end) over () as latest_from_a
from t;

SQL query for segregating max and min values of a column in two different columns say Val1 and Val2

I have below table:
ID DateVal Val
1 1/1/2010 a
1 2/2/2010 b
1 3/3/2010 c
2 4/4/2010 d
2 5/5/2010 e
2 6/6/2010 f
3 7/7/2010 g
3 8/8/2010 h
3 9/9/2010 i
I need below:
ID Val1 Val2
1 a c
2 d f
3 g i
i.e. the Val at min date in column 'Val1' and Val and max date in column 'Val2'.
What all queries are there to achieve this output and which one is easiest?
DDL
with T as (
select 1 as id, to_date('01.01.2010','DD.MM.YYYY') dt, 'a' val
from dual union all
select 1 as id, to_date('02.02.2010','DD.MM.YYYY') dt, 'b' val
from dual union all
select 1 as id, to_date('03.03.2010','DD.MM.YYYY') dt, 'c' val
from dual union all
select 2 as id, to_date('04.04.2010','DD.MM.YYYY') dt, 'd' val
from dual union all
select 2 as id, to_date('05.05.2010','DD.MM.YYYY') dt, 'e' val
from dual union all
select 2 as id, to_date('06.06.2010','DD.MM.YYYY') dt, 'f' val
from dual union all
select 3 as id, to_date('07.07.2010','DD.MM.YYYY') dt, 'g' val
from dual union all
select 3 as id, to_date('08.08.2010','DD.MM.YYYY') dt, 'h' val
from dual union all
select 3 as id, to_date('09.09.2010','DD.MM.YYYY') dt, 'i' val
from dual)
Code
select
id,
max(val) keep (dense_rank first order by dt) as maxs,
max(val) keep (dense_rank first order by dt desc) as mins from t
group by id
with t1 as
(select ID, min(dtval) dtval, min(val) val from date_val group by ID),
t2 as
(select ID, max(dtval) dtval, max(val) val from date_val group by ID)
select t1.id, t1.val val1, t2.val val2 from t1 join t2 on t1.id = t2.id;

SQL: Earliest Date After Latest Null If Exists

Using T-Sql I am looking to return the min date after the latest null if one exists and simply the min date on any products where there are no nulls.
Table:
DateSold Product
12/31/2012 A
1/31/2013
2/28/2013 A
3/31/2013 A
4/30/2013 A
5/31/2013
6/30/2013 A
7/31/2013 A
8/31/2013 A
9/30/2013 A
12/31/2012 B
1/31/2013 B
2/28/2013 B
3/31/2013 B
4/30/2013 B
5/31/2013 B
6/30/2013 B
7/31/2013 B
8/31/2013 B
9/30/2013 B
For product “A” 6/30/2013 is the desired return while for product “B” 12/31/2012 is desired.
Result:
MinDateSold Product
6/30/2013 A
12/31/2012 B
Any solutions will greatly be appreciated. Thank you.
This does it for me, if there's a GROUP involved, otherwise how do you know whether the NULLs are in the run of A or B products? I realise this may not be exactly what you're after, but I hope it helps anyway.
WITH DATA_IN AS (
SELECT 1 as grp,
convert(DateTime,'12/31/2012') as d_Date,
'A' AS d_ch
UNION ALL
SELECT 1, '1/31/2013', NULL UNION ALL
SELECT 1, '2/28/2013', 'A' UNION ALL
SELECT 1, '3/31/2013', 'A' UNION ALL
SELECT 1, '4/30/2013', 'A' UNION ALL
SELECT 1, '5/31/2013', NULL UNION ALL
SELECT 1, '6/30/2013', 'A' UNION ALL
SELECT 1, '7/31/2013', 'A' UNION ALL
SELECT 1, '8/31/2013', 'A' UNION ALL
SELECT 1, '9/30/2013', 'A' UNION ALL
SELECT 2, '12/31/2012', 'B' UNION ALL
SELECT 2, '1/31/2013', 'B' UNION ALL
SELECT 2, '2/28/2013', 'B' UNION ALL
SELECT 2, '3/31/2013', 'B' UNION ALL
SELECT 2, '4/30/2013', 'B' UNION ALL
SELECT 2, '5/31/2013', 'B' UNION ALL
SELECT 2, '6/30/2013', 'B' UNION ALL
SELECT 2, '7/31/2013', 'B' UNION ALL
SELECT 2, '8/31/2013', 'B' UNION ALL
SELECT 2, '9/30/2013', 'B'
)
SELECT
grp as YourGroup,
(SELECT Min(d_date) -- first date after...
FROM DATA_IN
WHERE d_date>
Coalesce( -- either the latest NULL
(SELECT max(d_Date)
FROM DATA_IN d2
WHERE d2.grp=d1.grp AND d2.d_ch IS NULL
)
, '1/1/1901' -- or a base date if no NULLs
)
) as MinDateSold
FROM DATA_IN d1
GROUP BY grp
Results :
1 2013-06-30 00:00:00.000
2 2012-12-31 00:00:00.000
One approach to this is to count the number of NULL values that appear before a given row for a given value. This divides the ranges into groups. For each group, take the minimum date. And, find the largest minimum date for each product:
select product, minDate
from (select product, NumNulls, min(DateSold) as minDate,
row_number() over (partition by product order by min(DateSold) desc
) as seqnum
from (select t.*,
(select count(*)
from table t2
where t2.product is null and t2.DateSold <= t.DateSold
) as NumNulls
from table t
) t
group by Product, NumNUlls
) t
where seqnum = 1;
In your data, there is no mixing of different products in a range, so this query sort of assumes that is true as well.

Get distinct rows based on priority?

I have a table as below.i am using oracle 10g.
TableA
------
id status
---------------
1 R
1 S
1 W
2 R
i need to get distinct ids along with their status. if i query for distinct ids and their status i get all 4 rows.
but i should get only 2. one per id.
here id 1 has 3 distinct statuses. here i should get only one row based on priority.
first priority is to 'S' , second priority to 'W' and third priority to 'R'.
in my case i should get two records as below.
id status
--------------
1 S
2 R
How can i do that? Please help me.
Thanks!
select
id,
max(status) keep (dense_rank first order by instr('SWR', status)) as status
from TableA
group by id
order by 1
fiddle
select id , status from (
select TableA.*, ROW_NUMBER()
OVER (PARTITION BY TableA.id ORDER BY DECODE(
TableA.status,
'S',1,
'W',2,
'R',3,
4)) AS row_no
FROM TableA)
where row_no = 1
This is first thing i would do, but there may be a better way.
Select id, case when status=1 then 'S'
when status=2 then 'W'
when status=3 then 'R' end as status
from(
select id, max(case when status='S' then 3
when status='W' then 2
when status='R' then 1
end) status
from tableA
group by id
);
To get it done you can write a similar query:
-- sample of data from your question
SQL> with t1(id , status) as (
2 select 1, 'R' from dual union all
3 select 1, 'S' from dual union all
4 select 1, 'W' from dual union all
5 select 2, 'R' from dual
6 )
7 select id -- actual query
8 , status
9 from ( select id
10 , status
11 , row_number() over(partition by id
12 order by case
13 when upper(status) = 'S'
14 then 1
15 when upper(status) = 'W'
16 then 2
17 when upper(status) = 'R'
18 then 3
19 end
20 ) as rn
21 from t1
22 ) q
23 where q.rn = 1
24 ;
ID STATUS
---------- ------
1 S
2 R
select id,status from
(select id,status,decode(status,'S',1,'W',2,'R',3) st from table) where (id,st) in
(select id,min(st) from (select id,status,decode(status,'S',1,'W',2,'R',3) st from table))
Something like this???
SQL> with xx as(
2 select 1 id, 'R' status from dual UNION ALL
3 select 1, 'S' from dual UNION ALL
4 select 1, 'W' from dual UNION ALL
5 select 2, 'R' from dual
6 )
7 select
8 id,
9 DECODE(
10 MIN(
11 DECODE(status,'S',1,'W',2,'R',3)
12 ),
13 1,'S',2,'W',3,'R') "status"
14 from xx
15 group by id;
ID s
---------- -
1 S
2 R
Here, logic is quite simple.
Do a DECODE for setting the 'Priority', then find the MIN (i.e. one with Higher Priority) value and again DECODE it back to get its 'Status'
Using MOD() example with added values:
SELECT id, val, distinct_val
FROM
(
SELECT id, val
, ROW_NUMBER() OVER (ORDER BY id) row_seq
, MOD(ROW_NUMBER() OVER (ORDER BY id), 2) even_row
, (CASE WHEN id = MOD(ROW_NUMBER() OVER (ORDER BY id), 2) THEN NULL ELSE val END) distinct_val
FROM
(
SELECT 1 id, 'R' val FROM dual
UNION
SELECT 1 id, 'S' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
UNION
SELECT 2 id, 'R' val FROM dual
UNION -- comment below for orig data
SELECT 3 id, 'K' val FROM dual
UNION
SELECT 4 id, 'G' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
))
WHERE distinct_val IS NOT NULL
/
ID VAL DISTINCT_VAL
--------------------------
1 S S
2 R R
3 K K
4 G G