Transform row in column in a table - sql-server

I am trying to rotate the visualization of a table showing the lines as columns without any kind of aggregation.
Suppose I have this table
create table user
id int,
name nvarchar(100),
company nvarchar(100),
division nvarchar(100),
city nvarchar(100)
that can be retrieved with this select
select name,company division, city from user order by id
wich gives me this result
john Company1 division1 City1
Peter Company2 division2 City2
Mary Company3 division3 City3
.
.
but what I need is to show each line as a column and the first column with the name of the field like this
Name john Peter Mary ....
Company Company1 Company2 Company3 ....
Division division1 division2 division3 ....
City City1 City2 City3 ....
How can I accomplish this? I Tried using this unpivot
select col,value
from
(select cast(name as varchar) as name,
cast(Company as varchar) as company,
cast(Division as varchar) as division
cast(City as varchar) as city
from user) p
unpivot
(value for col in (name,company,division,city)) as unpvt
but this is what I got (Note: I want all the names in the same row)
name john
Company Company1
Division division1
City City1
name peter // this should be in the first row as a second column
Company Company2
Division division2
City City2
...

This is super ugly, but it's the only way I could figure out how to do what you want solely in SQL Server. If you copy and paste the code it should run and give you results and leave your database clean. I use a couple permanent tables to work around some dynamic sql scoping limitations, but I drop them both before it's done.
If Object_ID('tempdb..#userInfo') Is Not Null Drop Table #userInfo
Create Table #userInfo (id Int, name Varchar(100), company Varchar(100), division Varchar(100), city Varchar(100))
Insert #userInfo (id, name, company, division, city)
Values (1, 'john','company1', 'division1', 'city1'),
(2, 'peter','company2', 'division2', 'city2'),
(3, 'mary','company3', 'division3', 'city3'),
(4, 'timmy','company4', 'division4', 'city4'),
(5, 'nancy','company5', 'division5', 'city5'),
(6, 'james','company6', 'division6', 'city6'),
(7, 'brandon','company7', 'division7', 'city7'),
(8, 'jay','company8', 'division8', 'city8')
If Object_ID('tempdb..#unPivoted') Is Not Null Drop Table #unPivoted
Create Table #unPivoted (id Int, rid Int, col Varchar(100), value Varchar(100))
Insert #unPivoted
Select id, Row_Number() Over (Partition By id Order By value) As rID, col, value
From #userInfo p
Unpivot (value For col In (name, company, division, city)) As u
If Object_ID('dbo.TempQueryOutput') Is Not Null Drop Table dbo.TempQueryOutput
Select 1 As OrderCol,'City' As ColName Into dbo.TempQueryOutput
Union
Select 2,'Company'
Union
Select 3,'Division'
Union
Select 4,'Name'
Declare #sql Nvarchar(Max),
#maxID Int,
#loopIter Int = 1
Select #maxID = Max(id)
From #userInfo
While #loopIter <= #maxID
Begin
Set #sql = 'Select o.*, u.value As Col' + Convert(Nvarchar(100),#loopIter) + ' Into dbo.TempQueryTable
From dbo.TempQueryOutput o
Join #unPivoted u
On o.OrderCol = u.rid
And u.id = ' + Convert(Nvarchar(100),#loopIter)
Exec sp_executeSQL #sql
If Object_ID('dbo.TempQueryOutput') Is Not Null Drop Table dbo.TempQueryOutput
Select * Into dbo.TempQueryOutput
From dbo.TempQueryTable
If Object_ID('dbo.TempQueryTable') Is Not Null Drop Table dbo.TempQueryTable
Set #loopIter = #loopIter + 1
End
Update dbo.TempQueryOutput
Set OrderCol = Case
When ColName = 'Name' Then 1
When ColName = 'Company' Then 2
When ColName = 'Division' Then 3
When ColName = 'City' Then 4
End
Select *
From dbo.TempQueryOutput
Order By OrderCol
If Object_ID('dbo.TempQueryOutput') Is Not Null Drop Table dbo.TempQueryOutput

Related

SQL Server query items with IN query but keep order

I have to query specific order of string IDs example data:
| ID | RES |
---------------
| A_12 | 1.89 |
| B_27 | 4.53 |
| B_28 | 1.02 |
| C_23 | 2.67 |
A tool generated a specific order which does not follow any standard ordering rule, and I cannot change that order.
I am getting ~20000 of these rows and the RES is misaligned.
I'd like to make a simple query which would collect all needed records by a list IDs and would give me a custom defined ordered list of results.
Something like:
SELECT RES FROM TABLE1 WHERE ID IN ('A_12', 'C_23', 'B_28', 'B_27')
and I'd lke it to return
1.89
2.67
1.02
4.53
I understand IN query would not follow order as under the hood it most likely gets translated to (ID = A OR ID = B OR ID = C) query.
How do I enforce the result of the IN query to maintain my defined order? Do I need to create a temp table with one column for maintaining order? Any good solutions?
Use JOIN instead of using IN and explicitly specify your order:
DECLARE #Test TABLE (
ID VARCHAR(32),
RES DECIMAL(5,2)
)
INSERT #Test (ID, RES)
VALUES
('A_12', 1.89),
('B_27', 4.53),
('B_28', 3.54),
('C_23', 2.67)
SELECT t.ID, t.RES
FROM #Test t
JOIN (
VALUES
('A_12', 1),
('C_23', 2),
('B_28', 3),
('B_27', 4)
) o(ID, OrderId) ON t.ID = o.ID
ORDER BY o.OrderId
Instead of temp table you can use values where you specify the desired order in the additional column, like this:
declare #table1 table(id varchar(10), res decimal(10,2));
insert into #table1 (id, res)
values
('A_12', 1.89),
('B_27', 4.53),
('B_28', 3.54),
('C_23', 2.67);
select t.*
from #table1 t
join (values(1, 'A_12'), (2, 'C_23'), (3, 'B_28'), (4, 'B_27')) v(id,val)
on t.id = v.val
order by v.id;
#Table1 here is a substitute of your physical Table1.
There is no order to keep.
Returns of a select are NOT ORDERED by SQL basic definition, UNLESS YOU DEFINE AN ORDER.
So, there is no order to keep. Period.
If you want to keep one, use a temporary table / table variable for the valeus in IN (and obviously a join) and order by an order you also keep in a second field in said variable.
And no, this is not new - SQL is based on the SET theorem ever since Cobb published his famous paper back in the 1960s or so and never had order in returned results outside of side effects of implementation.
Do I need to create a temp table with one column for maintaining order
This seems to be working:
create table #tmp
(
CustomOrder int,
ID varchar(100)
)
insert into #tmp values (1, 'A_12')
insert into #tmp values (2, 'C_23')
insert into #tmp values (3, 'B_28')
insert into #tmp values (4, 'B_27')
query:
SELECT RES FROM TABLE1 INNER JOIN #tmp ON TABLE1.ID = #tmp.ID WHERE TABLE1.ID IN ('A_12', 'C_23', 'B_28', 'B_27')
ORDER BY #tmp.CustomOrder
output:
1.89
2.67
1.02
4.53
Any better and easier solution?
Just a different approach:
SELECT RES FROM TABLE1 WHERE ID IN ('A_12')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('C_23')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('B_28')
UNION ALL
SELECT RES FROM TABLE1 WHERE ID IN ('B_27')
I supposed that the JOIN option is more efficent than this approach. If you want to automatize this option:
DROP TABLE #TABLE1
CREATE TABLE #TABLE1(ID NVARCHAR(4), RES FLOAT)
INSERT INTO #TABLE1 VALUES('A_12',1.89)
INSERT INTO #TABLE1 VALUES('B_27',4.53)
INSERT INTO #TABLE1 VALUES('B_28',1.02)
INSERT INTO #TABLE1 VALUES('C_23',2.67)
DECLARE #ID TABLE(ID NVARCHAR(4) not null);
--HERE HAVE TO INSERT IN ORDER YOU WANT TO RETURN THE RESULTS IN THE QUERY
insert into #ID VALUES('A_12')
insert into #ID VALUES('B_27')
insert into #ID VALUES('B_28')
insert into #ID VALUES('C_23')
DECLARE #UNIONALL NVARCHAR(10) = CHAR(13) + N'UNION ALL'
DECLARE #QUERY NVARCHAR(MAX) = NULL
DECLARE #ID_SEARCH NVARCHAR(4) = NULL
DECLARE C CURSOR FAST_FORWARD FOR SELECT ID FROM #ID
OPEN C
FETCH NEXT FROM C INTO #ID_SEARCH
SET #QUERY = N'SELECT RES FROM #TABLE1 WHERE ID = ''' + #ID_SEARCH + ''' '
FETCH NEXT FROM C INTO #ID_SEARCH
WHILE ##FETCH_STATUS = 0 BEGIN
SET #QUERY = #QUERY + #UNIONALL
SET #QUERY = #QUERY + N' SELECT RES FROM #TABLE1 WHERE ID = ''' + #ID_SEARCH + ''' '
FETCH NEXT FROM C INTO #ID_SEARCH
END
EXECUTE master..sp_executesql #QUERY

Store query result in variable

I have declared 6 variables in a stored procedure and I'd like to store a query result (which may bring up to 6 records) into each one of those variables. My query looks like this:
DECLARE
#Sib1 varchar(20),
#Sib2 varchar(20),
#Sib3 varchar(20),
#Sib4 varchar(20),
#Sib5 varchar(20),
#Sib6 varchar(20)
select
PC.SKU
from
Product PC
where
Parent_code in (select
Parent_code
from
Product
where
SKU =12345)
and ParentFlag <> 'p'
and SKU <> 12345
order by Parent_Child_Priority desc
I'd like to put each one of the resulting SKU in each #SIB variables. if it only returns 1 result, I'd like to put null values into the rest of the #SIB variables.
Thanks.
You could insert the SKU's into a table variable, with an identity column. Then set the variables equal to the sku in the table based on the identity columns value.
DECLARE #Sib1 VARCHAR(20)
,#Sib2 VARCHAR(20)
,#Sib3 VARCHAR(20)
,#Sib4 VARCHAR(20)
,#Sib5 VARCHAR(20)
,#Sib6 VARCHAR(20);
DECLARE #TempTbl TABLE (
RowID INT IDENTITY
,SKU VARCHAR(20)
)
INSERT INTO #TempTbl (SKU)
select
PC.SKU
from
Product PC
where
Parent_code in (select
Parent_code
from
Product
where
SKU =12345)
and ParentFlag <> 'p'
and SKU <> 12345
order by Parent_Child_Priority desc
SELECT #Sib1 = SKU
FROM #TempTbl
WHERE RowID = 1;
SELECT #Sib2 = SKU
FROM #TempTbl
WHERE RowID = 2;
SELECT #Sib3 = SKU
FROM #TempTbl
WHERE RowID = 3;
SELECT #Sib4 = SKU
FROM #TempTbl
WHERE RowID = 4;
SELECT #Sib5 = SKU
FROM #TempTbl
WHERE RowID = 5;
SELECT #Sib6 = SKU
FROM #TempTbl
WHERE RowID = 6;
EDIT
DECLARE #SQL VARCHAR(MAX);
SET #SQL = 'SELECT SKU, ..., sum(convert(INT, a.qty)) AS ' + #sib1 + ' FROM ...'
EXEC (#SQL);
Rather use a table variable like
DECLARE #MyTableVar table(
SKU int NOT NULL);
Then insert into it
insert into #MyTableVar(SKU)
select
PC.SKU
from
Product PC
where
Parent_code in (select
Parent_code
from
Product
where
SKU =12345)
and ParentFlag <> 'p'
and SKU <> 12345
order by Parent_Child_Priority desc;
Now you can use that #MyTableVar as you need. You don't need to declare N variable for N records.

How to Sum value of Pivoted Columns and add it into another Pivoted Column

I want to sum up all the CountHours and display in pivoted column Total.Total is the sum of all SUnday , monday ... for particular UserName. How to achieve this
?
select FullName,Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Total
from
(Select UserId_Fk,ISNULL(CAST(CountHours as decimal(18,2)),0)as CountHours,[Day] f rom CheckInCheckOut)
as convertedtable
inner join Users
on convertedtable.UserId_Fk=Users.UserId
PIVOT
(
SUM(CountHours)
FOR Day
IN([Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday],[Total])
)
as PivotTable
Result of this query is:
Table Structure:
Table[CheckInCheckOut]
CheckInCheckOutId int
UserId_Fk int
CountHours nvarchar(50)
Day nvarchar(50)
Example would be appreciated.
you should calculate total column field, i.e it is not in list of pivot columns.
Data
create table #CheckInCheckOut(Id int identity(1,1),UserId_Fk int,CountHours varchar(50),[Day] varchar(50))
INSERT INTO #CheckInCheckOut(UserId_Fk,CountHours,[Day]) VALUES
(1,'2','Sunday'),(1,'2','Monday'),(1,'2','Tuesday'),(1,'2','Wednesday'),(1,'2','Thursday'),(1,'2','Friday'),(1,'2','Saturday')
,(2,'3','Sunday'),(2,'3','Monday'),(2,'3','Tuesday'),(2,'3','Wednesday'),(2,'3','Thursday'),(2,'3','Friday'),(2,'3','Saturday')
,(3,'3','Sunday'),(3,'3','Monday'),(3,'3','Tuesday'),(3,'3','Wednesday'),(3,'3','Thursday'),(3,'3','Friday'),(3,'3','Saturday')
create table #Users(UserId int identity(1,1),FullName varchar(50))
INSERT #Users(FullName) values('Abdul'),('khan'),('Tariq')
Query to find total too:
select FullName
,[Sunday] = SUM([Sunday])
,[Monday] = SUM([Monday])
,[Tuesday] = SUM([Tuesday])
,[Wednesday] = SUM([Wednesday])
,[Thursday] = SUM([Thursday])
,[Friday] = SUM([Friday])
,[Saturday] = SUM([Saturday])
, Total= SUM([Sunday]+[Monday]+[Tuesday]+[Wednesday]+[Thursday]+[Friday]+[Saturday])
from
(Select UserId_Fk,ISNULL(CAST(CountHours as decimal(18,2)),0)as CountHours,[Day]
from #CheckInCheckOut)
as convertedtable
inner join #Users
on convertedtable.UserId_Fk=#Users.UserId
PIVOT
(
SUM(CountHours)
FOR Day
IN([Sunday],[Monday],[Tuesday],[Wednesday],[Thursday],[Friday],[Saturday])
)
as PivotTable
GROUP BY FullName
Output
Also if u want total horizontal and vertical both then replace:
--GROUP BY FullName
GROUP BY ROLLUP(FullName);
For more follow link https://stackoverflow.com/a/17142530/1915855
DROP TABLE #CheckInCheckOut
DROP TABLE #Users
Try This Way here is the example.
Create a Table
CREATE TABLE cars
(
car_id tinyint,
attribute varchar(20),
value varchar(20),
sumd decimal(18,2)
)
Insert Values to it
insert into cars(car_id, attribute, value, sumd)
values (1, 'Make', 'VW',1),
(1, 'Model', 'Rabbit',2),
(1, 'Color', 'Gold',3),
(2, 'Make', 'Jeep',4),
(2, 'Model', 'Wrangler',5),
(2, 'Color', 'Gray',6)
For Making the Total
declare #Columns2 VARCHAR(8000)
declare #Sql VARCHAR(4000)
declare #Columns VARCHAR(8000)
SET #Columns = substring((select distinct ',['+attribute+']' from cars group by attribute for xml path('')),2,8000)
SET #Columns2 = substring((select distinct ',IsNull(['+attribute+'],0) as ['+attribute+']' from cars group by attribute for xml path('')),2,8000)
print #Columns
print #Columns2
SET #SQL = 'SELECT car_id, '+#Columns2+', total
FROM
(Select car_id,attribute, SUM(sumd) OVER (PARTITION BY attribute) as total
, sumd from cars) SourceData
PIVOT
(sum(sumd) for attribute in ('+#Columns+')) pivottable
Order by car_id '
exec(#sql)

Use a table-value function to return data in columns instead of rows

I'm trying to write a query which will take a limited number of historical records and display the results in one row.
For example, I have a table of people:
|PersonID|Forename|Surname
|--------|--------|----------
|00000001|Andy |Cairns
|00000002|John |Smith
And a table of all their historical addresses:
|PersonID|Date |Street |Town
-------------------------------------------
|00000001|2011-01-01|Main Street |MyTown
|00000001|2010-01-01|Old Street |OldTown
|00000002|2010-01-01|Diagon Alley |London
|00000001|2009-01-01|First Street |OtherTown
etc..
I'd like to return the following:
|PersonID|Name |MoveDate1 |Town1 |MoveDate2 |Town2 |MoveDate3 |Town3
------------------------------------------------------------------------
|00000001|Andy |2011-01-01|MyTown|2010-01-01|OldTown|2009-01-01|OtherTown
|00000002|John |2010-01-01|London| | | |
At the moment, I'm using the following query:
select PersonID, Name, s.mdate, s.town
from dbo.people
cross apply dbo.getAddressList as s
And the following table-value function:
alter function [dbo].[getAddressList]
(
#personID
)
returns
#addresslisttable
(
mdate smalldatetime
town char
)
as
begin
insert into #addresslist (
mdate
town
)
select top 3 mdate, town
from dbo.addresses
where PersonID = #personID
order by mdate desc
return
end
Unfortunately, this is returning a new row for each address, like this:
|PersonID|Name|MDate |Town
|00000001|Andy|2011-01-01|MyTown
|00000001|Andy|2010-01-01|OldTown
|00000001|Andy|2009-01-01|OtherTown
How can I return each returned row in a field instead?
Thanks in advance.
Where possible you should always use inline TVFs in preference to multistatement ones.
ALTER FUNCTION [dbo].[getAddressList]
(
#personID INT
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(SELECT TOP 3 mdate, town, ROW_NUMBER() OVER (ORDER BY mdate DESC) rn
FROM dbo.addresses
WHERE PersonID = #personID
ORDER BY mdate DESC
)
SELECT
MAX(CASE WHEN rn=1 THEN mdate END) AS MoveDate1,
MAX(CASE WHEN rn=1 THEN town END) AS Town1,
MAX(CASE WHEN rn=2 THEN mdate END) AS MoveDate2,
MAX(CASE WHEN rn=2 THEN town END) AS Town2,
MAX(CASE WHEN rn=3 THEN mdate END) AS MoveDate3,
MAX(CASE WHEN rn=3 THEN town END) AS Town3
FROM cte
)
I'd also investigate the relative performance of not using the TVF at all. And doing a JOIN, ROW_NUMBER() OVER (PARTITION BY PersonID) and the PIVOT technique above.
Here, check it out:
-- Create People (not like that... jeez...)
CREATE TABLE #People (PersonID INT, Forename VARCHAR(25), Surname VARCHAR(25))
INSERT INTO #People VALUES (1, 'Andy', 'Cairns')
INSERT INTO #People VALUES (2, 'John', 'Smith')
-- Create historical addresses
CREATE TABLE #Addy (PersonID INT, AddyDate DATETIME, Street VARCHAR(50), Town VARCHAR(50))
INSERT INTO #Addy VALUES (1, '2011-01-01', 'Main Street', 'MyTown')
INSERT INTO #Addy VALUES (1, '2010-01-01', 'Old Street', 'OldTown')
INSERT INTO #Addy VALUES (2, '2010-01-01', 'Diagon Alley', 'London')
INSERT INTO #Addy VALUES (1, '2009-01-01', 'First Street', 'OtherTown')
-- Create ranked addresses mapped to people
SELECT p.Forename, p.Surname, a.*,
ROW_NUMBER() OVER (PARTITION BY p.PersonID ORDER BY p.PersonID) As Ordinal
INTO #Ranked
FROM #People p INNER JOIN #Addy a ON p.PersonID = a.PersonID
-- Make sure everything is kosher
SELECT * FROM #People
SELECT * FROM #Addy
SELECT * FROM #Ranked
-- Create a container for "final" results
DECLARE #Results TABLE (PersonID INT, Forename VARCHAR(25)
, MoveDate1 DATETIME, Street1 VARCHAR(50), Town1 VARCHAR(50)
, MoveDate2 DATETIME, Street2 VARCHAR(50), Town2 VARCHAR(50)
, MoveDate3 DATETIME, Street3 VARCHAR(50), Town3 VARCHAR(50))
-- Get our people primed in the results table
INSERT INTO #Results (PersonID, Forename) SELECT PersonID, Forename FROM #People
-- Fill it up
UPDATE #Results SET MoveDate1 = AddyDate, Street1 = Street, Town1 = Town FROM #Ranked INNER JOIN #Results r ON #RAnked.PersonID = r.PersonID WHERE Ordinal = 1
UPDATE #Results SET MoveDate2 = AddyDate, Street2 = Street, Town2 = Town FROM #Ranked INNER JOIN #Results r ON #RAnked.PersonID = r.PersonID WHERE Ordinal = 2
UPDATE #Results SET MoveDate3 = AddyDate, Street3 = Street, Town3 = Town FROM #Ranked INNER JOIN #Results r ON #RAnked.PersonID = r.PersonID WHERE Ordinal = 3
-- Winsauce?
SELECT * FROM #Results
-- Cleanup
DROP TABLE #People
DROP TABLE #Addy
DROP TABLE #Ranked

Select row data as ColumnName and Value

I have a history table and I need to select the values from this table in ColumnName, ColumnValue form. I am using SQL Server 2008 and I wasn’t sure if I could use the PIVOT function to accomplish this. Below is a simplified example of what I need to accomplish:
This is what I have:
The table’s schema is
CREATE TABLE TABLE1 (ID INT PRIMARY KEY, NAME VARCHAR(50))
The “history” table’s schema is
CREATE TABLE TABLE1_HISTORY(
ID INT,
NAME VARCHAR(50),
TYPE VARCHAR(50),
TRANSACTION_ID VARCHAR(50))
Here is the data from TABLE1_HISTORY
ID NAME TYPE TRANSACTION_ID
1 Joe INSERT a
1 Bill UPDATE b
1 Bill DELETE c
I need to extract the data from TABLE1_HISTORY into this format:
TransactionId Type ColumnName ColumnValue
a INSERT ID 1
a INSERT NAME Joe
b UPDATE ID 1
b UPDATE NAME Bill
c DELETE ID 1
c DELETE NAME Bill
Other than upgrading to Enterprise Edition and leveraging the built in change tracking functionality, what is your suggestion for accomplishing this task?
You could try using a UNION
Test Data
DECLARE #TABLE1_HISTORY TABLE (
ID INT,
NAME VARCHAR(50),
TYPE VARCHAR(50),
TRANSACTION_ID VARCHAR(50))
INSERT INTO #TABLE1_HISTORY
SELECT 1, 'Joe', 'INSERT', 'a'
UNION ALL SELECT 1, 'Bill', 'UPDATE', 'b'
UNION ALL SELECT 1, 'Bill', 'DELETE', 'c'
SQL Statement
SELECT [TransactionID] = Transaction_ID
, [Type] = [Type]
, [ColumnName] = 'ID'
, [ColumnValue] = CAST(ID AS VARCHAR(50))
FROM #Table1_History
UNION ALL
SELECT [TransactionID] = Transaction_ID
, [Type] = [Type]
, [ColumnName] = 'NAME'
, [ColumnValue] = [Name]
FROM #Table1_History
ORDER BY TransactionID
, ColumnName
This can be done with the UNPIVOT function in SQL Server:
select transaction_id,
type,
ColumnName,
ColumnValue
from
(
select transaction_id,
type,
cast(id as varchar(50)) id,
name
from TABLE1_HISTORY
) src
unpivot
(
ColumnValue
for ColumnName in (ID, Name)
) un

Resources