SQL Server unpivot two columns - sql-server

I'm trying to pivot a table to get 3 columns
my example table is like :
CREATE TABLE tbl1 (A1 int, cA1 int,A2 int, cA2 int,A3 int, cA3 int)
GO
INSERT INTO tbl1 VALUES (60,2,30,3,10,5);
GO
I am using the query below to get tthe results from two columns:
select A, value from tbl1
unpivot
(
value
for A in ([A1], [A2],[A3])
) un1;
The results are like :
A | value
--+-------
A1|60
A2|30
A3|10
but I want to add and second column with and the results to be like :
A | value1 | value2
--+--------+--------
A1| 60 | 2
A2| 30 | 3
A3| 10 | 5
Any Help??

I would use APPLY:
select v.*
from tbl1 t cross apply
(values ('A1', t.A1, t.cA1),
('A2', t.A2, t.cA2),
('A3', t.A3, t.cA3)
) v(A, value1, value2);
CROSS APPLY implements a lateral join. This is much more powerful than merely unpivoting data, although unpivoting data is one simple way to start learning about lateral joins.

Another way with XML:
DECLARE #x xml = (SELECT * FROM tbl1 as t FOR XML AUTO, TYPE)
;WITH cte AS (
SELECT CAST(t.c.query('local-name(.)') as nvarchar(10)) as [name],
t.c.value('.','int') as [value],
p.number as [pos]
FROM [master]..spt_values p
CROSS APPLY #x.nodes('/t[position()=sql:column("number")]/#*') as t(c)
WHERE p.[type] = 'p'
)
SELECT c.[name] as A,
c.[value] as value1,
c1.[value] as value2
FROM cte c
INNER JOIN cte c1
ON c1.[name] = N'c'+c.[name] and c.pos = c1.pos
Output:
A value1 value2
A1 60 2
A2 30 3
A3 10 5

Related

Join 2 Table Variables in SQL

How to join 2 table variables which are not consists of a foreign key column.
DECLARE #InventoryIDList TABLE(ID INT)
DECLARE #ProductSupplierIDList TABLE(ID INT)
Excepted output
#InventoryList
--------------
123
456
789
111
#ProductSupplierIDList
--------------
999
888
777
666
#InventoryList ProductSupplierIDList
---------------------------------------
123 | 999
567 | 888
789 | 777
111 | 666
All are random data. I just want to combine the 2 table variable to look like above. I tried all the types of joins. But I need to have the upper mentioned output without having null values.
I tried the CROSS APPLY
SELECT *
FROM #InventoryIDList invList CROSS APPLY #ProductSupplierIDList prdList
But it gives me 5^2 number of elements as the result with duplicates.
Since the IDs are not in sequential order and can be random, I would recommend using an Identity on the table variables and joining on that:
DECLARE #InventoryIDList TABLE(JoiningID INT IDENTITY(1,1), ID INT)
DECLARE #ProductSupplierIDList TABLE(JoiningID INT IDENTITY(1,1), ID INT)
INSERT INTO #InventoryIDList
VALUES
(123),
(456),
(789),
(111)
INSERT INTO #productsupplierIDList
VALUES
(999),
(888),
(777),
(666)
SELECT i.id, p.id
FROM #inventoryIDList i
INNER JOIN #productsupplierIDList p
oN i.joiningid = p.JoiningID
I guess you need Row_Number and Full Outer Join, considering there is no relation between those 2 tables
SELECT I.ID,
P.ID
FROM (SELECT Rn = Row_number()OVER(ORDER BY ID),*
FROM #InventoryList) I
FULL JOIN (SELECT Rn = Row_number()OVER(ORDER BY ID),*
FROM #ProductSupplierIDList) p
ON I.RN = P.RN
Assuming that the JOIN criteria is the same "row number" in ascending ID order:
WITH invList AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM #InventoryIDList),
prdList AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM #ProductSupplierIDList)
SELECT *
FROM invList IL
JOIN prdList PL ON IL.RN = PL.RN;

SQL joined table pivot without aggregate

I have two tables:
Table1
ID TYPE
1 ABC1
2 ABC2
3 ABC3
Table2
ID Data
1 100
1 101
2 10
2 90
And I want the results to look like this:
ID Data1 Data2
1 100 101
2 10 90
But I'm having a total mare with my attempts at creating the pivot. So far I have:
With Inital_Data As (
Select
A.ID,
B.Data As Data1,
B.Data As Data2
From
Table1 A join
Table2 B on
A.ID = B.ID
)
Select *
From
Initial_Data
PIVOT
(Max(ID) FOR Data IN (Data1,Data2)) p
I know this is rubbish but so far even the logic of what I'm trying to achieve is escaping me, let alone the syntax! Can anyone give me a guiding hand?
Pivot with more than one column needs some tricks, I prefer the XML concatenation. First I take all values for each ID in only one XML, then you can take these values one by one:
Just paste this into an empty query window and execute. Adapt for your needs
EDIT: Includes column Type from TABLE1 as Caption for ID...
DECLARE #Table1 TABLE(ID INT,[TYPE] VARCHAR(10));
INSERT INTO #Table1 VALUES
(1,'ABC1')
,(2,'ABC2')
,(3,'ABC3');
DECLARE #Table2 TABLE(ID INT,DATA INT);
INSERT INTO #Table2 VALUES
(1,100)
,(1,101)
,(2,10)
,(2,90);
WITH DistinctIDs AS
(
SELECT DISTINCT tbl2.ID,tbl1.[TYPE]
FROM #Table2 AS tbl2
INNER JOIN #Table1 AS tbl1 ON tbl1.ID=tbl2.ID
)
SELECT ID,[TYPE]
,concatXML.x.value('/root[1]/item[1]/#data','int') AS Data1
,concatXML.x.value('/root[1]/item[2]/#data','int') AS Data2
FROM DistinctIDs AS dIDs
CROSS APPLY
(
SELECT ID AS [#id],DATA AS [#data]
FROM #Table2 AS tbl WHERE tbl.ID=dIDs.ID
FOR XML PATH('item'), ROOT('root'),TYPE
) AS concatXML(x)

SQL Server Joining Between Two Tables Based on Closest Value

I have two sets of data, as shown below.
Table 1:
enter code here
ID Value_1
1 233.67
2 83.28
3 84.49
4 1234.83
Table 2:
NewID Value_3 Value_4
5 NULL 83
6 NULL 85
7 NULL 235
I want to join the two tables in such a way that the resulting data set would look like below.
ID NewID Value_1 Value_2
1 7 233.67 235
2 5 83.28 83
3 6 84.49 85
4 NULL 1234.83 NULL
I know that using the ROUND command would cause future problems. Do any of you know how I could create the above resulting set?
Something like this:
DECLARE #Table1 TABLE
(
Id int,
Value1 float
)
INSERT INTO #Table1
VALUES
(1, 233.67),
(2, 83.28),
(3, 84.49),
(4, 1234.83)
DECLARE #Table2 TABLE
(
NewId int,
Value2 float
)
INSERT INTO #Table2
VALUES
(5, 83),
(6, 85),
(7, 235)
SELECT DISTINCT
t1.Id,
FIRST_VALUE(t2.NewId) OVER (PARTITION BY t1.Id ORDER BY ABS(t1.Value1 - t2.Value2) ASC) AS NewId,
t1.Value1,
FIRST_VALUE(t2.Value2) OVER (PARTITION BY t1.Id ORDER BY ABS(t1.Value1 - t2.Value2) ASC) AS Value2
FROM #Table1 t1
CROSS JOIN #Table2 t2
ORDER BY t1.Id
But you will get a result for ID=4 too.
This approach avoids a cross join, which requires every combination to be tested. It also works with SQL Server 2005 and up. It works by calculating a lower and upper bound between the midpoints of each record in Table2 and then joining on Table1 where Value_1 is between the midpoints. If Value_1 lands right on the boundary this code will round up (choose the higher of Value_4 to match).
--Load Table1
select 1 ID, convert(float,233.67) Value_1 into #Table1
insert into #Table1 select 2, 83.28
insert into #Table1 select 3, 84.49
insert into #Table1 select 4, 1234.83
--Load Table2
select 5 NewID, null Value_3, convert(float,83) Value_4 into #Table2
insert into #Table2 select 6, NULL, 85
insert into #Table2 select 7, NULL, 235
;with cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from #Table2
)
Select #Table1.ID,
NewTable2.NewID,
#Table1.Value_1,
NewTable2.Value_4 Value_2
from #Table1
full join
(
select Table2.NewID,
Table2.Value_3,
Table2.Value_4,
Table2Prev.Value_4 + (Table2.Value_4 - Table2Prev.Value_4) / 2.0 LowerBound,
Table2.Value_4 + (Table2Next.Value_4 - Table2.Value_4) / 2.0 UpperBound
from cte_Table2 Table2
left join cte_Table2 Table2Prev
on Table2.OrderNum = Table2Prev.OrderNum + 1
left join cte_Table2 Table2Next
on Table2.OrderNum = Table2Next.OrderNum - 1
) NewTable2
on (#Table1.Value_1 < UpperBound or UpperBound is null)
and (#Table1.Value_1 >= LowerBound or LowerBound is null)
order by 1
I noticed that you did not show a match for ID of 4 in your expected output. If you need some kind of range to exclude matches then you would have to add that restriction to the ON conditions of the join. For example you could exclude values that are outside a threshold of 10 by making sure you use a FULL JOIN and add this to the ON conditions:
and abs(#Table1.Value_1-NewTable2.Value_4) < 10.0
It occurs to me though that maybe what you are really trying to do is a type of join where not only is the value in Table1 matched with it's closest value in Table2 but the value in Table2 is also the closest value in Table1. In this case, you would have to build a sub-query for Table1 with the boundary conditions and then check for it's closest match as well. Like this:
;with cte_Table1 as
(
select *, ROW_NUMBER() over (order by Value_1) OrderNum
from #Table1
),
cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from #Table2
)
Select NewTable1.ID,
NewTable2.NewID,
NewTable1.Value_1,
NewTable2.Value_4 Value_2
from
(
select Table1.ID,
Table1.Value_1,
Table1Prev.Value_1 + (Table1.Value_1 - Table1Prev.Value_1) / 2.0 LowerBound,
Table1.Value_1 + (Table1Next.Value_1 - Table1.Value_1) / 2.0 UpperBound
from cte_Table1 Table1
left join cte_Table1 Table1Prev
on Table1.OrderNum = Table1Prev.OrderNum + 1
left join cte_Table1 Table1Next
on Table1.OrderNum = Table1Next.OrderNum - 1
) NewTable1
full join
(
select Table2.NewID,
Table2.Value_3,
Table2.Value_4,
Table2Prev.Value_4 + (Table2.Value_4 - Table2Prev.Value_4) / 2.0 LowerBound,
Table2.Value_4 + (Table2Next.Value_4 - Table2.Value_4) / 2.0 UpperBound
from cte_Table2 Table2
left join cte_Table2 Table2Prev
on Table2.OrderNum = Table2Prev.OrderNum + 1
left join cte_Table2 Table2Next
on Table2.OrderNum = Table2Next.OrderNum - 1
) NewTable2
on (NewTable1.Value_1 < NewTable2.UpperBound or NewTable2.UpperBound is null)
and (NewTable1.Value_1 >= NewTable2.LowerBound or NewTable2.LowerBound is null)
and (NewTable2.Value_4 < NewTable1.UpperBound or NewTable1.UpperBound is null)
and (NewTable2.Value_4 >= NewTable1.LowerBound or NewTable1.LowerBound is null)
order by 1
Now the records in the tables will only match if the values are the closest match both ways. This will ensure that each record in Table1 will be matched to at most 1 value in Table2. Because it's a full join you can get null values on either side... depending on which table is smaller.
One more thing to mention... this code might not handle duplicate values the way you need. If you have more than one of the same value in either Value_1 or Value_4, it will find a match to one of them but not both. If you want that... then you would have to change your common table expressions to:
;with cte_Table1 as
(
select *, ROW_NUMBER() over (order by Value_1) OrderNum
from (select distinct Value_1 from #Table1) tbl1
),
cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from (select distinct Value_4 from #Table2) tbl2
)
Then update your sub-queries to only output the values with the boundaries. This would give you best matches with between the unique values. Then you could join to Table1 and Table2 ON Table1.Value_1 = NewTable1.Value1 and Table2.Value_4 = NewTable2.Value_4 to get the rest of the fields.
Certainly some optimizations can be made if you have very large tables... like breaking out some of these sub-queries into indexed temporary tables.

Finding which COLUMN has a max( value per row

MSSQL
Table looks like so
ID 1 | 2 | 3 | 4 | 5
AA1 1 | 1 | 1 | 2 | 1
any clues on how I could make a query to return
ID | MaxNo
AA1 | 4
, usign the above table example? I know I could write a case blah when statement, but I have a feeling there's a much simpler way of doing this
You can use UNPIVOT to get these comparable items, correctly1, into the same column, and then use ROW_NUMBER() to find the highest valued row2:
declare #t table (ID char(3) not null,[1] int not null,[2] int not null,
[3] int not null,[4] int not null,[5] int not null)
insert into #t (ID,[1],[2],[3],[4],[5]) values
('AA1',1,1,1,2,1)
;With Unpivoted as (
select *,ROW_NUMBER() OVER (ORDER BY Value desc) rn
from #t t UNPIVOT (Value FOR Col in ([1],[2],[3],[4],[5])) u
)
select * from Unpivoted where rn = 1
Result:
ID Value Col rn
---- ----------- ------------------------- --------------------
AA1 2 4 1
1 If you have data from the same "domain" appearing in multiple columns in the same table (such that it even makes sense to compare such values), it's usually a sign of attribute splitting, where part of your data has, incorrectly, been used to form part of a column name.
2 In your question, you say "per row", and yet you've only given a one row sample. If we assume that ID values are unique for each row, and you want to find the maximum separately for each ID, you'd write the ROW_NUMBER() as ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Value desc) rn, to get (I hope) the result you're looking for.
You can use a cross apply where you do max() over the columns for one row.
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (values (T1.[1]),
(T1.[2]),
(T1.[3]),
(T1.[4]),
(T1.[5])) as T(Value)
) as T2
If you are on SQL Server 2005 you can use union all in the derived table instead of values().
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (select T1.[1] union all
select T1.[2] union all
select T1.[3] union all
select T1.[4] union all
select T1.[5]) as T(Value)
) as T2
SQL Fiddle

Combine two tables in SQL Server

I have tow tables with the same number of rows
Example:
table a:
1,A
2,B
3,C
table b:
AA,BB
AAA,BBB,
AAAA,BBBB
I want a new table made like that in SQL SErver:
1,A,AA,BB
2,B,AAA,BBB
3,C,AAAA,BBBB
How do I do that?
In SQL Server 2005 (or newer), you can use something like this:
-- test data setup
DECLARE #tablea TABLE (ID INT, Val CHAR(1))
INSERT INTO #tablea VALUES(1, 'A'), (2, 'B'), (3, 'C')
DECLARE #tableb TABLE (Val1 VARCHAR(10), Val2 VARCHAR(10))
INSERT INTO #tableb VALUES('AA', 'BB'),('AAA', 'BBB'), ('AAAA', 'BBBB')
-- define CTE for table A - sort by "ID" (I just assumed this - adapt if needed)
;WITH DataFromTableA AS
(
SELECT ID, Val, ROW_NUMBER() OVER(ORDER BY ID) AS RN
FROM #tablea
),
-- define CTE for table B - sort by "Val1" (I just assumed this - adapt if needed)
DataFromTableB AS
(
SELECT Val1, Val2, ROW_NUMBER() OVER(ORDER BY Val1) AS RN
FROM #tableb
)
-- create an INNER JOIN between the two CTE which just basically selected the data
-- from both tables and added a new column "RN" which gets a consecutive number for each row
SELECT
a.ID, a.Val, b.Val1, b.Val2
FROM
DataFromTableA a
INNER JOIN
DataFromTableB b ON a.RN = b.RN
This gives you the requested output:
You could do a rank over the primary keys, then join on that rank:
SELECT RANK() OVER (table1.primaryKey),
T1.*,
T2.*
FROM
SELECT T1.*, T2.*
FROM
(
SELECT RANK() OVER (table1.primaryKey) [rank], table1.* FROM table1
) AS T1
JOIN
(
SELECT RANK() OVER (table2.primaryKey) [rank], table2.* FROM table2
) AS T2 ON T1.[rank] = T2.[rank]
Your query is strange, but in Oracle you can do this:
select a.*, tb.*
from a
, ( select rownum rn, b.* from b ) tb -- temporary b - added rn column
where a.c1 = tb.rn -- assuming first column in a is called c1
if there is not column with numbers in a you can do same trick twice
select ta.*, tb.*
from ( select rownum rn, a.* from a ) ta
, ( select rownum rn, b.* from b ) tb
where ta.rn = tb.rn
Note: be aware that this can generate random combination, for example
1 A AA BB
2 C A B
3 B AAA BBB
because there is no order by in ta and tb

Resources