Convert number sequence format so that it is hyphenated - sql

I have a sequence of numbers that need to be rendered with a hyphen but not sure how best to do this from the SQL database selection.
The expected result:
Peter: 1,3-7,10,11,13
Andrew: 1-3
Paul: 1-3
An example of the data from the table (small selection):
NAME #
Peter 1
Andrew 1
Paul 1
Andrew 2
Paul 2
Peter 3
Andrew 3
Paul 3
Peter 4
Peter 5
Peter 6
Peter 7

This is part gaps-and-islands and part string aggregation. This identifies the groupings:
select name,
(case when min(number) = max(number)
then convert(varchar(max), min(num))
else concat(min(number), '-', max(number))
end) as range
from (select name, number,
row_number() over (partition by name order by number) as seqnum
from t
) t
group by name, (number - seqnum);
With this you can add an additional level of aggregation to get the final result:
select name,
string_agg(range, ',') within group (order by min(min_number)) as col
from (select name, min(number) as min_number,
(case when min(number) = max(number)
then convert(varchar(max), min(num))
else concat(min(number), '-', max(number))
end) as range
from (select name, number,
row_number() over (partition by name order by number) as seqnum
from t
) t
group by name, (number - seqnum)
) n
group by name;

Related

Is there a way to partition by incremental series in Postgressql?

In postgressql is there a way to attain the result below by using partition by or any other way?
last_name year increment partition
Doe 2000 1 1
Doe 2001 2 1
Doe 2002 3 1
Doe 2003 -1 2
Doe 2004 1 3
Doe 2005 2 3
Doe 2006 3 3
Doe 2007 -1 4
Doe 2008 -2 4
SELECT last_name,
year,
increment,
SUM(CASE WHEN increment < 0 THEN 1 ELSE 0 END) OVER (PARTITION BY last_name ORDER BY year) AS partition
FROM your_table
ORDER BY last_name, year;
It seems that you want to group the consecutive positive/ negative values together, one option is to use a difference between two row_number functions, this will make the partition but with unordered group numbers.
select *,
row_number() over (partition by last_name order by year) -
row_number() over (partition by last_name,
case when increment>=0 then 1 else 2 end order by year) as prt
from tbl
order by last_name, year
If you want the partitions in order (1, 2, 3...) you could try another approach using lag and running sum as the following:
select last_name, year, increment,
1 + sum(case when sign(increment) <> sign(pre_inc) then 1 else 0 end) over
(partition by last_name order by year) as prt
from
(
select *,
lag(increment, 1 , increment) over
(partition by last_name order by year) pre_inc
from tbl
) t
order by last_name, year
See demo
If the increment column does encrease over the column year, it will be marked as 1; otherwise, it will be marked as 0. Then, we group the successive data using "LAG", regardless of whether the increment is positive or negative.
with cte as (
select * ,
row_number() over (partition by last_name order by year) as row_num,
case when increment >= LAG(increment,1,0) over (partition by last_name order by year)
then 1 else 0 end rank_num
from mytable
),
cte2 as (
select *, LAG(rank_num,1,1) over (partition by last_name order by year) as pre
from cte
order by year
)
select last_name, year, increment, 1+sum(case when pre <> rank_num then 1 else 0 end) over
(partition by last_name order by year) as partition
from cte2;

sql - get rows with first x field values

I would like to get rows with, lets say, 3 first values for type field. For the following table called items
id
name
type
1
banana
fruit
2
mango
fruit
3
car
toy
4
lion
animal
5
badger
animal
6
cupboard
furniture
7
shirt
cloth
The result would be rows 1-5 (fruit, toy, animal).
I understand using sql limit like
select * from items
limit 3;
will not return rows with animals which I want to get. Is there any smooth way to achieve that?
Use dense_rank() after calculating the minimum id for each type:
select t.*
from (select t.*,
dense_rank() over (order by min_type_id) as seqnum
from (select t.*, min(id) over (partition by type) as min_type_id
from t
) t
) t
where seqnum <= 3;
You could also use:
select t.*
from t
where t.type in (select t2.type
from t t2
group by t2.type
order by min(t2.id) asc
limit 3
);
You can do:
select *
from items
where type in (
select type
from (select type, min(id) as min_id from items group by type) x
order by min_id
limit 3
)
Result:
id name type
--- ------- ------
1 banana fruit
2 mango fruit
3 car toy
4 lion animal
5 badger animal
See running example at DB Fiddle.
Use LAG() and SUM() window functions to assign groups to the types and select top 3 groups:
SELECT id, name, type
FROM (
SELECT *, SUM(flag) OVER (ORDER BY id) grp
FROM (
SELECT *, (type <> LAG(type, 1, '') OVER (ORDER BY id))::int flag
FROM items
) t
) t
WHERE grp <= 3
See the demo.

how to find the number has more than two consecutive appearences?

The source table:
id num
-------------------
1 1
2 1
3 1
4 2
5 2
6 1
The output:(appear at least 2 times)
num times
--------------
1 3
2 2
Based on the addition logic defined in the comments it appears this is what you're after:
WITH YourTable AS(
SELECT V.id,
V.num
FROM (VALUES(1,1),
(2,1),
(3,1),
(4,2),
(5,2),
(6,1),
(7,1))V(id,num)), --Added extra row due to logic defined in comments
Grps AS(
SELECT YT.id,
YT.num,
ROW_NUMBER() OVER (ORDER BY id) -
ROW_NUMBER() OVER (PARTITION BY Num ORDER BY id) AS Grp
FROM YourTable YT),
Counts AS(
SELECT num,
COUNT(num) AS Times
FROM grps
GROUP BY grp,
num)
SELECT num,
MAX(times) AS times
FROM Counts
GROUP BY num;
This uses a CTE and ROW_NUMBER to define the groups, and then an additional CTE to get the COUNT per group. Finally you can then get the MAX COUNT per num.
I would adress this with a gaps-and-islands technique:
select num, max(cnt)
from (
select num, count(*) cnt
from (
select
id,
num,
row_number() over(order by id) rn1,
row_number() over(partition by num order by id) rn2
from mytable
) t
group by num, rn1 - rn2
) t
group by num
The most inner query computes row numbers over the whole table and within num groups; the difference between the row numbers gives you the group of adjacent records that each record belong to (you can run that subquery independently and follow how the difference evolves to understand more).
Then, the next level count the number of records in each group of adjacent records. The most outer query takes the maximum count of adjacent records in for each num.
Demo on DB Fiddle:
num | (No column name)
--: | ---------------:
1 | 3
2 | 2
this will work for you
select num,count(num) times from Tabl
group by num

concate or segregate strings based on requirement

USER_ID COLUMN1 COLUMN2
JOHN 24 CA
JOHN 24 LA
JOHN 63 CA
JOHN 63 LA
JOHN 66 CA
JOHN 66 LA
JOHN 9 AF
JOHN 9 AL
JOHN 9 AW
JOHN 9 DF
Required output:
USER_ID RESULT
JOHN 24~CA-LA + 63~CA-LA + 66~CA-LA + 9~AF-AL-AW-DF
This is my requirement. I am trying listagg():
select USER_ID,
(listagg(case when seqnum_p = 1 then COLUMN1 end, '-') within group (order by COLUMN1) ||
'~' ||
listagg(case when seqnum_b = 1 then COLUMN2 end, '-') within group (order by COLUMN2)
) as result
from (select TABLE.*,
row_number() over (partition by USER_ID, COLUMN1 order by COLUMN1) as seqnum_p,
row_number() over (partition by USER_ID, COLUMN2 order by COLUMN2) as seqnum_b
from TABLE
)
group by USER_ID;
Current output:
JOHN || AF-AL-AW-CA-DF-LA~24-63-66-9
You can do two levels of aggregation instead of dealing with the row numbers:
select user_id,
listagg(tmp, ' + ') within group (order by tmp) as result
from (
select user_id,
column1 ||'~'|| listagg(column2, '-') within group (order by column2) as tmp
from your_table
group by user_id, column1
)
group by user_id
order by user_id;
USER RESULT
---- --------------------------------------------------
JOHN 24~CA-LA + 63~CA-LA + 66~CA-LA + 9~AF-AL-AW-DF
The inner query gives you the first level:
USER TMP
---- --------------------------------------------------
JOHN 9~AF-AL-AW-DF
JOHN 24~CA-LA
JOHN 63~CA-LA
JOHN 66~CA-LA
and the outer level further aggregates those into a single string per user.
The order-by in the outer query aggregation is of a string starting with a number, which puts '9~...' after '24~...', which would normally be odd but seems to be what you expect.
If you actually wanted them in numerical column-1-order you can include that in the subquery and use it for ordering:
select user_id,
listagg(tmp, ' + ') within group (order by column1) as result
from (
select user_id, column1,
column1 ||'~'|| listagg(column2, '-') within group (order by column2) as tmp
from your_table
group by user_id, column1
)
group by user_id
order by user_id;
USER RESULT
---- --------------------------------------------------
JOHN 9~AF-AL-AW-DF + 24~CA-LA + 63~CA-LA + 66~CA-LA

How do I select a row based on a priority value in another row?

I am using Oracle 11G and I have a table with the following columns and values and I want to select the value for each column based on the priority column. I only want one row for each ID.
ID NAME NAME_PRIORITY COLOR COLOR_PRIORITY
1 SAM 2 RED 1
1 SAM 2 GREEN 2
1 JOHN 1 BLUE 3
2 MARY 2 ORANGE 1
3 JON 2 RED 2
3 PETE 3 GREEN 1
Desired Results
ID NAME NAME_PRIORITY COLOR COLOR_PRIORITY
1 JOHN 1 RED 1
2 MARY 2 ORANGE 1
3 JON 2 GREEN 1
How do I select the NAME and COLOR with the lowest PRIORITY # and only have one row for each ID.
one option is:
select d.id, min(name) keep (dense_rank first order by name_priority) name,
min(name_priority) name_priority,
min(color) keep (dense_rank first order by color_priority) color,
min(color_priority) color_priority
from yourtab d
group by id;
You can use row_number() on both the name_priority and color_priority to get the result:
select n.id,
name,
name_priority,
color,
color_priority
from
(
select id,
name,
name_priority,
row_number() over(partition by id order by name_priority) name_row
from yourtable
) n
inner join
(
select id,
color,
color_priority,
row_number() over(partition by id order by color_priority) color_row
from yourtable
) c
on n.id = c.id
and n.name_row = c.color_row
where n.name_row = 1
and c.color_row = 1
See SQL Fiddle with Demo.
Once you have the row_number() for each priority, then you will join the results on the id and the row number and only return the rows where the row number is equal to 1.
This query uses Common Table Expression and ROW_NUMBER()
WITH nameList
AS
(
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY ID
ORDER BY NAME_PRIORITY) rn
FROM TableName
),
colorList
AS
(
SELECT a.ID, a.Name,
b.Color, b.COLOR_PRIORITY,
ROW_NUMBER() OVER (PARTITION BY a.ID
ORDER BY COLOR_PRIORITY) rnB
FROM nameList a
INNER JOIN tableName b
ON a.ID = b.ID AND a.rn = 1
)
SELECT ID, Name, Color, COLOR_PRIORITY
FROM colorList
WHERE rnB = 1
SQLFiddle Demo