SQL Server: Order cols in a row - sql

I want to swap the value of columns to have them sorted.
Table:
pid | category1 | category2 | category3
----+-----------+-----------+----------
1 | a | b |
2 | b | a |
3 | a | c | b
Result should be:
pid | category1 | category2 | category3
----+-----------+-----------+----------
1 | a | b |
2 | a | b |
3 | a | b | c
My approach was to select the columns to rows, order them in groups an return the new columns:
pid | category
----+---------
1 | a
1 | b
2 | b
2 | a
3 | a
3 | c
3 | b
I found the pivot function, but didn't really understand how to use it in this context.

Sean Lange is right about how you should correct your database schema.
Here is something to get you your results until then:
Using cross apply(values ...) to unpivot your data along with row_number() to reorder your category inside a common table expression; then repivoting that data with conditional aggregation:
;with cte as (
select
t.pid
, u.category
, rn = row_number() over (partition by t.pid order by u.category)
from t
cross apply (values (category1),(category2),(category3)) u(category)
where nullif(u.category,'') is not null
)
select
pid
, category1 = max(case when rn = 1 then category end)
, category2 = max(case when rn = 2 then category end)
, category3 = max(case when rn = 3 then category end)
from cte
group by pid
rextester demo: http://rextester.com/GIG22558
returns:
+-----+-----------+-----------+-----------+
| pid | category1 | category2 | category3 |
+-----+-----------+-----------+-----------+
| 1 | a | b | NULL |
| 2 | a | b | NULL |
| 3 | a | b | c |
+-----+-----------+-----------+-----------+

you can pivot as below
select * from (
select *, RowN = row_number() over(partition by pid order by Category) from Categories
) a
pivot (max(category) for RowN in ([1],[2],[3])) p
Output as below:
+-----+-----------+-----------+-----------+
| Pid | Category1 | Category2 | Category3 |
+-----+-----------+-----------+-----------+
| 1 | a | b | NULL |
| 2 | a | b | NULL |
| 3 | a | b | c |
+-----+-----------+-----------+-----------+
For dynamic list of columns you can use as below:
declare #cols1 varchar(max)
declare #cols2 varchar(max)
declare #query nvarchar(max)
--Row Numbers with tally
;with c1 as (
select * from ( values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) v(n) )
,c2 as (select n1.* from c1 n1, c1 n2, c1 n3, c1 n4)
,RowNumbers as (
select top (select max(cnt) from (select cnt = count(*) from Categories group by pid ) a) convert(varchar(6), row_number() over (order by (select null))) as RowN
from c2 n1, c2 n2
)
select #cols1 = stuff((select ','+QuoteName(RowN) from RowNumbers group by RowN for xml path('')),1,1,''),
#cols2 = stuff((select ',' + QuoteName(RowN) + ' as '+ QuoteName(concat('Category' , RowN)) from RowNumbers group by RowN for xml path('')),1,1,'')
select #cols1, #cols2
Set #query = ' Select Pid, '+ #cols2 +' from ( '
Set #query += ' select *, RowN = row_number() over(partition by pid order by Category) from Categories) a '
Set #query += ' pivot (max(category) for RowN in (' + #cols1 + ')) p'
--select #query
exec sp_executesql #query

Related

I want row data of same id in one single row

The Table Format is like
ID | MID | PID | Quantity
1 | 1 | 2 | 3
2 | 1 | 3 | 10
3 | 2 | 2 | 11
4 | 2 | 1 | 5
I want to result as following
ID | MID | Final
1 | 1 | 2(3),3(10)
2 | 2 | 2(11),1(5)
first concate two columns and then do string_agg. Here is the demo.
with cte as
(
select
mid,
concat(pid, '(', quantity, ')') as concat_col
from table1
)
select
row_number() over (order by mid) as id,
mid,
string_agg(concat_col, ', ') as final
from cte
group by
mid
output:
| id | mid | final |
| --- | --- | ----------- |
| 1 | 1 | 2(3), 3(10) |
| 2 | 2 | 2(11), 1(5) |
If you are using older version of SQL Server then try the following
with cte as
(
select
mid,
concat(pid, '(', quantity, ')') as concat_col
from table1
)
select
row_number() over (order by mid) as id,
mid,
stuff((
select ',' + concat_col
from cte c1
where c.mid = c1.mid
for XML PATH('')
), 1, 1, '') as final
from cte c
group by
mid
select MID, string_agg(concat(PID, '(', Quantity,')'), ', ')
from dbo.Sample
group by MID
Result :
MID FINAL
1 2(3), 3(10)
2 2(11), 1(5)

display oldest value with newest value at id level

I have a table that looks something like this where ids are displayed with old value with the new value (the one it just got changed into).
IF OBJECT_ID('tempdb.dbo.#TT','U')IS NOT NULL
DROP TABLE #TT;
SELECT *
INTO #TT
FROM (
SELECT 1 AS ID, 'A' AS OldValue, 'B' AS NewValue, CONVERT(DATE,'20190421') AS [Date]
UNION ALL
SELECT 1 AS ID, 'B' AS OldValue, 'C' AS NewValue, CONVERT(DATE,'20190423') AS [Date]
UNION ALL
SELECT 2 AS ID, 'D' AS OldValue, 'E' AS NewValue, CONVERT(DATE,'20190422') AS [Date]
UNION ALL
SELECT 3 AS ID, 'J' AS OldValue, 'K' AS NewValue, CONVERT(DATE,'20190422') AS [Date]
UNION ALL
SELECT 3 AS ID, 'K' AS OldValue, 'L' AS NewValue, CONVERT(DATE,'20190423') AS [Date]
UNION ALL
SELECT 3 AS ID, 'L' AS OldValue, 'M' AS NewValue, CONVERT(DATE,'20190424') AS [Date]
) T
;
I want to display each id and old value with newest value available for them. For example ID=1 and OldValue = A should display C or ID = 3 and OldValue = K should display M
I have tried to do recursive cte as follows:
WITH RecCTE AS (
SELECT ID, OldValue, NewValue
FROM #TT
UNION ALL
SELECT A.ID, B.OldValue, A.NewValue
FROM #TT A
INNER JOIN RecCTE B ON A.ID = B.ID AND B.NewValue = A.OldValue
)
SELECT *
FROM RecCTE
With that recursive query. I am getting some line correctly but I am also getting extra lines in between:
| ID | OldValue | NewValue |
|-----------|---------------|----------------|
| 1 | A | B |
| 1 | B | C |
| 2 | D | E |
| 3 | J | K |
| 3 | K | L |
| 3 | L | M |
| 3 | K | M |
| 3 | J | L |
| 3 | J | M |
| 1 | A | C |
I want to get results like this:
| ID | OldValue | NewValue |
|-----------|---------------|----------------|
| 1 | A | C |
| 1 | B | C |
| 2 | D | E |
| 3 | J | M |
| 3 | K | M |
| 3 | L | M |
I think taking the last row should work:
WITH RecCTE AS (
SELECT ID, OldValue, NewValue, 1 as lev
FROM #TT
UNION ALL
SELECT A.ID, B.OldValue, A.NewValue, lev + 1
FROM #TT A JOIN
RecCTE B
ON A.ID = B.ID AND B.NewValue = A.OldValue
)
SELECT *
FROM (SELECT r.*, ROW_NUMBER() OVER (PARTITION BY id, OldValue ORDER BY lev DESC) as seqnum
FROM RecCTE r
) r
WHERE seqnum = 1;
If the data works the way it looks to me like it works, the following will do it without using recursion:
;WITH cte as (
-- List of all, ordered by ID, with the most recent being the first listed
SELECT
ID
,NewValue
,row_number() over (partition by Id order by Date desc) Ranking
from #TT
)
select
tt.Id
,tt.OldValue
,cte.NewValue
from #TT tt
-- Join table to the "most recent" row for the ID
inner join cte
on cte.ID = tt.Id
and cte.Ranking = 1

SQL Server condition based concatenation of multiple rows

Consider a table holding data like below,
COL1| COL2| active_flag
----| ----| ------------
1 | A | Y
1 | B | Y
1 | C | Y
1 | D | N
1 | E | N
2 | M | Y
2 | N | Y
2 | O | N
2 | P | Y
2 | Q | Y
and I require the output like below.
COL1| COL2
----| -----
1 | ABC
1 | D
1 | E
2 | MN
2 | O
2 | PQ
How to achieve this in SQL Server 2012
First you should add column to id to get stable sort. Then you could calculate each group and then concatenate using FOR XML:
WITH cte AS(
SELECT *,
CASE WHEN
LAG(active_flag) OVER(ORDER BY id) <> active_flag
OR LAG(active_flag) OVER(ORDER BY id) = 'N' AND active_flag = 'N' THEN 1
ELSE 0
b END as l
FROM t
), cte2 AS (
SELECT *, SUM(l) OVER(ORDER BY id) AS grp
FROM cte
)
SELECT DISTINCT col1, (SELECT '' + col2
FROM cte2
WHERE grp = c.grp
ORDER BY id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') AS col2
FROM cte2 c;
Rextester demo
Output:
Warning!
ORDER BY 1/0 or ORDER BY (SELECT 1) do not provide stable sort.

grouping and switching the columns and rows

I don't know if this would officially be called a pivot, but the result that I would like is this:
+------+---------+------+
| Alex | Charley | Liza |
+------+---------+------+
| 213 | 345 | 1 |
| 23 | 111 | 5 |
| 42 | 52 | 2 |
| 323 | | 5 |
| 23 | | 1 |
| 324 | | 5 |
+------+---------+------+
my input data is in this form:
+-----+---------+
| Apt | Name |
+-----+---------+
| 213 | Alex |
| 23 | Alex |
| 42 | Alex |
| 323 | Alex |
| 23 | Alex |
| 324 | Alex |
| 345 | Charley |
| 111 | Charley |
| 52 | Charley |
| 1 | Liza |
| 5 | Liza |
| 2 | Liza |
| 5 | Liza |
| 1 | Liza |
| 5 | Liza |
+-----+---------+
because I have approximately 100 names, I don't want to have to do a ton of sub queries lik this
select null, null, thirdcolumn from...
select null, seconcolumn from...
select firstcolumn from...
Is there a way to do this with PIVOT or otherwise?
You can do this with dynamic PIVOT and the ROW_NUMBER() function:
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT DISTINCT *
FROM #test)
,cte2 AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Apt)RowRank
FROM cte)
SELECT *
FROM cte2
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Distinct List, Specific Order
Edit: If you don't want the list to be distinct, eliminate the first cte above, and if you want to keep arbitrary ordering change the ORDER BY to (SELECT 1):
DECLARE #cols AS VARCHAR(1000),
#query AS VARCHAR(8000)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(Name)
FROM (SELECT DISTINCT Name
FROM #test
)sub
ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)')
,1,1,'')
PRINT #cols
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT *
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
SQL Fiddle - Full List, Arbitrary Order
And finally, if you didn't want the RowRank field in your results, just re-use the #cols variable in your SELECT:
SET #query = '
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY Name ORDER BY (SELECT 1))RowRank
FROM #test)
SELECT '+#cols+'
FROM cte
PIVOT (max(Apt) for Name in ('+#cols+')) p
'
EXEC (#query)
Oh, this is something of a pain, but you can do it with SQL. You are trying to concatenate the columns.
select seqnum,
max(case when name = 'Alex' then apt end) as Alex,
max(case when name = 'Charley' then apt end) as Charley,
max(case when name = 'Liza' then apt end) as Liza
from (select t.*, row_number() over (partition by name order by (select NULL)) as seqnum
from t
) t
group by seqnum
order by seqnum;
As a note: there is no guarantee that the original ordering will be the same within each column. As you know, SQL tables are inherently unordered, so you would need a column to specify the ordering.
To handle multiple names, I'd just get the list using a query such as:
select distinct 'max(case when name = '''+name+''' then apt end) as '+name+','
from t;
And copy the results into the query.

Write advanced SQL Select

Item table:
| Item | Qnty | ProdSched |
| a | 1 | 1 |
| b | 2 | 1 |
| c | 3 | 1 |
| a | 4 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
Is there a way I can output it like this using SQL SELECT?
| Item | ProdSched(1)(Qnty) | ProdSched(2)(Qnty) |
| a | 1 | 4 |
| b | 2 | 5 |
| c | 3 | 6 |
You can use PIVOT for this. If you have a known number of values to transform, then you can hard-code the values via a static pivot:
select item, [1] as ProdSched_1, [2] as ProdSched_2
from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in ([1], [2])
) p
see SQL Fiddle with Demo
If the number of columns is unknown, then you can use a dynamic pivot:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(prodsched)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT item,' + #cols + ' from
(
select item, qty, prodsched
from yourtable
) x
pivot
(
max(qty)
for prodsched in (' + #cols + ')
) p '
execute(#query)
see SQL Fiddle with Demo
SELECT Item,
[ProdSched(1)(Qnty)] = MAX(CASE WHEN ProdSched = 1 THEN Qnty END),
[ProdSched(2)(Qnty)] = MAX(CASE WHEN ProdSched = 2 THEN Qnty END)
FROM dbo.tablename
GROUP BY Item
ORDER BY Item;
Let's hit this in two phases. First, although this is not the exact format you wanted, you can get the data you asked for as follows:
Select item, ProdSched, max(qty)
from Item1
group by item,ProdSched
Now, to get the data in the format you desired, one way of accomplishing it is a PIVOT table. You can cook up a pivot table in SQL Server as follows:
Select item, [1] as ProdSched1, [2] as ProdSched2
from ( Select Item, Qty, ProdSched
from item1 ) x
Pivot ( Max(qty) for ProdSched in ([1],[2])) y