I have data in a SQLServer table like this:
ID Name Year Value
-- ---- ---- -----
2 Ted 2013 2000
2 Ted 2012 1000
I need the view syntax to output this:
ID Name Yr1 Value1 Yr2 Value2
-- ---- --- ------ --- ------
2 Ted 2013 2000 2012 1000
No cursors if possible.
Any clues would be greatful.
In SQL Server there are several ways that you can get the result.
If you have a limited number of values, then you can easily hard-code the result. One way you can get the result would be using an aggregate function with a CASE expression:
select d.id,
d.name,
max(case when seq = 1 then year end) year1,
max(case when seq = 1 then value end) value1,
max(case when seq = 2 then year end) year2,
max(case when seq = 2 then value end) value2
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) d
group by d.id, d.name;
See SQL Fiddle with Demo. If you want to use the PIVOT function, then I would suggest first unpivoting the data in the year and value columns first. The process of unpivot converts the multiple columns into multiple rows. You can use the UNPIVOT function, but in my example I used CROSS APPLY with a UNION ALL query and the code is:
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select 'year', t.year union all
select 'value', t.value
) c (col, val)
See SQL Fiddle with Demo. This converts your multiple columns into a slightly different format with multiple rows:
| ID | NAME | COL | VAL |
| 2 | Ted | year1 | 2013 |
| 2 | Ted | value1 | 2000 |
| 2 | Ted | year2 | 2012 |
| 2 | Ted | value2 | 1000 |
You can then apply the PIVOT function on this to get your final desired result:
select id, name, year1, value1, year2, value2
from
(
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select 'year', t.year union all
select 'value', t.value
) c (col, val)
) d
pivot
(
max(val)
for col in (year1, value1, year2, value2)
) piv;
See SQL Fiddle with Demo. Finally if you have an unknown number of values that you want to transform from rows into columns, then you can use dynamic SQL inside a stored procedure:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(seq as varchar(4)))
from
(
select row_number() over(partition by id order by year desc) seq
from yourtable
) d
cross apply
(
select 'year', 1 union all
select 'value', 2
) 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, name,' + #cols + '
from
(
select t.id, t.name,
col = c.col+cast(seq as varchar(4)),
c.val
from
(
select id, name, year, value,
row_number() over(partition by id order by year desc) seq
from yourtable
) t
cross apply
(
select ''year'', t.year union all
select ''value'', t.value
) c (col, val)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| ID | NAME | YEAR1 | VALUE1 | YEAR2 | VALUE2 |
| 2 | Ted | 2013 | 2000 | 2012 | 1000 |
Related
I have a table that looks like this:
att1 att2
| a | 1 |
| a | 2 |
| b | 2 |
| b | 3 |
And I need the different record of att2 for the duplicate value on att1 to be grouped into a new column like this
att1 att2 att3
| a | 1 | 2 |
| b | 2 | 3 |
I tried to pivot, I tried to self join, but I can't seem to find the query to separate the values like this. Can someone please help me? Thanks
Simply Use method of Stuff with XML to achieve the above result would be simple.
SELECT DISTINCT
AA.att1,
split.a.value('/X[1]', 'NVARCHAR(MAX)') [att2],
split.a.value('/X[2]', 'NVARCHAR(MAX)') [att3]
FROM
(
SELECT A.att1,
CAST('<X>'+REPLACE(A.att2, ',', '</X><X>')+'</X>' AS XML) AS String
FROM
(
SELECT DISTINCT
T.att1,
att2 = STUFF(
(
SELECT DISTINCT
','+att2
FROM <table_name>
WHERE att1 = T.att1 FOR XML PATH('')
), 1, 1, '')
FROM <table_name> T
) A
) AA
CROSS APPLY String.nodes('/X') AS split(a);
Final Result :
att1 att2 att3
| a | 1 | 2 |
| b | 2 | 3 |
If you have a known or maximum number of columns and you don't want to go dynamic
Example
Select att1
,att2 = max(case when RN=2 then att2 end)
,att3 = max(case when RN=3 then att2 end)
From (
Select *
,RN = 1+Row_Number() over (Partition By att1 order by att2)
From YourTable
) A
Group By att1
Returns
att1 att2 att3
a 1 2
b 2 3
you can write a query with the combination of XML Path and built-in STUFF function. that way you can achieve the result you want with dynamic column count.
see this page for an example:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
and a sample query from the page above, which accomplishes what you need:
DECLARE #cols NVARCHAR(MAX), #query NVARCHAR(MAX);
SET #cols = STUFF(
(
SELECT DISTINCT
','+QUOTENAME(c.[DocName])
FROM [dbo].[InsuranceClaims] c FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SET #query = 'SELECT [PolNumber], '+#cols+'from (SELECT [PolNumber],
[PolType],
[submitted] AS [amount],
[DocName] AS [category]
FROM [dbo].[InsuranceClaims]
)x pivot (max(amount) for category in ('+#cols+')) p';
EXECUTE (#query);
Try this code
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
CREATE TABLE #t(att1 VARCHAR(10) ,att2 INT)
INSERT INTO #t(att1,att2)
SELECT 'a' AS att1, 1 AS att2 UNION ALL
SELECT 'a' , 2 UNION ALL
SELECT 'b' , 2 UNION ALL
SELECT 'b' , 3
SELECT * FROM #t
using lag and cte we could get the result
;With cte
AS
(
SELECT
Att1,
Att2,
CASE
WHEN Att1 = Att3 THEN MAX(Att2)OVER(Partition by Att1 order by Att1)
END att3
FROM (
SELECT Att1,
Lag(Att1, 1)
OVER (
ORDER BY Att1)AS att3,
Att2
FROM #t
)dt
),
CTe2
AS
(
SELECT *,
ROW_NUMBER()
OVER(
PARTITION BY Att1
ORDER BY Att1) AS RNk
FROM (SELECT DISTINCT Att1,
Att2,
Max(Att3)
OVER(
Partition by Att1 ORDER BY Att1) AS att3
FROM cte)dt
)
SELECT att1,Att2,att3 FROM CTe2 WHERE RNk=1
Result
Att1 Att2 Att3
---------------------
a 1 2
b 2 3
I'm new to SQL (more precisely T-SQL) and I can't seem to wrap my head around this one. I'm sure there is a simple solution and I'm just not thinking of (maybe involving subqueries and/or a table pivot). But I was hoping one of you SQL whizzes could help out a clueless newb.
Basically, I need to turn this data:
CaseNumber|DecisionNumber|Date |Decision
----------+--------------+-----------+--------
444 |29833 |04/05/2005 |Sell
444 |29777 |05/10/2006 |Sell
444 |29654 |08/19/2007 |Buy
468 |29230 |08/19/2006 |Sell
468 |29192 |08/19/2011 |Sell
Into this result:
CaseNumber|DecisionNumber1|Date1 |Decision1|DecisionNumber2|Date2 |Decision2|DecisionNumber3|Date3 |Decision3
----------+---------------+------------+---------+---------------+------------+---------+---------------+------------+---------
444 |29833 |04/05/2005 |Sell |29777 |05/10/2006 |Sell |29654 |08/19/2007 |Buy
468 |29230 |08/19/2006 |Sell |29192 |08/19/2011 |Sell |NULL |NULL |NULL
Any ideas would be MUCH appreciated.
The problem with what you are trying to do, is that you are trying to pivot more than one column at a time. This can be done with unpivot then pivot. Something like this:
WITH CTE
AS
(
SELECT
CAST(CaseNumber AS NVARCHAR(50)) AS CaseNumber
,CAST(DecisionNumber AS NVARCHAR(50)) AS DecisionNumber
,CAST(Date AS NVARCHAR(50)) AS [Date]
,ROW_NUMBER() OVER(PARTITION BY [CaseNumber] ORDER BY DecisionNumber DESC) AS RN
FROM Table1
), unpivoted
AS
(
SELECT CaseNumber, val, col + ' ' + CAST(RN AS NVARCHAR(50)) AS col
FROM CTE
UNPIVOT
(
val
FOR col IN(DecisionNumber, Date)
) AS u
)
SELECT *
FROM unpivoted AS u
PIVOT
(
MAX(val)
FOR col IN([DecisionNumber 1], [Date 1],
[DecisionNumber 2], [Date 2],
[DecisionNumber 3], [Date 3])
) AS p;
SQL Fiddle Demo
This will give you:
| CaseNumber | DecisionNumber 1 | Date 1 | DecisionNumber 2 | Date 2 | DecisionNumber 3 | Date 3 |
|------------|------------------|------------|------------------|------------|------------------|------------|
| 444 | 29833 | 2005-04-05 | 29777 | 2006-05-10 | 29654 | 2007-08-19 |
| 468 | 29230 | 2006-08-19 | 29192 | 2011-08-19 | (null) | (null) |
However, if you want to do this for any number of decisionnumber and date, you can do this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
WITH CTE
AS
(
SELECT
CAST(CaseNumber AS NVARCHAR(50)) AS CaseNumber
,CAST(DecisionNumber AS NVARCHAR(50)) AS DecisionNumber
,CAST(Date AS NVARCHAR(50)) AS [Date]
,ROW_NUMBER() OVER(PARTITION BY [CaseNumber] ORDER BY DecisionNumber DESC) AS RN
FROM Table1
), Data
AS
(
SELECT col, MAX(RN) AS RN
FROM
(
SELECT RN, col + CAST(RN AS NVARCHAR(50)) AS col
FROM CTE
UNPIVOT
(
val
FOR col IN(DecisionNumber, Date)
) AS u
) AS t
GROUP BY col
)
select #cols = STUFF((SELECT ',' +
QUOTENAME(col)
FROM Data
ORDER BY RN
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = 'WITH CTE
AS
(
SELECT
CAST(CaseNumber AS NVARCHAR(50)) AS CaseNumber
,CAST(DecisionNumber AS NVARCHAR(50)) AS DecisionNumber
,CAST(Date AS NVARCHAR(50)) AS [Date]
,ROW_NUMBER() OVER(PARTITION BY [CaseNumber] ORDER BY DecisionNumber DESC) AS RN
FROM Table1
), unpivoted
AS
(
SELECT CaseNumber, val, col + CAST(RN AS NVARCHAR(50)) AS col
FROM CTE
UNPIVOT
(
val
FOR col IN(DecisionNumber, Date)
) AS u
)
SELECT *
FROM unpivoted AS u
PIVOT
(
MAX(val)
FOR col IN('+ #cols + ')
) AS p;';
EXECUTE(#query);
Dynamic SQL Demo
In a table there are two columns:
-----------
| A | B |
-----------
| 1 | 5 |
| 2 | 1 |
| 3 | 2 |
| 4 | 1 |
-----------
Want a table where if A=B then
-------------------
|Match | notMatch|
-------------------
| 1 | 5 |
| 2 | 3 |
| Null | 4 |
-------------------
How can i do this?
I tried something which shows the Matched part
select distinct C.A as A from Table c inner join Table d on c.A=d.B
Try this:
;WITH TempTable(A, B) AS(
SELECT 1, 5 UNION ALL
SELECT 2, 1 UNION ALL
SELECT 3, 2 UNION ALL
SELECT 4, 1
)
,CTE(Val) AS(
SELECT A FROM TempTable UNION ALL
SELECT B FROM TempTable
)
,Match AS(
SELECT
Rn = ROW_NUMBER() OVER(ORDER BY Val),
Val
FROM CTE c
GROUP BY Val
HAVING COUNT(Val) > 1
)
,NotMatch AS(
SELECT
Rn = ROW_NUMBER() OVER(ORDER BY Val),
Val
FROM CTE c
GROUP BY Val
HAVING COUNT(Val) = 1
)
SELECT
Match = m.Val,
NotMatch= n.Val
FROM Match m
FULL JOIN NotMatch n
ON n.Rn = m.Rn
Try with EXCEPT, MINUS and INTERSECT Statements.
like this:
SELECT A FROM TABLE1 INTERSECT SELECT B FROM TABLE1;
You might want this:
SELECT DISTINCT
C.A as A
FROM
Table c
LEFT OUTER JOIN
Table d
ON
c.A=d.B
WHERE
d.ID IS NULL
Please Note that I use d.ID as an example because I don't see your schema. An alternate is to explicitly state all d.columns IS NULL in WHERE clause.
Your requirement is kind of - let's call it - interesting. Here is a way to solve it using pivot. Personally I would have chosen a different table structure and another way to select data:
Test data:
DECLARE #t table(A TINYINT, B TINYINT)
INSERT #t values
(1,5),(2,1),
(3,2),(4,1)
Query:
;WITH B AS
(
( SELECT A FROM #t
EXCEPT
SELECT B FROM #t)
UNION ALL
( SELECT B FROM #t
EXCEPT
SELECT A FROM #t)
), A AS
(
SELECT A val
FROM #t
INTERSECT
SELECT B
FROM #t
), combine as
(
SELECT val, 'A' col, row_number() over (order by (select 1)) rn FROM A
UNION ALL
SELECT A, 'B' col, row_number() over (order by (select 1)) rn
FROM B
)
SELECT [A], [B]
FROM combine
PIVOT (MAX(val) FOR [col] IN ([A], [B])) AS pvt
Result:
A B
1 3
2 4
NULL 5
I have a table customer in the database as below.
ID UID Address1 Name code
10 5 A Jac 683501
11 5 B Joe 727272
13 6 C mat 373737
first two records (10,11) have a common uID -5 . These two records can be considered as a single unit.
fourth record is having a separate UID , so it is a separate unit
I need to produce an output in a csv file such a way that
ID UID Name code Address1 Name2 Code2
10 5 jac 683501 A Joe 727272
13 6 mat 373737 C
Name2 and code2 values are from the second row, since UID is same for first two records, we can considered it as a single unit.
Can anyone give hints to query for generating these records.
This process to transform data from rows into columns is known as a PIVOT. There are several ways that this can be done.
You can use a row_number() along with an aggregate function with a CASE expression:
select min(id),
uid,
max(case when seq = 1 then name end) Name,
max(case when seq = 1 then code end) Code,
max(case when seq = 1 then Address1 end) Address1,
max(case when seq = 2 then name end) Name2,
max(case when seq = 2 then code end) code2,
max(case when seq = 2 then Address1 end) Address1_2
from
(
select id, uid, address1, name, code,
row_number() over(partition by uid order by id) seq
from yourtable
) d
group by uid;
See SQL Fiddle with Demo.
You could use both the UNPIVOT and the PIVOT function:
select id, uid,
name1, code1, address1, name2, code2, address2
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) src
pivot
(
max(value)
for col in (name1, code1, address1, name2, code2, address2)
) piv;
See SQL Fiddle with Demo.
Finally if you have an unknown number of values for each uid, then you can use 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 uid order by id) seq
from yourtable
) d
cross apply
(
select 'name', 1 union all
select 'code', 2 union all
select 'address', 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, uid,' + #cols + '
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions will give a result:
| ID | UID | NAME1 | CODE1 | ADDRESS1 | NAME2 | CODE2 | ADDRESS2 |
---------------------------------------------------------------------
| 10 | 5 | Jac | 683501 | A | Joe | 727272 | B |
| 13 | 6 | mat | 373737 | C | (null) | (null) | (null) |
Try inner join.
select u2.firstname,u2.col2,
from users u
inner join users u2 on u.userid=u2.userid
where u.firstname=u2.lastname
I have this kind of table :
Name Date Value
-----------------------
Test 1/1/2001 10
Test 2/1/2001 17
Test 3/1/2001 52
Foo 5/4/2011 15
Foo 6/4/2011 321
My 15/5/2005 36
My 25/7/2005 75
And I would like to show the results like this :
Name Date Value Name Date Value Name Date Value
---------------------------------------------------------------------
Test 1/1/2001 10 Foo 5/4/2011 15 My 15/5/2005 36
Test 2/1/2001 17 Foo 6/4/2011 321 My 25/7/2005 75
Test 3/1/2001 52
I need to show as many columns as what is present in my Name column
How could I do this in Sql ?
In order to get the result that you want, you are going to have to unpivot the columns in your table and apply the pivot function.
The unpivot can be done using either the UNPIVOT function or you can use CROSS APPLY with VALUES.
UNPIVOT:
select rn,
col +'_'+cast(dr as varchar(10)) col,
new_values
from
(
select name,
convert(varchar(10), date, 101) date,
cast(value as varchar(10)) value,
dense_rank() over(order by name) dr,
row_number() over(partition by name order by date) rn
from yourtable
) d
unpivot
(
new_values
for col in (name, date, value)
) un;
CROSS APPLY:
select rn,
col +'_'+cast(dr as varchar(10)) col,
c.value
from
(
select name,
convert(varchar(10), date, 101) date,
cast(value as varchar(10)) value,
dense_rank() over(order by name) dr,
row_number() over(partition by name order by date) rn
from yourtable
) d
cross apply
(
values
('Name', name), ('Date', date), ('Value', Value)
) c (col, value);
See SQL Fiddle with Demo of both versions. This gives the result:
| RN | COL | NEW_VALUES |
-----------------------------
| 1 | name_1 | Foo |
| 1 | date_1 | 04/05/2011 |
| 1 | value_1 | 15 |
| 2 | name_1 | Foo |
| 2 | date_1 | 04/06/2011 |
| 2 | value_1 | 321 |
| 1 | name_2 | My |
| 1 | date_2 | 05/15/2005 |
| 1 | value_2 | 36 |
These queries take your existing columns values and converts them to rows. Once they are in rows, you create the new column names by using the windowing function dense_rank.
Once the data has been converted to rows, you then use the new column names (created with the dense_rank value) and apply the PIVOT function.
PIVOT with UNPIVOT:
select name_1, date_1, value_1,
name_2, date_2, value_2,
name_3, date_3, value_3
from
(
select rn,
col +'_'+cast(dr as varchar(10)) col,
new_values
from
(
select name,
convert(varchar(10), date, 101) date,
cast(value as varchar(10)) value,
dense_rank() over(order by name) dr,
row_number() over(partition by name order by date) rn
from yourtable
) d
unpivot
(
new_values
for col in (name, date, value)
) un
) src
pivot
(
max(new_values)
for col in (name_1, date_1, value_1,
name_2, date_2, value_2,
name_3, date_3, value_3)
) piv;
See SQL Fiddle with Demo
PIVOT with CROSS APPLY:
select name_1, date_1, value_1,
name_2, date_2, value_2,
name_3, date_3, value_3
from
(
select rn,
col +'_'+cast(dr as varchar(10)) col,
c.value
from
(
select name,
convert(varchar(10), date, 101) date,
cast(value as varchar(10)) value,
dense_rank() over(order by name) dr,
row_number() over(partition by name order by date) rn
from yourtable
) d
cross apply
(
values
('Name', name), ('Date', date), ('Value', Value)
) c (col, value)
) src
pivot
(
max(value)
for col in (name_1, date_1, value_1,
name_2, date_2, value_2,
name_3, date_3, value_3)
) piv;
See SQL Fiddle with Demo.
Dyanmic PIVOT:
The above versions will work great if you have a limited or known number of columns, if not, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col +'_'+cast(dr as varchar(10)))
from
(
select dense_rank() over(order by name) dr
from yourtable
) t
cross apply
(
values(1, 'Name'), (2, 'Date'), (3, 'Value')
) c (sort, col)
group by col, dr, sort
order by dr, sort
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select rn,
col +''_''+cast(dr as varchar(10)) col,
c.value
from
(
select name,
convert(varchar(10), date, 101) date,
cast(value as varchar(10)) value,
dense_rank() over(order by name) dr,
row_number() over(partition by name order by date) rn
from yourtable
) d
cross apply
(
values
(''Name'', name), (''Date'', date), (''Value'', Value)
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p'
execute(#query)
See SQL Fiddle with Demo.
The result for each of the queries is:
| NAME_1 | DATE_1 | VALUE_1 | NAME_2 | DATE_2 | VALUE_2 | NAME_3 | DATE_3 | VALUE_3 |
-------------------------------------------------------------------------------------------------
| Foo | 04/05/2011 | 15 | My | 05/15/2005 | 36 | Test | 01/01/2001 | 10 |
| Foo | 04/06/2011 | 321 | My | 07/25/2005 | 75 | Test | 01/02/2001 | 17 |
| (null) | (null) | (null) | (null) | (null) | (null) | Test | 01/03/2001 | 52 |
By hand or with a program. Most people are accustomed to showing output in a linear fashion (the default). If someone is asking you to do this, you can tell them that's not how the application works. You can export the result set to csv and then import that into something like Excel and reformat it by hand or use a serverside language like ASP.net or PHP to format the results into a table.
When you're parsing the output you could check the last var Name against the current. If they're different then add a column. It would still be tricky to script it because they will more than likely come out of the database in order. So you would have a sequence like test, test, test, foo, foo which would mean that you need to create a multidimensional array to organize the data to get a column count. Then setup the table based on that with a counter that counts row names, then data underneath.
I'm not sure which apps you're familiar with, so in PHP the output would look something like this from the multidimensional array.
row [1]['name']=test
row [1][test][1]['date'] = 1/1/2001
This is more of a visual output though. The databases are designed to hold data and return it in an intuitive fashion.