TSQL Convert rows to columns - sql

I have a query that returns some data that looks like this:
Description
----------
Thing1
Thing2
Thing3
Thing4
Thing5
Thing6
Thing7
I would like to make the data look like this:
Desc1 Desc2 Desc3
----- ----- -----
Thing1 Thing2 Thing3
Thing4 Thing5 Thing6
Thing7
Could someone provide an example on how to do this?
Thanks!

You stated the comments that the order of the data in the Desc columns does not matter.
If that is the case, then you can use the following which implement NTILE and row_number():
;with cte as
(
select description,
'desc'+cast(ntile(3) over(order by Description) as varchar(10)) col
from yt
)
select desc1, desc2, desc3
from
(
select description, col,
row_number() over(partition by col order by col) rn
from cte
) d
pivot
(
max(description)
for col in (desc1, desc2, desc3)
) piv;
See SQL Fiddle with Demo.
The NTILE function distributes the rows into separate groups. Once that is done, then apply a row_number() to give a unique number to each row while grouping.
This gives a result:
| DESC1 | DESC2 | DESC3 |
----------------------------
| Thing1 | Thing4 | Thing6 |
| Thing2 | Thing5 | Thing7 |
| Thing3 | (null) | (null) |

Another approach of doing it, if the order does matter, and skipping PIVOT function:
WITH CTE AS
(
SELECT
(ROW_NUMBER() OVER (ORDER BY [Description])+2) /3 AS RowID,
(ROW_NUMBER() OVER (ORDER BY [Description])+2) % 3 +1 AS ColID,
[Description]
FROM Table1
)
SELECT
MIN(CASE WHEN ColID = 1 THEN [Description] END) DESC1
,MIN(CASE WHEN ColID = 2 THEN [Description] END) DESC2
,MIN(CASE WHEN ColID = 3 THEN [Description] END) DESC3
FROM CTE
GROUP BY RowID
SQLFiddle DEMO

I used the base data from the sqlfiddle of #NenadZivkovic to provide a different solution
http://www.sqlfiddle.com/#!6/b676a/15
WITH Numbered AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY Description) as rn,
(ROW_NUMBER() OVER (ORDER BY Description) - 1) % 3 as colid,
(ROW_NUMBER() OVER (ORDER BY Description) - 1) / 3 as line,
Description
FROM Table1
)
SELECT desc1.description as DESC1, desc2.description as DESC2, desc3.description as DESC3
FROM (SELECT line, description FROM Numbered WHERE colid=0) as desc1
LEFT JOIN
(SELECT line, description FROM Numbered WHERE colid=1) as desc2
ON (desc1.line = desc2.line)
LEFT JOIN
(SELECT line, description FROM Numbered WHERE colid=2) as desc3
ON (desc2.line = desc3.line)

Related

Convert Table to another format in MSSQL

I am facing a problem with MS-SQL in getting output from a table in a particular format.
Name | StringValue | Parent_ID
FieldName | TestHeader1 | 3
FieldValue | ValueForTestHeader1 | 3
FieldName | TestHeader2 | 6
FieldValue | ValueForTestHeader2 | 6
And I want to select data from this table as follows:
TestHeader1 | TestHeader2
ValueForTestHeader1 | ValueForTestHeader2
Any help would be highly appreciated!
use conditional aggregation
select max(case when parent_id=3 then stringvalue) as col1,
max(case when parent_id=6 then stringvalue) as col2
from tablename
group by parent_id
You can do conditional aggregation by using row_number() & dense_rank():
select max(case when seq1 = 1 then stringvalue end),
max(case when seq1 = 2 then stringvalue end)
from (select t.*,
dense_rank() over (order by parent_id) as seq1,
row_number() over (partition by parent_id order by stringvalue) seq2
from table t
) t
group by seq2;

How to group subtotals on the same row by date, by code

I couldn't find an equivalent question on here for this question. Apologies if this is a repeat
Basically I have a table with transactions. Each transaction has a code and a datetime stamp. I want to be able to create a SQL query so that the results look something like this
+------------+--------+--------+-------+--------+-------+--------+
| DATE | CODE1 | COUNT1 | CODE2 | COUNT2 | CODE3 | COUNT3 |
+------------+--------+--------+-------+--------+-------+--------+
| 2017-01-01 | George | 12 | John | 10 | Ringo | 114 |
+------------+--------+--------+-------+--------+-------+--------+
I currently have a query that I can pull the subtotals on individual lines, i.e:
SELECT CONVERT(mytime AS DATE), code, COUNT(*) FROM transactiontable
GROUP BY CONVERT(mytime AS DATE), code
ORDER BY CONVERT(mytime AS DATE), code
Would give me
DATE CODE COUNT
-----------------------------------
2017-01-01 George 12
2017-01-01 John 10
etc ...
I don't currently have a separate table for the codes, but I am considering it.
Thanks !
You also can use PIVOT for making this.
DECLARE #Table TABLE (DATE DATETIME, CODE VARCHAR(10), [COUNT] INT)
INSERT INTO #Table
VALUES
('2017-01-01','George',12),
('2017-01-01','John',10)
;WITH CTE AS
(
SELECT RN = ROW_NUMBER() OVER (ORDER BY DATE), * FROM #Table
)
SELECT * FROM
(SELECT DATE, CONCAT('CODE',RN) RN, CODE Value FROM CTE
UNION ALL
SELECT DATE, CONCAT('COUNT',RN) RN, CONVERT(VARCHAR,[COUNT]) Value FROM CTE
) SRC
PIVOT (MAX(Value) FOR RN IN ([CODE1],[COUNT1],[CODE2],[COUNT2])) PVT
Result:
DATE CODE1 COUNT1 CODE2 COUNT2
----------- ----------- ----------- -------- -------
2017-01-01 George 12 John 10
You can use window function row_number to form groups and use conditional aggregation to pivot:
select dt,
max(case when rn = 1 then code end) as code_1,
max(case when rn = 1 then cnt end) as code_1,
max(case when rn = 2 then code end) as code_2,
max(case when rn = 2 then cnt end) as code_2,
max(case when rn = 3 then code end) as code_3,
max(case when rn = 3 then cnt end) as code_3,
....
from (
select convert(date, mytime) as dt,
code,
count(*),
row_number() over (partition by convert(date, mytime) order by code) as rn
from transactiontable
group by convert(date, mytime), code
) t
group by dt
order by dt;

Two rows in one in SQL server

The answer is too close, Thanks
But
The problem is that if too many records to be entered
| id | name | age | Tel
------------------------------------------
1 | 1 | Frank | 40 | null
2 | 1 | null | 50 | 7834xx
3 | 1 | Alex | null | null
4 | 1 | null | 20 | null
5 | 2 | James | null | 4121xx
Query return the Maximum value
Like:
| id | name | age | Tel
------------------------------------------
1 | 1 | Frank | 50 | 7834xx
i need Select Query like this:
| id | name | age | Tel
------------------------------------------
1 | 1 | Alex | 20 | 7834xx
what do I do? Plz?
Here's a roundabout way to combine the last non-empty value of 3 columns:
-- Using a table variable for test data
declare #Test table (tableId int identity(1,1), id int, name varchar(100), age int, tel varchar(30));
insert into #Test (id, name, age, tel) values
(1,'Frank',40,null),
(1,null,50,'7834xx'),
(1,'Alex',null,null),
(1,null,20,null),
(2,'James',null,'4121xx');
select n.id, n.name, a.age, t.tel
from (
select top(1) with ties id, name
from #Test
where name is not null
order by row_number() over (partition by id order by tableId desc)
) n
inner join (
select top(1) with ties id, age
from #Test
where age is not null
order by row_number() over (partition by id order by tableId desc)
) a on (n.id = a.id)
inner join (
select top(1) with ties id, tel
from #Test
where tel is not null
order by row_number() over (partition by id order by tableId desc)
) t on (n.id = t.id);
or by re-using a CTE
;with CTE AS (
select * ,
row_number() over (partition by id, iif(name is not null,1,0) order by tableId desc) as rn_name,
row_number() over (partition by id, iif(age is not null,1,0) order by tableId desc) as rn_age,
row_number() over (partition by id, iif(tel is not null,1,0) order by tableId desc) as rn_tel
from #Test
)
select n.id, n.name, a.age, t.tel
from CTE n
join CTE a on (a.id = n.id and a.age is not null and a.rn_age = 1)
join CTE t on (t.id = n.id and t.tel is not null and t.rn_tel = 1)
where (n.name is not null and n.rn_name = 1);
Result :
╔════╦══════╦═════╦════════╗
║ id ║ name ║ age ║ tel ║
╠════╬══════╬═════╬════════╣
║ 1 ║ Alex ║ 20 ║ 7834xx ║
╚════╩══════╩═════╩════════╝
After looking at this answer more than a year later.
You could also use the window function first_value for this.
Without using any join.
select Id, name, age, tel
from
(
select Id
, row_number() over (partition by id order by tableId desc) as rn
, first_value(name) over (partition by id order by iif(name is null,1,0), tableId desc) as name
, first_value(age) over (partition by id order by iif(age is null,1,0), tableId desc) as age
, first_value(tel) over (partition by id order by iif(tel is null,1,0), tableId desc) as tel
from #Test
) q
where rn = 1
and name is not null and age is not null and tel is not null;
One simple way is to get max as below:
Select Id, Max(name) as [Name], Max(age) as Age, Max(Tel) as Tel
from yourtable
Group by Id
thanks for answering and helping.
i find this query:
SELECT TOP 10
(SELECT top(1) name FROM test1 where id=1 and name is not null order by autoID desc) as name
,(SELECT top(1) age FROM test1 where id=1 and age is not null order by autoID desc) as Age
,(SELECT top(1) Tel FROM test1 where id=1 and Tel is not null order by autoID desc) as Telephon
FROM [dbo].[test1]
group by id
its worked !!!
but i thing there are another Easy Way , maybe like this:
Select Id, NotNull(name) as [Name], NotNull(age) as Age, NotNull(Tel) as Tel
from yourtable Group by Id
???

Need to select table data into distinct columns based on a date in a row

I am not sure what I need here, looks sort of like I could use a pivot but I don't think it's that complicated and would like to avoid pivot if I can as I haven't used it much (er, at all).
I have data like this:
ID score notes CreateDate
1661 9.2 8.0 on Sept 2010 7/22/2010
1661 7.6 11/4/2010
1661 7.9 6/10/2011
1661 8.3 9/28/2011
1661 7.9 1/20/2012
I want to organize all that data on to one row with the oldest date being first and then use the next oldest date, then next oldest...until I use 4 or 5 dates. So the end result would look something like this:
ID score1 notes1 date1 score2 notes2 date2 score3 notes3 date3 score4 notes4 date4
1661 9.2 8.0 on Sept 2010 7/22/2010 7.6 blah 11/4/2010 7.9 blah2 6/10/2011 8.3 blah3 9/28/2011
PIVOT would be tricky in this situation, since you have more than one column per test (PIVOT works well if you only wanted to show Score1, Score2, Score3, etc). Fortunately, you can create a simple (if long-winded) solution with CASE statements:
select
ID,
max(case when RowNum = 1 then Score else null end) as Score1,
max(case when RowNum = 1 then Notes else null end) as Notes1,
max(case when RowNum = 1 then CreateDate else null end) as Date1,
max(case when RowNum = 2 then Score else null end) as Score2,
max(case when RowNum = 2 then Notes else null end) as Notes2,
max(case when RowNum = 2 then CreateDate else null end) as Date2,
max(case when RowNum = 3 then Score else null end) as Score3,
max(case when RowNum = 3 then Notes else null end) as Notes3,
max(case when RowNum = 3 then CreateDate else null end) as Date3,
max(case when RowNum = 4 then Score else null end) as Score4,
max(case when RowNum = 4 then Notes else null end) as Notes4,
max(case when RowNum = 4 then CreateDate else null end) as Date4,
max(case when RowNum = 5 then Score else null end) as Score5,
max(case when RowNum = 5 then Notes else null end) as Notes5,
max(case when RowNum = 5 then CreateDate else null end) as Date5
from
(
select
*, row_number() over (partition by ID order by CreateDate) as RowNum
from
mytable
) tt
group by
ID
This is hard-coded to cover 5 tests. It will be OK with less, but won't display a 6th. You can obviously create more CASE statements to handle more tests.
Just because I love pivots, I will show you how this can be done using the PIVOT function. In order to get the result with the PIVOT function you will first want to UNPIVOT your multiple columns score, notes and createdate. The unpivot process will convert the multiple columns into multiple rows.
Since you are using SQL Server 2008 you can use CROSS APPLY to unpivot your data, the first part of the query will be similar to:
;with cte as
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
)
select id, col, value
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from cte t
cross apply
(
values
('score', cast(score as varchar(10))),
('notes', notes),
('date', convert(varchar(10), createdate, 120))
) c (col, value)
) d;
See SQL Fiddle with Demo. Doing this gets your data in the format:
| ID | COL | VALUE |
| 1661 | score1 | 9.20 |
| 1661 | notes1 | 8.0 on Sept 2010 |
| 1661 | date1 | 2010-07-22 |
| 1661 | score2 | 7.60 |
| 1661 | notes2 | (null) |
| 1661 | date2 | 2010-11-04 |
| 1661 | score3 | 7.90 |
Now you can apply the PIVOT function:
;with cte as
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
)
select id, col, value
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from cte t
cross apply
(
values
('score', cast(score as varchar(10))),
('notes', notes),
('date', convert(varchar(10), createdate, 120))
) c (col, value)
) d
pivot
(
max(value)
for col in (score1, notes1, date1, score2, notes2, date2,
score3, notes3, date3, score4, notes4, date4,
score5, notes5, date5)
) piv;
See SQL Fiddle with Demo.
Then if you were going to have an unknown number of values for each id, you could implement dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col + cast(seq as varchar(10)))
from
(
select row_number() over(partition by id order by createdate) seq
from yourtable
) d
cross apply
(
select 'score', 1 union all
select 'notes', 2 union all
select 'date', 3
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, ' + #cols + '
from
(
select t.id,
col = col + cast(seq as varchar(10)),
value
from
(
select id, score, notes, createdate,
row_number() over(partition by id order by createdate) seq
from yourtable
) t
cross apply
(
values
(''score'', cast(score as varchar(10))),
(''notes'', notes),
(''date'', convert(varchar(10), createdate, 120))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both versions give the result:
| ID | SCORE1 | NOTES1 | DATE1 | SCORE2 | NOTES2 | DATE2 | SCORE3 | NOTES3 | DATE3 | SCORE4 | NOTES4 | DATE4 | SCORE5 | NOTES5 | DATE5 |
| 1661 | 9.20 | 8.0 on Sept 2010 | 2010-07-22 | 7.60 | (null) | 2010-11-04 | 7.90 | (null) | 2011-06-10 | 8.30 | (null) | 2011-09-28 | 7.90 | (null) | 2012-01-20 |

Display multiple rows and column values into a single row, multiple column values

I have to show multiple incomes, type of income and employer name values for a single individual in a single row. So, if 'A' has three different incomes from three different sources,
id | Name | Employer | IncomeType | Amount
123 | XYZ | ABC.Inc | EarningsformJob | $200.00
123 | XYZ | Self | Self Employment | $300.00
123 | XYZ. | ChildSupport| Support | $500.00
I need to show them as
id | Name | Employer1 | Incometype1| Amount1 | Employer2 | incometype2 | Amount2| Employer3 | Incometype3| Amount3.....
123 |XYZ | ABC.Inc |EarningsformJob | $200.00|Self | Self Employment | $300.00|ChildSupport| Support | $500.00.....
I need both 'fixed number of columns' (where we know how many times employer, incometype and amount colums are going to repeat)logic and 'dynamic display of columns' ( unknown number of times these columns are going to repeat)
Thanks.
Since you are using SQL Server there are several ways that you can transpose the rows of data into columns.
Aggregate Function / CASE: You can use an aggregate function with a CASE expression along with row_number(). This version would require that you have a known number of values to become columns:
select id,
name,
max(case when rn = 1 then employer end) employer1,
max(case when rn = 1 then IncomeType end) IncomeType1,
max(case when rn = 1 then Amount end) Amount1,
max(case when rn = 2 then employer end) employer2,
max(case when rn = 2 then IncomeType end) IncomeType2,
max(case when rn = 2 then Amount end) Amount2,
max(case when rn = 3 then employer end) employer3,
max(case when rn = 3 then IncomeType end) IncomeType3,
max(case when rn = 3 then Amount end) Amount3
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) src
group by id, name;
See SQL Fiddle with Demo.
PIVOT/UNPIVOT: You could use the UNPIVOT and PIVOT functions to get the result. The UNPIVOT converts your multiple columns of Employer, IncomeType and Amount into multiples rows before applying the pivot. You did not specific what version of SQL Server, assuming you have a known number of values then you could use the following in SQL Server 2005+ which uses CROSS APPLY with UNION ALL to unpivot:
select id, name,
employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select 'employer', employer union all
select 'incometype', incometype union all
select 'amount', cast(amount as varchar(50))
) c (col, value)
) src
pivot
(
max(value)
for col in (employer1, incometype1, amount1,
employer2, incometype2, amount2,
employer3, incometype3, amount3)
) piv;
See SQL Fiddle with Demo.
Dynamic Version: Lastly, if you have an unknown number of values then you will need to use dynamic SQL to generate the result.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(rn as varchar(10)))
from
(
select row_number() over(partition by id order by employer) rn
from yourtable
) d
cross apply
(
select 'employer', 1 union all
select 'incometype', 2 union all
select 'amount', 3
) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name,' + #cols + '
from
(
select id, name, col+cast(rn as varchar(10)) col, value
from
(
select id, name, employer, incometype, amount,
row_number() over(partition by id order by employer) rn
from yourtable
) t
cross apply
(
select ''employer'', employer union all
select ''incometype'', incometype union all
select ''amount'', cast(amount as varchar(50))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions give a result:
| ID | NAME | EMPLOYER1 | INCOMETYPE1 | AMOUNT1 | EMPLOYER2 | INCOMETYPE2 | AMOUNT2 | EMPLOYER3 | INCOMETYPE3 | AMOUNT3 |
-------------------------------------------------------------------------------------------------------------------------------------
| 123 | XYZ | ABC.Inc | EarningsformJob | 200 | ChildSupport | Support | 500 | Self | Self Employment | 300 |