re - arrange table - sql

I have a table like this:
ID Country InternetTLD CallingCode
1 Nicaragua .ni +505
2 USA .us +1
3 Spain .es +34
4 Germany .de +49
and I need a result like this
1 2 3 4
Nicaragua USA Spain Germany
.ni .us .es .de
+505 +1 +34 +49
I have tried with pivot but I just get to convert one column row, but in this case for every row in the first table it should be a column in the resulting table.
this is my code:
Create table #SampleTable (
ID int,
Country nvarchar(50),
InternetTLD nvarchar(50),
CallingCode nvarchar(50)
);
insert into #SampleTable (ID, Country, InternetTLD, CallingCode)
values
(1, 'Nicaragua', '.ni', '+505'),
(2, 'USA', '.us', '+505'),
(3, 'Spain', '.es', '+34'),
(4, 'Germany', '.de', '+49')
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
declare #PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(ID)
FROM (SELECT ID FROM #SampleTable ) AS ID
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames
= ISNULL(#PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(ID) + ', 0) AS '
+ QUOTENAME(ID)
FROM (SELECT ID FROM #SampleTable ) AS ID
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT ' + #PivotSelectColumnNames + '
FROM #SampleTable
PIVOT(MAX(Country)
FOR ID IN (' + #ColumnName + ')) AS PVTTable '
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
DROP TABLE #SampleTable

You need pivot & unpivot both so, i would do it with conditional aggregation & apply:
select max(case when id = 1 then val end) as [1],
max(case when id = 2 then val end) as [2],
max(case when id = 3 then val end) as [3],
max(case when id = 4 then val end) as [4]
from table t cross apply
( values ('Country', Country, 1), ('InternetTLD', InternetTLD, 2), ('CallingCode', CallingCode, 3)
) tt(col, val)
group by col, seq
order by seq;

Related

Combination of dynamic pivot and static pivot in SQL Server

Dynamic pivot combined combined with static aggregates
I have a table that looks something like this:
Place State Category CategCount MCount Buys Cost
London UK Old 3 NULL 22 4.50
London UK Old 6 5 3 22.00
Brussels BE Young 2 NULL 4 3.50
Brussels BE M NULL 5 12 1.20
Brussels BE M NULL 2 1 1.20
I basically need to:
Group by a number of fields (Place, State, Category in the example)
Count per such group
Sum MCount, Cost (and others, not in example) per group, these columns are static
Pivot over Category and sum CategCount for each such grouped category (here Old, Young). This is the dynamic part
Result should look like:
Count Place State Category SumMCount SumOld SumYoung SumCost SumBuys
2 London UK Old 5 9 0 26.50 25
1 Brussels BE Young 0 0 2 3.50 4
2 Brussels BE NULL 7 0 0 2.40 13
I know how to get a dynamic pivot query (as per https://stackoverflow.com/a/38505375/111575) and I know how to do the static part. But I don't know how to combine the two. Anybody any ideas? Maybe I go about it all wrong?
What I've got so far:
The following gives me the proper dynamic pivot results for Old and Young, but not sure how to add the count and the the 'regular' sums/aggregates to it:
create table #temp
(
Place nvarchar(20),
State nvarchar(20),
Category nvarchar(20) null,
CategCount int null,
MCount int null,
Buys int,
Cost int
)
insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
--select (#cols) as bm
set #query =
'SELECT * from
(
select
sum(CategCount) as totalCatCount,
Category
from #temp
group by Category
) src
pivot
(
max(totalCatCount) for Category in (' + #cols + ')
) piv'
execute(#query)
drop table #temp
Returning:
And the following is the 'regular' query without the pivoting:
select count(*) as count, place, state, category,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(buys, 0)) as SumBuys,
sum(Cost) as SumCost
from #temp
group by place, state, category
Returning:
But it should look something like this:
I have used your static pivot part of the query as the source of dynamic pivot. Create two sets of dynamic pivot column list. One for pivoting and the another with Coalesce() to select pivoted columns (to convert null into 0). If there is no categcount for any category then that category has been replaced with null (case when). Two more aliases for Category and SumCatCount have been created since those were used in pivot condition.
Here goes your answer:
create table #temp
(
Place nvarchar(20),
State nvarchar(20),
Category nvarchar(20) null,
CategCount int null,
MCount int null,
Buys int,
Cost int
)
insert into #temp values ('London', 'UK', 'Old', 3, NULL, 22, 4.50)
insert into #temp values ('London', 'UK', 'Old', 6, 5, 3, 22.00)
insert into #temp values ('Brussels', 'BE', 'Young', 2, NULL, 4, 3.50)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 5, 12, 1.20)
insert into #temp values ('Brussels', 'BE', 'M', NULL, 2, 1, 1.20)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
DECLARE #colsForSelect AS NVARCHAR(MAX)='';
SET #cols = STUFF((SELECT distinct ',' + quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #colsForSelect = STUFF((SELECT distinct ',' + ' Coalesce('+quotename(category)+',0) '+ quotename(category)
FROM #temp where CategCount is not null
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
--select (#cols) as bm
set #query =
'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + #colsForSelect + ',SumCost,SumBuys from
(
select count(*) as count, place, state,category OldCategory, category,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(CategCount, 0)) as OldSumCatCount,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(Cost) as SumCost,
sum(ISNULL(buys, 0)) as SumBuys
from #temp
group by place , state, category
) src
pivot
(
max(SumCatCount) for Category in (' + #cols + ')
) piv
order by place desc,count'
execute(#query)
GO
count
place
state
Category
SumMCount
Old
Young
SumCost
SumBuys
2
London
UK
Old
5
9
0
26
25
1
Brussels
BE
Young
0
0
2
3
4
2
Brussels
BE
null
7
0
0
2
13
db<>fiddle here
Thanks to #Larnu in the comments for pointing me in the right direction. His/her statement on "you cannot JOIN a static to a dynamic query" and that either all or nothing has to be dynamic, prompted me to build onto the dynamic part and simply extend it.
I thought I needed to repeat the columns somehow in the PIVOT section, but that appears to not be the case. Only the column you want to pivot, apparently (logically so, once you think about it).
The only part I haven't figured out yet is how to get rid of NULL in the resulting set, hopefully someone answers with that in mind ;).
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(Category) + ',' FROM (select distinct Category from #temp where CategCount IS NOT NULL) as tmp
select #cols = substring(#cols, 0, len(#cols)) --trim "," at end
--select (#cols) as bm
set #query =
'SELECT * from
(
select
count(*) as count,
Place,
State,
Category,
Category as CatPivot,
sum(ISNULL(CategCount, 0)) as TotalCatCount,
sum(ISNULL(Buys, 0)) as SumBuys,
sum(ISNULL(Cost, 0)) as SumCost,
sum(ISNULL(MCount, 0)) as SumMCount
from #temp
group by Category, Place, State
) src
pivot
(
max(TotalCatCount) for CatPivot in (' + #cols + ')
) piv'
execute(#query)
Here I am sharing another answer which is same but as suggested by #Anthony Hancock dynamic column names for pivot have been created with string_agg() instead of stuff() and xml path for(). It's way too faster and more readable (for SQL Server 2017 and onward)
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
DECLARE #colsForSelect AS NVARCHAR(MAX)='';
select #cols =string_agg(category,',') from (
select distinct category FROM #temp where CategCount is not null )t
select #colsForSelect= STRING_AGG(category,',') from
(select distinct 'coalesce('+category+',0) '+category category FROM #temp where CategCount is not null )t
set #query =
'SELECT count,place,state,(case when OldSumCatCount >0 then OldCategory else null end)Category,SumMCount, ' + #colsForSelect + ',SumCost,SumBuys from
(
select count(*) as count, place, state,category OldCategory, category,
sum(ISNULL(MCount, 0)) as SumMCount,
sum(ISNULL(CategCount, 0)) as OldSumCatCount,
sum(ISNULL(CategCount, 0)) as SumCatCount,
sum(Cost) as SumCost,
sum(ISNULL(buys, 0)) as SumBuys
from #temp
group by place , state, category
) src
pivot
(
max(SumCatCount) for Category in (' + #cols + ')
) piv
order by place desc,count'
execute(#query)

SQL Server pivot query - questions

I have a table with 3 columns: order_id, product_id, product_count
The first column is an order passed by a client, the second is the product unique id and the third is the quantity of a product bought in an order.
I want to create a matrix of order_id / product_id with number of items bought.
As a result I would like to have something that looks like this:
If I make this request:
SELECT *
FROM
(SELECT
[order_id], [prod_id], [product_count]
FROM mydb.dbo.mytable) QueryResults
PIVOT
(SUM([product_count])
FOR [prod_id] IN ([21], [22], [23])
) AS PivotTable
My issue is that I have more than 200 different products to retrieve. Is there a way to make it without entering all values?
I'd written this and was testing when BICube posted his comment - and yes, this is another dynamic Pivot. You had the basic code - all you need to do is to
Build a variable with the column name list e.g., ColList = '[21],[22],[23]'
Use this variable in the PIVOT to provide the column list - but note you then need to make the whole statement into Dynamic SQL.
Here is the answer I wrote (Note I just made up order data rather than transcribing from your image).
CREATE TABLE #MyTable (Order_ID int, Prod_ID int, Product_Count int);
INSERT INTO #MyTable (Order_ID, Prod_ID, Product_Count)
VALUES
(100, 1, 15),
(100, 2, 12),
(100, 5, 17),
(101, 3, 10),
(101, 4, 11),
(102, 6, 12),
(102, 1, 16);
SELECT * FROM #MyTable;
DECLARE #ColList nvarchar(max) = N''
SELECT #ColList += N',' + QUOTENAME(LTRIM(STR(Prod_ID)))
FROM (SELECT DISTINCT Prod_ID FROM #MyTable) A;
SET #ColList = STUFF(#ColList,1,1,''); -- Remove leading comma
DECLARE #PivotSQL nvarchar(max);
SET #PivotSQL =
N'SELECT * FROM (
SELECT
[Order_ID],
[prod_id],
[product_count]
FROM #MyTable
) QueryResults
PIVOT (
SUM([product_count])
FOR [prod_id]
IN (' + #ColList + N')
) AS PivotTable;'
EXEC (#PivotSQL);
And here are the results
Order_ID 1 2 3 4 5 6
100 15 12 NULL NULL 17 NULL
101 NULL NULL 10 11 NULL NULL
102 16 NULL NULL NULL NULL 12
Based on #seanb answer that saved me, I tried to replace the NULL values with 0. I understood the principle (the base). Here is how I updated the SQL request to replace the NULL values.
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames= ISNULL(#PivotColumnNames + ',','') + QUOTENAME(prod_id)
FROM (SELECT DISTINCT prod_id FROM #MyTable) AS prod_id
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames
= ISNULL(#PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(prod_id) + ', 0) AS '
+ QUOTENAME(prod_id)
FROM (SELECT DISTINCT prod_id FROM #MyTable) AS prod_id
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT order_id, ' + #PivotSelectColumnNames + '
FROM #MyTable
PIVOT(SUM(product_count)
FOR prod_id IN (' + #PivotColumnNames + ')) AS PivotTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery

Multiple Rows into One Row multiple columns

We need to list all numbers as a flat data set, how can we do that?
Table Name: Telephone
ID TYPE NUMBER
==================================
123 MN 042153939
123 HN 2242116
123 MN 1234567890
123 HN 12345678
Create Table Telephone
(
ID Integer,
Type char(3),
Number Varchar(20)
);
insert into Telephone values
(123, 'MN', '042153939'),
(123, 'HN', '2242116'),
(123, 'MN', '1234567890'),
(123, 'HN', '12345678');
I want SQL to return data in this format
ID MN#1 Mn#2 HN#1 HN#2
================================================
123 042153939 1234567890 2242116 12345678
Dynamic approach
Init
DROP TABLE IF EXISTS #Telephone;
CREATE TABLE #Telephone(ID INT,Type CHAR(3),Number VARCHAR(20));
INSERT INTO #Telephone (ID,Type,Number) VALUES
(123, 'MN', '042153939'),
(123, 'HN', '2242116'),
(123, 'MN', '1234567890'),
(123, 'HN', '12345678');
The code
DECLARE #ColumnList NVARCHAR(MAX);
SELECT #ColumnList = STUFF((SELECT ',[' + RTRIM(t.[Type]) + '#'
+ CONVERT(NVARCHAR(255),ROW_NUMBER()OVER(PARTITION BY t.[Type] ORDER BY t.ID)) + ']'
FROM #Telephone t FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'')
;
DECLARE #sql NVARCHAR(MAX) = '';
SET #sql = N'
SELECT ID,' + #ColumnList + N'
FROM (
SELECT t.ID,t.Number, RTRIM(t.[Type]) + ''#'' + CONVERT(NVARCHAR(255),ROW_NUMBER()OVER(PARTITION BY t.[Type] ORDER BY t.ID)) AS [Type]
FROM #Telephone t
) a
PIVOT(MAX(a.Number) FOR a.Type IN (' + #ColumnList + N')) p
'
;
--PRINT #sql
IF #sql IS NOT NULL EXEC(#sql);
try pivoting like below :
SELECT first_column AS <first_column_alias>,
[pivot_value1], [pivot_value2], ... [pivot_value_n]
FROM
(<source_table>) AS <source_table_alias>
PIVOT
(
aggregate_function(<aggregate_column>)
FOR <pivot_column> IN ([pivot_value1], [pivot_value2], ... [pivot_value_n])
) AS <pivot_table_alias>;
We can try using a pivot query with the help of ROW_NUMBER():
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY TYPE DESC, NUMBER) rn
FROM Telephone
)
SELECT
ID,
MAX(CASE WHEN rn = 1 THEN NUMBER END) AS [MN#1],
MAX(CASE WHEN rn = 2 THEN NUMBER END) AS [MN#2],
MAX(CASE WHEN rn = 3 THEN NUMBER END) AS [HN#3],
MAX(CASE WHEN rn = 4 THEN NUMBER END) AS [HN#4]
FROM cte
GROUP BY ID;
You may try this. with row_number() and pivot.
For more info about pivot you may find this link PIVOT.
; with cte as (
select row_number() over (partition by type order by id ) as Slno, * from Telephone
)
, ct as (
select id, type + '#' + cast(slno as varchar(5)) as Type, values from cte
)
select * from (
select * from ct
) as d
pivot
( max(values) for type in ( [MN#1],[Mn#2],[HN#1],[HN#2] )
) as p

Merging rows to columns

I have the following situation (heavily abstracted, please ignore bad design):
CREATE TABLE dbo.PersonTest (Id INT, name VARCHAR(255))
INSERT INTO dbo.PersonTest
(Id, name )
VALUES (1, 'Pete')
, (1, 'Marie')
, (2, 'Sam')
, (2, 'Daisy')
I am looking for the following result:
Id Name1 Name2
1 Marie Pete
2 Daisy Sam
So, for each Id, the rows should be merged.
Getting this result I used the following query:
WITH PersonRN AS
(
SELECT *
, ROW_NUMBER() OVER(PARTITION BY Id ORDER BY name) RN
FROM dbo.PersonTest
)
SELECT PT1.Id
, PT1.name Name1
, PT2.name Name2
FROM PersonRN AS PT1
LEFT JOIN PersonRN AS PT2 -- Left join in case there's only 1 name
ON PT2.Id = PT1.Id
AND PT2.RN = 2
WHERE PT1.RN = 1
Which works perfectly fine.
My question is: Is this the best way (best in terms of performance and resilience)? If, for example, one of these Id's has a third name, this third name is ignored by my query. I'm thinking the best way to deal with that would be dynamic SQL, which would be fine, but if it can be done without dynamic, I would prefer that.
Aside from dynamic PIVOT, you can do this using Dynamic Crosstab, which I prefer for readability.
SQL Fiddle
DECLARE #sql1 VARCHAR(1000) = '',
#sql2 VARCHAR(1000) = '',
#sql3 VARCHAR(1000) = ''
DECLARE #max INT
SELECT TOP 1 #max = COUNT(*) FROM PersonTest GROUP BY ID ORDER BY COUNT(*) DESC
SELECT #sql1 =
'SELECT
ID' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN RN =' + CONVERT(VARCHAR(5), RN)
+ ' THEN name END) AS ' + QUOTENAME('Name' + CONVERT(VARCHAR(5), RN)) + CHAR(10)
FROM(
SELECT TOP(#max)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RN
FROM sys.columns
)t
ORDER BY RN
SELECT #sql3 =
'FROM(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY name)
FROM PersonTest
)t
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)

How can I create a month name as a column name for a given date range in sql?

I have a data as below:
Table
country date value
------------------------------------------------------
test1 5/1/2008 500
test1 5/7/2008 200
test1 5/8/2008 300
test1 7/1/2008 100
test1 7/2/2008 100
test2 6/1/2008 100
And I want a result as below:
Result
-----------
countryName May-08 Jun-08 July-08
test1 1000 - 200
test2 - 100
This is adapted from T-SQL Pivot? Possibility of creating table columns from row values
You can see it working here: http://sqlfiddle.com/#!3/7b8c0/28
I think you might need to fiddle around with the column ordering
-- Static PIVOT
SELECT *
FROM (SELECT country,
CONVERT(char(3), date, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS date,
value
FROM country) AS D
PIVOT(SUM(value) FOR date IN([May-08],[Jun-08],[Jul-08])) AS P;
GO
-- Dynamic PIVOT
DECLARE #T AS TABLE(y INT NOT NULL PRIMARY KEY);
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX)
SELECT #cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT DISTINCT CONVERT(char(3), date, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS y
FROM Country
) AS Y
ORDER BY y desc
FOR XML PATH('')),
1, 1, N'')
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT country, CONVERT(char(3), date, 0) + ''-'' +
RIGHT(CONVERT(varchar, YEAR(date)), 2) AS date, value
FROM Country) AS D
PIVOT(SUM(value) FOR date IN(' + #cols + N')) AS P;'
EXEC sp_executesql #sql
You have to use a rather complex query for that, using a LOOP it think.
For creating dynamic column names look at this post: https://stackoverflow.com/a/10926106/1321564
With sql server you have some advantages: https://stackoverflow.com/a/5638042/1321564