Pivot Rows to Column By Group in SQL Server - sql-server

I am looking for a code which can transpose row to column using the group by in the SQL Server 2008.
This is my table :
Name | Stdy | Val
-------+---------+-------
Kunjan | Technic | 80
Kunjan | Sains | 90
Kunjan | Sport | 60
Shone | Technic | 60
Shone | Sains | 80
Shone | Sport | 70
Peudd | Technic | 85
Peudd | Sains | 75
Peudd | Sport | 90
What I want to eventually display is something like this (for the data above):
Stdy | Kunjan | Shone | Peudd
--------+--------+-------+-------
Technic | 80 | 60 | 85
Sains | 90 | 80 | 75
Sport | 60 | 70 | 90
Any help would be appreciated.
Thanks in advance.

this can be done using PIVOT operator
select *
from yourtable
pivot
(
max(Val)
for Name in ([Kunjan], [Shone], [Peudd])
) p
or conditional CASE statement. There are lots of example, just do a search on it
EDIT : for dynamic case
declare #sql nvarchar(max),
#col nvarchar(max)
select #col = isnull(#col + ',', '') + Name
from yourtable
group by Name
print #col
select #sql =
'
select *
from youtable
pivot
(
max(Val)
for Name in (' + #col + ')
) p
'
print #sql
exec sp_executesql #sql

Related

Pivoting rows to columns depending on data from another table

I have two tables VisitorsPerDay and Languages as follows:
Languages table
| Code | Alias |
|---------------------|------------------|
| EN | English |
| AR | Arabic |
| FR | French |
| JP | Japanese |
VisitorsPerDay table
| Date | VisitorLanguage | Count |
|---------------------|------------------|-------------|
| 10/1/2019 | EN | 20 |
| 10/1/2019 | EN | 10 |
| 10/1/2019 | AR | 5 |
| 15/1/2019 | FR | 1 |
What the result should be is aggregated data for each day and two columns for each language in the languages table dynamically in which if a new language has been added there will be no need to edit the stored procedure
| Date | TotalVisits | En Visits | En AVG Visit % |
|---------------------|------------------|-------------|------------------|
| 10/1/2019 | 35 | 30 | 85% |
| 15/1/2019 | 1 | 0 | 0% |
What I have done is created a dynamic query and a cursor that loop over the languages and generate the require SQL statements for each language and append it to the dynamic query
What I want to know is there a better way to get the result set or is a dynamic query OK?
You have to use Dynamic SQL, the query will be ugly and not easy to maintain
declare #sql nvarchar(max)
select #sql = isnull(#sql + ',', 'SELECT [Date], TotalVisits = sum([Count]),' + char(13))
+ 'SUM(CASE WHEN VisitorLanguage = ''' + Code + ''' THEN [Count] END) AS [' + Code + ' Visits],'+ char(13)
+ 'SUM(CASE WHEN VisitorLanguage = ''' + Code + ''' THEN [Count] END) * 100 / SUM([Count]) AS [' + Code + ' AVG Visits %]'+ char(13)
from Languages
select #sql = #sql + 'FROM VisitorsPerDay GROUP BY [Date]'
-- print out the dynmamic query
print #sql
exec sp_executesql #sql

SQL Server with non-alphabetic value as column name

I am building a pivot query inside a CTE. I have a table Table_1:
Store Count xCount
------- ---- ------
101 1 138
109 1 59
101 2 282
109 2 97
105 3 60
109 3 87
105 4.a 60
109 4.b 87
In Table_1, datatype of column count is varchar(10).
I used dynamic pivot query to pivot Table_1
DECLARE #DynamicCol AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX)
select #DynamicCol = STUFF((SELECT distinct ',' + QUOTENAME(count)
from table_1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #SQL = ';WITH CTE as (
SELECT store,' + #DynamicCol + ' from
(
select * from table_1
) res
pivot
(
MAX(xCount)
for Count in (' + #DynamicCol + ')
) piv ) SELECT *
FROM CTE where 4.a is null'
execute(#SQL);
and get result as :
| STORE | 1 | 2 | 3 | 4.a |
+-------+-----+-----+-----+-----+
| 101 | 138 | 282 | null| null|
| 105 | null| null| 60 | 60 |
| 109 | 59 | 97 | 87 | 87 |
Now, I tried to get data from column 3 and 4.a where 3 and 4.a is null.
The query I build to get data is
Select * from CTE where 3 is null
Select * from CTE where 4.a is null
Also i tried to use this inside case statement as :
Select *,case when (3 is null) then 'some result' else '' end from CTE
In every query I am not getting any value returned by queries.
I tried by to append 'X' in each pivoted column and remove '.' from column anme, like column name looks like
| STORE | X1 | X2 | X3 | X4a |
+-------+-----+-----+-----+-----+
| 101 | 138 | 282 | null| null|
| 105 | null| null| 60 | 60 |
| 109 | 59 | 97 | 87 | 87 |
I am not able to query for this. Could anyone help me or suggest me any other idea to get data using above mentioned query ?
You have to wrap identifier that starts with digit with []:
DECLARE #DynamicCol AS NVARCHAR(MAX),
#SQL AS NVARCHAR(MAX)
select #DynamicCol = STUFF((SELECT distinct ',' + QUOTENAME(count)
from table_1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #SQL = ';WITH CTE as (
SELECT store,' + #DynamicCol + ' from
(
select * from table_1
) res
pivot
(
MAX(xCount)
for Count in (' + #DynamicCol + ')
) piv ) SELECT *
FROM CTE where [4.a] is null' -- here
execute(#SQL);

Convert Access TRANSFORM/PIVOT query to SQL Server with multiple table [duplicate]

TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |

How to use subquery in openquery where clause

So I have this Openquery in a stored procedure, where I need to return results where the values in a column are the same as the ones in a local table
exec spx_SELECT_LocalizacoesEtiquetas
GO
IF OBJECT_ID('dbo.spx_SELECT_LocalizacoesEtiquetas') IS NOT NULL
DROP PROCEDURE spx_SELECT_LocalizacoesEtiquetas
GO
CREATE PROCEDURE spx_SELECT_LocalizacoesEtiquetas
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL NVARCHAR(MAX);
SET #SQL =
'SELECT ET0109 AS Localizacao, Etiquetas
FROM OpenQuery(MACPAC, ''SELECT FET001.ET0109, FET001.ET0101 AS Etiquetas
FROM AUTO.D805DATPOR.FET001 FET001
WHERE FET001.ET0104=''''POE'''' AND FET001.ET0105=''''DIS'''''' AND FET001.ET0101 = '''''
+ (SELECT Localizacao FROM xLocalizacao WHERE InventarioID = 1 ) + ''''' ) ';
EXEC sp_executesql #SQL
END
basically it won't accept the subquery 'cause it says it has too many values.... So my question is. How can i limit the values from the subquery where the values of a column match the ones in a local table? basically a where column A in open query = column B in local table
EDIT.
Here is what I'm trying to achieve.
SubQuery returns from Local table
Column A
| A |
| B |
| C |
| D |
| E |
Open query returns
Column A Column B
| A | 0 |
| A | 0 |
| A1 | 1 |
| A | 2 |
| B | 3 |
| B | 3 |
| B1 | 4 |
Final result should Be
Final query
Column A Column B
| A | 0 |
| A | 0 |
| A | 2 |
| B | 3 |
| B | 3 |
Ok, there are two changes you need to make in your approach.
First of all, you are concatenating your sub-query to a string. No matter what, your subquery has to return a single value, not a multi-row set. So you need to use the method of your choice for having your query return a comma-separated string.
Here's one that will work on any version of SQL Server after 2005.
in other words, instead of this:
Column A
| A |
| B |
| C |
| D |
| E |
your subquery needs to return a single varchar column containing this:
'A','B','C','D','E'
The next change you need to make is using IN instead of =.
So instead of this:
AND FET001.ET0101 = '''''
+ (Your Subquery) + ''''' ) '
you need this:
AND FET001.ET0101 IN ( '
+ (Your Subquery) + ') ) '

Dynamic columns generation on the basis of rows of a table in sql

I have a table with four columns item_id, color, size, weight, I want to show my table rows into one row like item1,color1,size1,weight1,item2,color2,...........,item4,color4,size4,weight4 ...
Following is my table
+---------+--------+--------+--------+
| item_id | color | size | weight |
+---------+--------+--------+--------+
| 1 | blue | large | 65 |
| 2 | orange | large | 57 |
| 3 | red | small | 12 |
| 4 | violet | medium | 34 |
My desired result will be
+---------+--------+--------+--------++---------+--------+--------+
| item_id1| color1| size1 | weight1| item_id2 | color2 | size2 | weight2 |....
+---------+--------+--------+--------+---------+--------+--------+---------------
| 1 | blue | large| 65 | 2 | orange | large | 57 |...
+---------+--------+--------+--------+ +---------+--------+--------+--------+
Thanks in advance.
In order to get this result, you will need to do a few things:
UNPIVOT the current data
PIVOT the result from the unpivot
use dynamic SQL since you will have an unknown number of rows
Since you are using SQL Server 2005+ you can use CROSS APPLY to unpivot the data, this process takes your multiple columns of item_id, color, size and weight and converts them into multiple rows:
select col+'_'+cast(seq as varchar(50)) col,
value
from
(
select item_id as seq, item_id, color, size, weight
from yourtable
) d
cross apply
(
values
('item_id', cast(item_id as varchar(50))),
('color', color),
('size', size),
('weight', cast(weight as varchar(50)))
) c (col, value);
See SQL Fiddle with Demo. This gives a result:
| COL | VALUE |
----------------------
| item_id_1 | 1 |
| color_1 | blue |
| size_1 | large |
| weight_1 | 65 |
| item_id_2 | 2 |
| color_2 | orange |
| size_2 | large |
| weight_2 | 57 |
| item_id_3 | 3 |
As you can see from the result you now have multiple rows in based off your original data. The COL values are the values that you will use to PIVOT. The full dynamic SQL code will be similar to the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+cast(item_id as varchar(10)))
from yourtable
cross apply
(
select 'item_id', 0 union all
select 'color', 1 union all
select 'size', 2 union all
select 'weight', 3
) c (col, so)
group by item_id, col, so
order by item_id, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
select col+''_''+cast(seq as varchar(50)) col,
value
from
(
select item_id as seq, item_id, color, size, weight
from yourtable
) d
cross apply
(
values
(''item_id'', cast(item_id as varchar(50))),
(''color'', color),
(''size'', size),
(''weight'', cast(weight as varchar(50)))
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. The final result is:
| ITEM_ID_1 | COLOR_1 | SIZE_1 | WEIGHT_1 | ITEM_ID_2 | COLOR_2 | SIZE_2 | WEIGHT_2 | ITEM_ID_3 | COLOR_3 | SIZE_3 | WEIGHT_3 | ITEM_ID_4 | COLOR_4 | SIZE_4 | WEIGHT_4 |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | blue | large | 65 | 2 | orange | large | 57 | 3 | red | small | 12 | 4 | violet | medium | 34 |
If you want to do it programmatically and you don't know the number of rows try this :
DECLARE #I INT, #END INT, #DATA nvarchar(max), #TEMPSTR nvarchar(max), #DynamicTableSQL nvarchar(max)
SET #I = 1
SET #DATA = ''
SET #TEMPSTR = ''
SELECT #END = MAX(item_id) from items
SET #DynamicTableSQL = 'DECLARE #DynamicTable TABLE('
WHILE #I <= #END
BEGIN
--SELECT #I
SET #TEMPSTR = (select CAST(item_id as nvarchar) + ',''' + color + ''',''' + size + ''',' + cast(weight as nvarchar) + ',' FROM items WHERE item_id = #I)
SET #DynamicTableSQL = #DynamicTableSQL + 'item_id_' + CAST(#I AS VARCHAR(10))+ ' INT ,' + 'color_' + CAST(#I AS VARCHAR(10))+ ' NVARCHAR(15) ,'+ 'size_' + CAST(#I AS VARCHAR(10))+ ' NVARCHAR(15) ,'+ 'weight_' + CAST(#I AS VARCHAR(10))+ ' INT ,'
SET #DATA += #TEMPSTR
SELECT #I = #I + 1
END
SET #DynamicTableSQL = SUBSTRING(#DynamicTableSQL, 0, LEN(#DynamicTableSQL))
SET #DynamicTableSQL = #DynamicTableSQL + ') '
SET #DATA = SUBSTRING(#DATA, 0, LEN(#DATA))
SET #DynamicTableSQL = #DynamicTableSQL + ' INSERT INTO #DynamicTable VALUES (' + #DATA + ')'
SET #DynamicTableSQL = #DynamicTableSQL + ' SELECT * FROM #DynamicTable '
EXEC SP_EXECUTESQL #DynamicTableSQL

Resources