Table Transform with SQL - sql

Normally when I deal with SQL (rarely) I only use SELECT * FROM statement all the time. My primary job is working with Excel as an analysis. However, I feel my efficiency can be much improved with programming, I have started to learn some programming (VBA for Excel). Today I want to do something more advantage which is trying to transform a table using Microsoft SQL like attached picture below.
SELECT Part_Number as [Part Number], SubPart, Quantity FROM....
Bascially, the Part Number can be as many as 200, SubPart only has 3 type Sub-I, Sub-II, and Sub-III, Quantity can be anything. I need some help to transform the table as shown

Write dynamic T-Sql query as:
DECLARE #columns NVARCHAR(MAX)
,#sql NVARCHAR(MAX)
SET #columns = N''
--Get column names for entire pivoting
SELECT #columns += N', ' + QUOTENAME(SpreadCol)
FROM (select distinct Part_Number as SpreadCol
from test
) AS T
--select #columns
SET #sql = N'
SELECT SubPart, ' + STUFF(#columns, 1, 2, '') + '
FROM
(select SubPart , Part_Number as SpreadCol , Quantity
from test ) as D
PIVOT
(
sum(Quantity) FOR SpreadCol IN ('
+ STUFF(REPLACE(#columns, ', [', ',['), 1, 1, '')
+ ')
) AS Pivot1
'
--select #sql
EXEC sp_executesql #sql
Check Fiddle Demo here..

It seems you need to use Pivoting.
Refer to:
Using PIVOT and UNPIVOT
Simple Way To Use Pivot In SQL Query

Related

how to create dynamic table

I have a dynamic pivoted query which generates a result set and I want to insert that data into a table. But the problem is columns are dropped or generated by the time. So by the time I cannot predict columns. That is why I created a dynamic pivoted dataset. So how to insert that data set into table?
One solution is to drop and recreate the table every time but I don't know how to do it. I tried CTE, TEMP table but EXEC support only select, insert, update, delete statement:
DECLARE #columns NVARCHAR(MAX), #sqlquery NVARCHAR(MAX), #orderby Nvarchar(MAX),#value Nvarchar(max);
SET #columns = N'';
SET #value=N'0'
SELECT #columns += N', ' + QUOTENAME([Note_Type])
FROM
(
SELECT distinct
No_T
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)as A order by No_T
SET #sqlquery = N'
Select
K._Number
,D.C_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select
_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select distinct
right(REPLICATE('+#value+',11) +_Number,11) as [_Number]
,No_t
,No_T_Des
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)AS J
pivot
(
count(No_T_Des) FOR [No_t] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
)P
)K
left join
[DS_DM].[dbo].[D_TABLE] D on k._Number = D._Number
';
EXEC sp_executesql #sqlquery
I've modified your code to reflect my proposed solution.
IF OBJECT_ID (N'NEW_TABLE', N'U') IS NOT NULL
BEGIN
DROP TABLE NEW_TABLE
END
DECLARE #columns NVARCHAR(MAX), #sqlquery NVARCHAR(MAX), #orderby Nvarchar(MAX),#value Nvarchar(max);
SET #columns = N'';
SET #value=N'0'
SELECT #columns += N', ' + QUOTENAME([Note_Type])
FROM
(
SELECT distinct
No_T
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)as A order by No_T
SET #sqlquery = N'
Select
K._Number
,D.C_Number
,' + STUFF(#columns, 1, 2, '') + '
INTO NEW_TABLE
from
(
select
_Number
,' + STUFF(#columns, 1, 2, '') + '
from
(
select distinct
right(REPLICATE('+#value+',11) +_Number,11) as [_Number]
,No_t
,No_T_Des
FROM [DS_DM].[dbo].[DAILY_TABLE]
where No_T not in (570,80,150,590,80,99)
)AS J
pivot
(
count(No_T_Des) FOR [No_t] IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
)P
)K
left join
[DS_DM].[dbo].[D_TABLE] D on k._Number = D._Number
';
EXEC sp_executesql #sqlquery
So I found the answers to my questions.
there are 2 fileds which remais static every time. so I've created ETL that drop that table and recreate every single day with those two fileds. and rest of the 66 columns which are dynamic (drops or newly created) every day. so i've made a cursor for that which loop through all of that categories and altering that table that've created and added that code to ETL
So bassically Every single day that ETL Runs Drop the existing table, Create new table with the 2 static filds and altering same table using cursor with the dynamic filds.

change column names in a dynamic pivot table result

I have a tsql dynamic pivot table query that works fine although I´m not happy with the column names in the final output.
To make it work, the user has to select up to 20 fruit names from a list of 200 fruit names. Then I build the pivot table so I end up with different column names everytime the select is run.
For example:
First time the column names are: apple, orange and pear
Second time is: .orange, banana, kiwi and apple
My question is: Is there a posibility to have static names, like for example: name of the first column always "col_1", second column "col_2" etc?
The select statement is as follows:
DECLARE #idList varchar(800)
DECLARE #sql nvarchar(max)
SELECT #idList = coalesce(#idList + ', ', '') + '['+ltrim(rtrim(id_producto)) +']'
from gestor_val_pos
group by id_producto order by id_producto
SELECT #sql = 'select * from #correlaciones pivot (max (correl)
for codigo2 in (' + #IDlist + ')) AS pvt order by codigo1;'
exec sp_executeSQL #sql
sure.. just created a new variable to hold the column aliases and Row_Number to get the column number.
DECLARE #idList varchar(800)
DECLARE #idListAlias varchar(800)
DECLARE #sql nvarchar(max)
SELECT
#idList = coalesce(#idList + ', ', '') + '['+ltrim(rtrim(id_producto)) +']',
#idListAlias = coalesce(#idListAlias + ', ', '') + '['+ltrim(rtrim(id_producto)) +'] as col_' + CONVERT(VARCHAR(10), ROW_NUMBER() OVER(ORDER BY id_producto))
from gestor_val_pos
group by id_producto order by id_producto
SELECT #sql = 'select ' + #idListAlias + ' from #correlaciones pivot (max (correl)
for codigo2 in (' + #IDlist + ')) AS pvt order by codigo1;'
exec sp_executeSQL #sql
Yes, but it'll make your query significantly more complex.
You'll need to return a list of the possible column names generated from #IDList and convert that into a SELECT clause more sophisticated than your current SELECT *.
When you've got that, use some SQL string splitting code to convert the #IDList into a table of items with a position parameter. Append AS <whatever> onto the end of any you want and use the FOR XML PATH trick to flatten it back, and you've got a SELECT clause that'll do what you want. But, as I said, your code is now significantly more complicated.
As an aside - I really hope that #idList is either completely impossible for any user input to ever reach or hugely simplified from your real code for this demonstration. Otherwise you've got a big SQL injection hole right there.

Transpose row results to single row

Perhaps my example is too simple but I need to transpose the results from being multiple rows to being multiple columns in a single row. The only issue that the number of returned initial rows may vary so therefore my final number of columns may vary also.
As an example, my returned results from
select name from pets
could be:
Dog
Cat
Fish
Rabbit
And I need each value in a separate column:
Dog Cat Fish Rabbit
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX)
-- First create list of columns that you need in end result
SET #columns = N''
SELECT #columns += N', ' + QUOTENAME(name)
FROM (select distinct name from pets) AS x
-- now create pivot statement as:
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT name
FROM pets
) AS j
PIVOT
(
max(name) FOR name IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;'
EXEC sp_executesql #sql;
DEMO

Group by and aggregate functions in dynamic SQL Pivot Query

I have formulated the following dynamic SQL Query to turn an unkown number of row values (Maschine Names as nvarchar) into columns. The Row Values of the Pivoted Columns should be the sum of downtime and maintenance time (both int) for the particular Maschine.
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(Maschines)
FROM (SELECT Maschines FROM Rawdata AS p
GROUP BY MASCHINE) AS x;
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM(
SELECT * from Rawdata
) AS j
PIVOT
(
SUM(maintenance) FOR Maschines IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+')
) AS p;';
EXEC sp_executesql #sql;
The Query returnes the Summed up maintenance time, but once i try to include more than one column in the sum ( like Sum(maintenance+downtime) ) i get an error close to '+'.
In addition, the query returns a pivoted table, but still has the same number of rows, however i need a result that is grouped for the unkown number of columns, thus containing only one row
So unfortunately PIVOT in SQL is fairly limited in that you can only perform a single aggregation of a single column.
Your best option is instead of just doing
Select * from rawdata
as your source, include a column that pre-adds maintenance and downtime together, like
select *, maintenance + downtime as TotalTime from rawdata
and then aggregate it.

SQL Server stored procedure based on PIVOT and parameterized query not sorting properly

I am using a SQL Server stored procedure to generate a data table I use as a datasource for a WPF datagrid. The data are in two tables of parent [Sample] child [SampleConstituent] relationship and I am using a PIVOT to generate columns for the data records in the child table. The query uses parameters so that I can filter the records returned to the datagrid.
My problem is that I would like to return a TOP N result set from the query based on the most recent records. I have the following stored procedure and everything works except the data are always returned oldest records first. The TOP N filter is thus returning the oldest records and not the most recent. Sample.SampleDateTime is the column in the parent table I wish to sort by.
I have tried so many iterations that my novice brain is in knots!
ALTER PROCEDURE [dbo].[spSampleDisplayAllParams]
-- Add the parameters for the stored procedure here
#fromDate DATE = '2013-01-01',
#toDate DATE = '2100-01-01',
#ProductName NVARCHAR(50) = '%',
#SampleNumber NVARCHAR(50) = '%',
#numSamples NVARCHAR(50) = 200
AS
BEGIN
SET NOCOUNT ON;
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(ConstituentName)
from SampleConstituent
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
PRINT #cols
set #query
= 'SELECT top (' + #numSamples + ') * from
(SELECT TOP 100 PERCENT s.SampleID, s.SampleNumber, s.SampleDateTime, s.ProductName, sc.ConstituentName, sc.ConstituentValue
FROM dbo.Sample s INNER JOIN
dbo.SampleConstituent sc ON s.SampleID = sc.SampleID
WHERE (s.Active = 1) AND
(s.ProductName Like ''' + #ProductName + ''') AND
(s.SampleNumber Like ''' + #SampleNumber + ''') AND
(s.SampleDateTime BETWEEN ''' + CONVERT(nvarchar(24), #FromDate, 121) +''' AND'''+ CONVERT(nvarchar(24), #ToDate, 121) +''')
ORDER BY s.SampleDateTime ) x
pivot
(
max(ConstituentValue)
for ConstituentName in (' + #cols + ')
) p '
execute(#query)
END
Try putting order by after the pivot :
) x
pivot
(
max(ConstituentValue)
for ConstituentName in (' + #cols + ')
) p ORDER BY SampleDateTime '
for more clarification your query should look something like this:
set #query
= 'SELECT top (' + #numSamples + ') * from
(SELECT s.SampleID, s.SampleNumber, s.SampleDateTime, s.ProductName, sc.ConstituentName, sc.ConstituentValue
FROM dbo.Sample s INNER JOIN
dbo.SampleConstituent sc ON s.SampleID = sc.SampleID
WHERE (s.Active = 1) AND
(s.ProductName Like ''' + #ProductName + ''') AND
(s.SampleNumber Like ''' + #SampleNumber + ''') AND
(s.SampleDateTime BETWEEN ''' + CONVERT(nvarchar(24), #FromDate, 121) +''' AND'''+ CONVERT(nvarchar(24), #ToDate, 121) +''')
) x
pivot
(
max(ConstituentValue)
for ConstituentName in (' + #cols + ')
) p ORDER BY SampleDateTime DESC'
--sort and order is placed after the pivot
Your order by is inside with the TOP 100 PERCENT. In this case, the ORDER BY is used to dictate which rows to include, but since you said you want all of them, SQL Server is clever and throws out both the TOP and the ORDER BY. Look at the plan, I bet there is no sort anywhere. This:
SELECT * FROM
(SELECT TOP 100 PERCENT something FROM somewhere ORDER BY something) AS x;
Is the exact same as:
SELECT * FROM
(SELECT something FROM somewhere) AS x;
In neither case is SQL Server told to order the end result by anything. So it doesn't, and instead chooses an ordering which is most efficient rather than trying to read your mind.
If you want to sort, you need to put it on the outermost query, as #Sonam has identified. Some background info here:
Bad habits to kick : relying on undocumented behavior
And FYI, you should do your best to guard yourself from SQL injection and use proper, parameterized queries. Again, not to take anything from #Sonam's answer, but this would be better as:
SELECT #sql = N'SELECT TOP (#numSamples) * FROM
(
SELECT s.SampleID, s.SampleNumber, s.SampleDateTime,
s.ProductName, sc.ConstituentName, sc.ConstituentValue
FROM dbo.Sample AS s
INNER JOIN dbo.SampleConstituent AS sc
ON s.SampleID = sc.SampleID
WHERE (s.Active = 1) AND
(s.ProductName LIKE #ProductName) AND
(s.SampleNumber LIKE #SampleNumber) AND
(s.SampleDateTime >= #FromDate AND s.SampleDateTime < DATEADD(DAY, 1, #ToDate)
) AS x
PIVOT
(
MAX(ConstituentValue) FOR ConstituentName IN (' + #cols + ')
) AS p
ORDER BY SampleDateTime DESC;';
DECLARE #params NVARCHAR(MAX) = N'#numSamples INT, #fromDate DATE, ' +
'#toDate DATE, #ProductName NVARCHAR(50), #SampleNumber NVARCHAR(50)';
EXEC sp_executesql #sql, #params, #numSamples, #fromDate, #toDate,
#ProductName, #SampleNumber;
Note that your creation of #cols may include constituent names that don't appear in the table in the date range you selected (or with the other parts of the where clause), so if you don't want a bunch of columns with NULL values for every date selected, you may want to add some of those criteria to that query as well.