How to group the same values which is in sequence order - sql

I'm trying to group data in sequence order. I have the following table:
id num
-------
1 1
2 1
3 1
4 2
5 1
6 2
7 2
8 4
9 4
10 4
I need the SQL query to output the following:
num count(num)
-------------------
1 3
2 1
1 1
2 2
4 3
Sample data:
select * into #temp
from (
select 1 as id, 1 as num union all
select 2, 1 union all
select 3, 1 union all
select 4, 2 union all
select 5, 1 union all
select 6, 2 union all
select 7, 2 union all
select 8, 4 union all
select 9, 4 union all
select 10, 4
) as abc
select * from #temp
select num, count(num)
from #temp
group by num
I need this :
num count(num)
-------------------
1 3
2 1
1 1
2 2
4 3
The actual output :
num count(num)
---------------------
1 4
2 3
4 3

This is a gaps and islands problem. Here is one way to solve it using lag() and a cumulative sum():
select min(num) num, count(*) count_num
from (
select t.*, sum(case when num = lag_num then 0 else 1 end) over(order by id) grp
from (
select t.*, lag(num) over(order by id) lag_num
from #temp t
) t
) t
group by grp
Demo on DB Fiddlde:
num | count_num
--: | --------:
1 | 3
2 | 1
1 | 1
2 | 2
3 | 3

Another Approach can be using row_number
select num, count(*)
from (select t.*,
(row_number() over (order by id) -
row_number() over (partition by num order by id)
) as grp
from #temp t
) t
group by grp, num;
DBFIDDLE

Gaps and islands problems are fun because there are so many different ways to address them. Here is one approach that does not require aggregation -- although it does require more use of window functions.
This is possible because the only information you are requesting is the count. If the id has no gaps and is sequential:
select num,
lead(id, 1, max_id + 1) over (order by id) - id
from (select t.*,
lag(num) over (order by id) as prev_num,
max(id) over () as max_id
from temp t
) t
where prev_num is null or prev_num <> num
order by id;
Otherwise, you can generate such a sequence easily:
select num,
lead(seqnum, 1, cnt + 1) over (order by id) - seqnum
from (select t.*,
lag(num) over (order by id) as prev_num,
row_number() over (order by id) as seqnum,
count(*) over () as cnt
from temp t
) t
where prev_num is null or prev_num <> num
order by id;
Here is a db<>fiddle.

Related

How to mix two results in SQL (example included)

I'm trying to figure out a way to make a mix of data this way:
Table A:
1
2
3
4
5
Table B:
a
b
c
d
e
Mix into:
1
a
2
b
3
c
4
d
5
e
This is a sorting problem:
select col
from ((select col, 1 as which, row_number() over (order by col) as seqnum
from a
) union all
(select col, 2 as which, row_number() over (order by col) as seqnum
from b
)
) ab
order by seqnum, which
Use UNION ALL with ROW_NUMBER():
SELECT mycol FROM (
SELECT 1 seq, mycolA AS mycol, ROW_NUMBER() OVER(ORDER BY mycolA) AS rn
FROM tableA
UNION ALL
SELECT 2, mycolB, ROW_NUMBER() OVER(ORDER BY mycolB)
FROM tableB
) x
ORDER BY rn, seq

Oracle get difference in Average of Current and Previous group (partition)

I am using Oracle 12.1.0.2.0
I want difference in average of current group(partition) - average of previous group(partition)
My code to get current group Average is
with rws as (
select rownum x, mod(rownum, 2) y from dual connect by level <= 10
), avgs as (
select x, y, avg(x) over (partition by y) mean from rws
)
select x, y, mean
from avgs;
Now I want something like :
X Y MEAN PREV_MEAN MEAN_DIFF
4 0 6
8 0 6
2 0 6
6 0 6
10 0 6
9 1 5 6 -1
7 1 5
3 1 5
1 1 5
5 1 5
2 2 3 5 -3
3 2 3
5 2 3
1 2 3
4 2 3
AVG( this partitioned group) - Avg( previous partition group)
In this case I need ( 5 - 6 ) to compute in GROUP_MEAN_DIFFERENCE column.
Also How can I get mean difference always w.r.t first group.
In the example above I need (5 - 6) and (3 - 6)
Can you please assist?
Use the function lag() with ignore nulls clause:
select id, val, av, av - lag(av ignore nulls) over (order by id) diff
from (select id, val,
case when row_number() over (partition by id order by null) = 1
then avg(val) over (partition by id) end av
from t)
order by id
Test:
with t (id, val) as (select 1, 44.520 from dual union all
select 1, 47.760 from dual union all
select 1, 50.107 from dual union all
select 1, 48.353 from dual union all
select 1, 47.640 from dual union all
select 2, 48.353 from dual union all
select 2, 50.447 from dual union all
select 2, 51.967 from dual union all
select 2, 45.800 from dual union all
select 2, 46.913 from dual )
select id, val, av, av - lag(av ignore nulls) over (order by id) diff
from (select id, val,
case when row_number() over (partition by id order by null) = 1
then avg(val) over (partition by id) end av
from t)
order by id
Output:
ID VAL AV DIFF
--- ------- ------- -------
1 44.520 47.676
1 47.760
1 50.107
1 48.353
1 47.640
2 48.353 48.696 1.02
2 50.447
2 51.967
2 45.800
2 46.913

SQL Grouping by first digit from sets of record

I need your help in SQL
I have a set of records of Cost center ID below.
what I want to do is to segregate/group them by inserting column to distinguish the category.
as you can see all digits start in 7 is belong to the bold digits.
my expected out is on below image also.
You can as the below:
DECLARE #Tbl TABLE (ID INT)
INSERT INTO #Tbl
VALUES
(735121201),
(735120001),
(5442244),
(735141094),
(735141097),
(4008060),
(735117603),
(40100000),
(735142902),
(735151199),
(4010070)
;WITH TableWithRowId
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY(SELECT NULL)) RowId,
ID
FROM
#Tbl
), TempTable
AS
(
SELECT T.RowId + 1 AS RowId FROM TableWithRowId T
WHERE
LEFT(T.ID, 1) != 7
), ResultTable
AS
(
SELECT
T.RowId ,
T.ID,
DENSE_RANK() OVER (ORDER BY (SELECT TOP 1 A.RowId FROM TempTable A WHERE A.RowId > T.RowId ORDER BY A.RowId)) AS Flag
FROM TableWithRowId T
)
SELECT * FROM ResultTable
Result:
RowId ID Flag
----------- ----------- ----------
1 735121201 1
2 735120001 1
3 5442244 1
4 735141094 2
5 735141097 2
6 4008060 2
7 735117603 3
8 40100000 3
9 735142902 4
10 735151199 4
11 4010070 4
The following query is similer with NEER's
;WITH test_table(CenterID)AS(
SELECT '735121201' UNION ALL
SELECT '735120001' UNION ALL
SELECT '5442244' UNION ALL
SELECT '735141094' UNION ALL
SELECT '735141097' UNION ALL
SELECT '4008060' UNION ALL
SELECT '735117603' UNION ALL
SELECT '40100000' UNION ALL
SELECT '735142902' UNION ALL
SELECT '735151199' UNION ALL
SELECT '4010070'
),t1 AS (
SELECT *,ROW_NUMBER()OVER(ORDER BY(SELECT 1)) AS rn,CASE WHEN LEFT(t.CenterID,1)='7' THEN 1 ELSE 0 END AS isSeven
FROM test_table AS t
),t2 AS(
SELECT t1.*,ROW_NUMBER()OVER(ORDER BY t1.rn) AS toFilter
FROM t1 LEFT JOIN t1 AS pt ON pt.rn=t1.rn-1
WHERE pt.CenterID IS NULL OR (t1.isSeven=1 AND pt.isSeven=0)
)
SELECT t1.CenterID,x.toFilter FROM t1
CROSS APPLY(SELECT TOP 1 t2.toFilter FROM t2 WHERE t2.rn<=t1.rn ORDER BY rn desc) x
CenterID toFilter
--------- --------------------
735121201 1
735120001 1
5442244 1
735141094 2
735141097 2
4008060 2
735117603 3
40100000 3
735142902 4
735151199 4
4010070 4

In Oracle, how to select specific row while aggregating all rows

I have a requirement that I need to both aggregate all rows by id, and find 1 specific row among the rows of the same id. It's like 2 SQL queries, but I want to make it in 1 SQL query. I'm using Oracle database.
for example,table t1 whose data looks like:
id | name | num
----- -------- -------
1 | 'a' | 1
2 | 'b' | 3
2 | 'c' | 6
2 | 'd' | 6
I want to aggregate the data by the id, find the 'name' with the highest 'count', and sum all count of the id to 'total_count'.
There are 2 rows with same num, pick up the first one.
id | highest_num | name_of_highest_num | total_num | avg_num
----- ------------- --------------------- ------------ -------------------
1 | 1 | 'a' | 1 | 1
2 | 6 | 'c' | 15 | 5
Can I get this result by 1 Oracle SQL query?
Thanks in advance for any replies.
Oracle Setup:
CREATE TABLE table_name ( id, name, num ) AS
SELECT 1, 'a', 1 FROM DUAL UNION ALL
SELECT 2, 'b', 3 FROM DUAL UNION ALL
SELECT 2, 'c', 6 FROM DUAL UNION ALL
SELECT 2, 'd', 6 FROM DUAL;
Query:
SELECT id,
MAX( num ) AS highest_num,
MAX( name ) KEEP ( DENSE_RANK LAST ORDER BY num ) AS name_of_highest_num,
SUM( num ) AS total_num,
AVG( num ) AS avg_num
FROM table_name
GROUP BY id
Output:
ID HIGHEST_NUM NAME_OF_HIGHEST_NUM TOTAL_NUM AVG_NUM
-- ----------- ------------------- --------- -------
1 1 a 1 1
2 6 d 15 5
Here's one option using row_number in a subquery with conditional aggregation:
select id,
max(num) as highest_num,
max(case when rn = 1 then name end) as name_of_highest_num,
sum(num) as total_num,
avg(num) as avg_num
from (
select id, name, num,
row_number() over (partition by id order by num desc) rn
from a
) t
group by id
SQL Fiddle Demo
Sounds like you want to use some analytic functions. Something like this should work
select id,
num highest_num,
name name_of_highest_num,
total total_num,
average avg_num
from (select id,
num,
name,
rank() over (partition by id
order by num desc, name asc) rnk,
sum(num) over (partition by id) total,
avg(num) over (partition by id) average
from table t1)
where rnk = 1

How to check group for containing certain elements in SQL?

I have a simple table:
id num
1 7
1 5
1 4
2 5
2 4
2 7
3 4
3 7
How to select ids having num 5 as well as 7 and 4
For this example ids: 1, 2
SELECT `id` FROM `table`
WHERE `num` IN (4, 5, 7)
GROUP BY `id`
HAVING COUNT(*) = 3
This is a very slightly amended version of zerkms' answer:
SELECT `id` FROM `table`
WHERE `num` IN (4, 5, 7)
GROUP BY `id`
HAVING COUNT(DISTINCT `num`) = 3
How about
;WITH T AS
(
SELECT ID, NUM, COUNT(*) OVER(PARTITION BY ID) AS ROWNO,
SUM(NUM) OVER(PARTITION BY ID) AS SUMNUM
FROM TABLE
)
SELECT ID, NUM
FROM T
WHERE ROWNO = 3 AND SUMNUM = 16