Turn one column into multiple based on index ranges - sql

I have the following table in SQL Server:
| idx | value |
| --- | ----- |
| 1 | N |
| 2 | C |
| 3 | C |
| 4 | P |
| 5 | N |
| 6 | N |
| 7 | C |
| 8 | N |
| 9 | P |
I would like to turn it to this:
| idx 1-3 | idx 4-6 | idx 7-9 |
| ------- | ------- | ------- |
| N | P | C |
| C | N | N |
| C | N | P |
How can I do this?

If you want to split the data into three columns, with the data in order by id -- and assuming that the ids start at 1 and have no gaps -- then on your particular data, you can use:
select max(case when (idx - 1) / 3 = 0 then value end) as grp_1,
max(case when (idx - 1) / 3 = 1 then value end) as grp_2,
max(case when (idx - 1) / 3 = 2 then value end) as grp_3
from t
group by idx % 3
order by min(idx);
The above doesn't hard-code the ranges, but the "3" means different things in different contexts -- sometimes the number of columns, sometimes the number of rows in the result set.
However, the following generalizes so it adds additional rows as needed:
select max(case when (idx - 1) / num_rows = 0 then idx end) as grp_1,
max(case when (idx - 1) / num_rows = 1 then idx end) as grp_2,
max(case when (idx - 1) / num_rows = 2 then idx end) as grp_3
from (select t.*, convert(int, ceiling(count(*) over () / 3.0)) as num_rows
from t
) t
group by idx % num_rows
order by min(idx);
Here is a db<>fiddle.

You can compute the category of each row with a lateral join, then enumerate the rows within each category, and finally pivot with conditional aggregation:
select
max(case when cat = 'idx_1_3' then value end) as idx_1_3,
max(case when cat = 'idx_4_6' then value end) as idx_4_6,
max(case when cat = 'idx_7_9' then value end) as idx_7_9
from (
select t.*, row_number() over(partition by v.cat) as rn
from mytable t
cross apply (values (
case
when idx between 1 and 3 then 'idx_1_3'
when idx between 4 and 6 then 'idx_4_6'
when idx between 7 and 9 then 'idx_7_9'
end
)) v(cat)
) t
group by rn

Another solution with union all operator and row_number function
select max(IDX_1_3) as IDX_1_3, max(IDX_4_6) as IDX_4_6, max(IDX_1_3) as IDX_1_3
from (
select
case when idx in (1, 2, 3) then value end as idx_1_3
, null as idx_4_6
, null as idx_7_9
, row_number()over(order by idx) as rnb
from Your_table where idx in (1, 2, 3)
union all
select null as idx_1_3
, case when idx in (4, 5, 6) then value end as idx_4_6
, null as idx_7_9
, row_number()over(order by idx) as rnb
from Your_table where idx in (4, 5, 6)
union all
select null as idx_1_3
, null as idx_4_6
, case when idx in (7, 8, 9) then value end as idx_7_9
, row_number()over(order by idx) as rnb
from Your_table where idx in (7, 8, 9)
) t
group by rnb
;

drop table if exists #t;
create table #t (id int identity(1,1) primary key clustered, val varchar(20));
insert into #t(val)
select top (2002) concat(row_number() over(order by ##spid), ' - ', char(65 + abs(checksum(newid()))%26))
from sys.all_objects
order by row_number() over(order by ##spid);
select p.r, 1+(p.r-1)/3 grp3id, p.[1] as [idx 1-3], p.[2] as [idx 4-6], p.[3] as [idx 7-9]
from
(
select
val,
1+((1+(id-1)/3)-1)%3 as c3,
row_number() over(partition by 1+((1+(id-1)/3)-1)%3 order by id) as r
from #t
) as src
pivot
(
max(val) for c3 in ([1], [2], [3])
) as p
order by p.r;

You can use the mod as follows:
select max(case when idx between 1 and 3 then value end) as idx_1_3,
max(case when idx between 4 and 6 then value end) as idx_4_6,
max(case when idx between 7 and 9 then value end) as idx_7_9
from t
group by (idx-1) % 3;
If your idx is not continuous numbers then instead of from t use the following
from (select value, row_number() over(order by idx) as idx
from your_table t) t

Related

Split Columns into two equal number of Rows

I have the table structure below,
I need to merge the CouponNumber to two equal as CouponNumber1 and CouponNumber2 as shown in the figure
SELECT Name, MobileNumber, CouponNumber, IsDispatched, Status
FROM CouponInvoicePrescription
This is my query.
Try this:
WITH
input(ord,name,mobno,couponno,isdispatched,status) AS (
SELECT 0,'amar',8888888888,'CPever901',FALSE,1
UNION ALL SELECT 1,'amar',8888888888,'CP00005' ,FALSE,1
UNION ALL SELECT 2,'pt3' ,7777777777,'cp9090' ,FALSE,1
UNION ALL SELECT 3,'pt3' ,7777777777,'ev2' ,FALSE,1
UNION ALL SELECT 4,'pt3' ,7777777777,'cp9909' ,FALSE,1
UNION ALL SELECT 5,'pt3' ,7777777777,'cp10' ,FALSE,1
)
SELECT
name
, MAX(CASE ord % 2 WHEN 1 THEN couponno END) AS couponno1
, MAX(CASE ord % 2 WHEN 0 THEN couponno END) AS couponno2
, isdispatched
, status
FROM input
GROUP BY
ord / 2
, name
, isdispatched
, status
ORDER BY 1
-- out name | couponno1 | couponno2 | isdispatched | status
-- out ------+-----------+-----------+--------------+--------
-- out amar | CP00005 | CPever901 | f | 1
-- out pt3 | cp10 | cp9909 | f | 1
-- out pt3 | ev2 | cp9090 | f | 1
Try this:
SELECT * FROM
(
SELECT
sub.rn,
sub.Name,
sub.MobileNumber,
sub.CouponNumber as CouponNumber1,
LEAD(sub.CouponNumber,1) OVER (PARTITION BY sub.MobileNumber ORDER BY sub.rn) as CouponNumber2,
sub.IsDispatched,
sub.Status
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION by MobileNumber ORDER BY Name) as rn,
*
FROM
input
) sub
)
WHERE rn % 2 <> 0

SQL Server: Increment row value depending on previous row

I have a table with the columns id and value. I'd like to create a column that groups the id. If a row's current value equals 0 then a new group in ideal_group will be created.
Table:
id | value | ideal_group
1 1 1
2 1 1
3 1 1
4 0 2
5 1 2
6 0 3
7 0 4
I'm thinking the solution should be something like:
SET #n = 1;
SELECT id,
CASE
WHEN value = 0 THEN #n = #n + 1
ELSE #n END AS ideal_group
But I'd prefer not to use an counter variable. Is there another way to go about this?
Try the below code, I assumed, that values in value column are only 1s and 0s:
select id,
value,
sum(1 - value) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
More general solution (without mentioned assumption):
select id,
value,
sum(case value when 0 then 1 else 0 end) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
create table tbl (id int, value int);
insert into tbl values
(1, 1),
(2, 1),
(3, 1),
(4, 0),
(5, 1),
(6, 0),
(7, 0);
GO
7 rows affected
select id,
value,
1 + sum(iif(value = 0, 1, 0)) over
(order by id rows between unbounded preceding and current row) as ideal_group
from tbl
GO
id | value | ideal_group
-: | ----: | ----------:
1 | 1 | 1
2 | 1 | 1
3 | 1 | 1
4 | 0 | 2
5 | 1 | 2
6 | 0 | 3
7 | 0 | 4
dbfiddle here
If you reversed the 1 and 0 and it was only 1 or 0 this would be easier.
declare #T table (id int primary key, val int);
insert into #T values
(1, 1)
, (2, 1)
, (3, 1)
, (4, 0)
, (5, 1)
, (6, 0)
, (7, 0);
select t.id, t.val
, case when t.val = 0 then 1 else 0 end as trig
, sum(case when t.val = 0 then 1 else 0 end) over (order by t.id) + 1 as grp
from #T t
order by t.id;
id val trig grp
----------- ----------- ----------- -----------
1 1 0 1
2 1 0 1
3 1 0 1
4 0 1 2
5 1 0 2
6 0 1 3
7 0 1 4

Calculation between row_value at sqlite3

I have the following table.
| col_num | row_num | value |
|---------|----------|-----------|
| 1 | 3 | 5(value1) |
| 2 | 4 | 7(value2) |
| 5 | 1 | 8(value3) |
Now I want to conduct (value2) / (sqrt(value1)*sqrt(value2)) by querying in sqlite3.
Do you have any idea for this?
Try something like this.
select col_num, row_num,
value/((select sqrt(value) from tbl where row_num = 3) * sqrt(value)) as newValue
from tbl where row_num = 4;
It is unclear how you know what is value1, value2, and value3. You can use conditional aggregation. If you were to use row_num, then it would look like:
select (max(case when row_number = 4 then value end) /
sqrt(max(case when row_nunmber = 3 then value end) *
max(case when row_nunmber = 1 then value end)
)
)
from t;
EDIT:
If the valueX is actually part of the value itself:
select max(case when value like '%(value2)' then substring_index(value, '(', 1) + 0 end) /
sqrt(max(case when '%(value1)' then substring_index(value, '(', 1) + 0 end) *
max(case when '%(value2)' then substring_index(value, '(', 1) + 0 end)
)
)
from t;

How to pivot rows to columns with known max number of columns

I have a table structured as such:
Pricing_Group
GroupID | QTY
TestGroup1 | 1
TestGroup1 | 2
TestGroup1 | 4
TestGroup1 | 8
TestGroup1 | 22
TestGroup2 | 2
TestGroup3 | 2
TestGroup3 | 5
What I'm looking for is a result like this:
Pricing_Group
GroupID | QTY1 | QTY2 | QTY3 | QTY4 | QTY5
TestGroup1 | 1 | 2 | 4 | 8 | 22
TestGroup2 | 2 | NULL | NULL | NULL | NULL
TestGroup3 | 2 | 5 | NULL | NULL | NULL
Note that there can only ever be a maximum of 5 different quantities for a given GroupID, there's just no knowing what those 5 quantities will be.
This seems like an application of PIVOT, but I can't quite wrap my head around the syntax that would be required for an application like this.
Thanks for taking the time to look into this!
Perfect case for pivot and you don't need a CTE:
Declare #T Table (GroupID varchar(10) not null,
QTY int)
Insert Into #T
Values ('TestGroup1', 1),
('TestGroup1', 2),
('TestGroup1', 4),
('TestGroup1', 8),
('TestGroup1', 22),
('TestGroup2', 2),
('TestGroup3', 2),
('TestGroup3', 5)
Select GroupID, [QTY1], [QTY2], [QTY3], [QTY4], [QTY5]
From (Select GroupID, QTY,
RowID = 'QTY' + Cast(ROW_NUMBER() Over (Partition By GroupID Order By QTY) as varchar)
from #T) As Pvt
Pivot (Min(QTY)
For RowID In ([QTY1], [QTY2], [QTY3], [QTY4], [QTY5])
) As Pvt2
You can pivot on a generated rank;
;with T as (
select
rank() over (partition by GroupID order by GroupID, QTY) as rank,
GroupID,
QTY
from
THE_TABLE
)
select
*
from
T
pivot (
max(QTY)
for rank IN ([1],[2],[3],[4],[5])
) pvt
>>
GroupID 1 2 3 4 5
----------------------------------------
TestGroup1 1 2 4 8 22
TestGroup2 2 NULL NULL NULL NULL
TestGroup3 2 5 NULL NULL NULL
You can also use case statement to perform the pivot:
declare #t table ( GroupID varchar(25), QTY int)
insert into #t
values ('TestGroup1', 1),
('TestGroup1', 2),
('TestGroup1', 4),
('TestGroup1', 8),
('TestGroup1', 22),
('TestGroup2', 2),
('TestGroup3', 2),
('TestGroup3', 5)
;with cte_Stage (r, GroupId, QTY)
as ( select row_number() over(partition by GroupId order by QTY ),
GroupId,
QTY
from #t
)
select GroupId,
[QTY1] = sum(case when r = 1 then QTY else null end),
[QTY2] = sum(case when r = 2 then QTY else null end),
[QTY3] = sum(case when r = 3 then QTY else null end),
[QTY4] = sum(case when r = 4 then QTY else null end),
[QTY5] = sum(case when r = 5 then QTY else null end),
[QTYX] = sum(case when r > 5 then QTY else null end)
from cte_Stage
group
by GroupId;

Select and merge rows in a table in SQL Stored procedure

Have a temp table with schema: ID | SeqNo | Name
ID - Not unique
SeqNo - Int (can be 1,2 or 3). Sort of ID+SeqNo as Primary key
Name - Any text
And sample data in the table like this
1 | 1 | RecordA
2 | 1 | RecordB
3 | 1 | RecordC
1 | 2 | RecordD
4 | 1 | RecordE
5 | 1 | RecordF
3 | 1 | RecordG
Need to select from this table and output like
1 | RecordA/RecordD
2 | RecordB
3 | RecordC/RecordG
4 | RecordE
5 | RecordF
Need to do this without cursor.
If SeqNo is limited to 1,2,3:
select id, a.name + coalesce('/'+b.name, '') + coalesce('/'+c.name, '')
from myTable a
left outer join myTable b on a.id=b.id and b.seqno = 2
left outer join myTable c on a.id=c.id and c.seqno = 3
where a.seqno = 1;
If SeqNo is open ended you can deploy a recursive cte:
;with anchor as (
select id, name, seqno
from myTable
where seqno=1)
, recursive as (
select id, name, seqno
from anchor
union all
select t.id, r.name + '/' + t.name, t.seqno
from myTable t
join recursive r on t.id = r.id and r.seqno+1 = t.seqno)
select id, name from recursive;
If you know SeqNo will never be more than 3:
select Id, Names = stuff(
max(case when SeqNo = 1 then '/'+Name else '' end)
+ max(case when SeqNo = 2 then '/'+Name else '' end)
+ max(case when SeqNo = 3 then '/'+Name else '' end)
, 1, 1, '')
from table1
group by Id
Otherwise, something like this is the generic solution to an arbitrary number of items:
select Id, Names = stuff((
select '/'+Name from table1 b
where a.Id = b.Id order by SeqNo
for xml path (''))
, 1, 1, '')
from table1 a
group by Id
Or write a CLR UDA.
Edit: had the wrong alias on the correlated table!
Edit2: another version, based on Remus's recursion example. I couldn't think of any way to select only the last recursion per Id, without aggregation or sorting. Anybody know?
;with
myTable as (
select * from (
values
(1, 1, 'RecordA')
, (2, 1, 'RecordB')
, (3, 1, 'RecordC')
, (1, 2, 'RecordD')
, (4, 1, 'RecordE')
, (5, 1, 'RecordF')
, (3, 2, 'RecordG')
) a (Id, SeqNo, Name)
)
, anchor as (
select id, name = convert(varchar(max),name), seqno
from myTable where seqno=1
)
, recursive as (
select id, name, seqno
from anchor
union all
select t.id, r.name + '/' + t.name, t.seqno
from myTable t
join recursive r on t.id = r.id and r.seqno+1 = t.seqno
)
select id, name = max(name)
from recursive
group by id;
---- without aggregation, we get 7 rows:
--select id, name
--from recursive;
The solution is good. I have a similar issue, but here I am using 2 different tables. ex:
table1
1 | 1 |
2 | 3 |
3 | 1 |
4 | 2 |
5 | 1 |
1 | 2 |
1 | 3 |
4 | 1 |
5 | 2 |
2 | 2 |
4 | 3 |
table2
1 | RecordA
2 | RecordB
3 | RecordC
I want to get the data from two tables and display in the below format.
1 | RecordA,RecordB,RecordC|
2 | RecordB,RecordC|
3 | RecordA |
4 | RecordA,RecordB,RecordC|
5 | RecordA,RecordB |