SQL - Dynamically add columns to temp table based on value - sql

I need to perform some dynamic operation temp table based on some condition.
(add columns based of number and update only those columns)
I have one one master table which contains unit information -
E.G.
ID UNIT
1 kg
2 cm
3 mm
Here, number of rows can vary. It can contain 3 rows or 4 rows or 2 rows.
Now i want create some columns in my temp table based on this.
E.G.
if master table has 2 values then #temp should contain 2 columns as unit1 and unit2.
if 3 values then unit1, unit2 and unit3.
Is it possible? Or do i need to create max number of columns directly in temp table?
Thanks

You have to use Dynamic PIVOT and GLOBAL TEMP TABLE in following:
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
GO
CREATE table #t
(id varchar(max),unit varchar(max))
insert into #t (id,unit)values
(1,'kg'),
(2,'cm'),
(3,'mm'),
(4,'m')
DECLARE #statement NVARCHAR(max)
,#columns NVARCHAR(max)
SELECT #columns = ISNULL(#columns + ',', '') + N'[' + cast(tbl.id as varchar(max)) + ']'
FROM (
SELECT DISTINCT id
FROM #t
) AS tbl
SELECT #statement = 'select *
INTO ##temp
from (
SELECT id,[unit]
FROM #t
) as s
PIVOT
(max(unit) FOR id in(' + #columns + ')) as pvt
'
EXEC sp_executesql #statement = #statement
SELECT * FROM ##temp
DROP TABLE ##temp

Related

Pivot & UnPivot functionality with dates in SQL Server

I'm currently working to retrieve the last 6 available dates data using pivot, unpivot. Can anyone help me with this query?
select *
from #myTable
unpivot (value for Area in (abc,def,fgh,ijk,klp)) up
pivot (max(value) for [date] in (
##-- Here I need to get the last 6 available dates less than current date
)) p
Datatype of [Date] column is DATE.
Sample values of date in my db
2017-09-16,
2017-09-09,
2017-09-02,
2017-08-26,
2017-07-22,
2017-07-01,
2017-06-24,
2017-06-11
Sample table, with expected result
Well, from your sample data, the top 6 for one area would be the same for each area since Area is the column names. With that information, we can use a dynamic unpivot after the original pivot.
declare #table table (TheDate date, abc int, def int, fgh int, ijk int, klp int)
insert into #table
values
('20170916',1,2,34,4,5),
('20170909',2,3,4,5,676),
('20170902',6,7,8,8,9)
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
if object_id('tempdb..#staging') is not null drop table #staging
select
Area
,TheDate
,Val
into #staging
from #table
unpivot
(Val for Area in (abc,def,fgh,ijk,klp)
) up
--Get distinct values of the PIVOT Column / top 6 by date
SELECT #ColumnName= ISNULL(#ColumnName + ',','')
+ QUOTENAME(TheDate)
FROM (SELECT DISTINCT TOP 6 TheDate FROM #staging ORDER BY TheDate DESC) AS TheDate
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT Area, ' + #ColumnName + '
FROM #staging
PIVOT(SUM(Val)
FOR TheDate IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
See It In Action Here

How to sum dynamic columns in SQL Server?

Assume I have a table with 3 columns. Is there possible sum of each column without specifying name of the column?
And is there possible create a table with dynamic name of the column, and then sum of each column?
UPDATE: Here is my sample.
First, I do a query and get the result like this:
---------
| Col |
---------
| DLX |
| SUI |
| PRE |
| TWQ |
---------
The number of row maybe different each time, and then I create a table with columns from rows above like this:
---------------------------------
| DLX | SUI | PRE | TWQ |
---------------------------------
And then I fill data the table from another table. After all, I sum each column. Because I will not know exactly name of the column, so I need sum of each column without specifying name of the column.
If your table is small (i.e. 10 columns) I would just do it manually. But if it's like 20+ columns, I would employ some dynamic sql.
To answer your question directly, yes, you can dynamically create a table with dynamic column names using dynamic sql.
Here's one way to do it:
You can use INFORMATION_SCHEMA.COLUMNS View to get all the column names and put them in a temp table.
SELECT NAME INTO #COLUMNS
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = YourTable
Next, create a temp table to store your sums.
CREATE TABLE #SUMS (
COLUMN_NAME NVARCHAR(MAX),
SUM_COLUMN INT
)
You can then use dynamic sql and a loop to sum each column.
WHILE EXISTS(SELECT TOP 1 * FROM #COLUMNS)
BEGIN
DECLARE #COLUMN NVARCHAR(MAX) = (SELECT TOP 1 * FROM #COLUMNS)
DECLARE #DYNAMICSQL NVARCHAR(MAX) = N'SELECT ' + #COLUMN + ' AS COLUMN_NAME, SUM(' + #COLUMN + ') FROM YourTable'
INSERT INTO #SUMS
EXEC SP_EXECUTESQL #DYNAMICSQL
DELETE FROM #COLUMNS
WHERE NAME = #COLUMN
END
Then you would need another dynamic sql and loop to loop through the new table and create a temp table with the column names you want using the sum values and the table name you want.
You should be able to do that using the code already supplied above.
At first I thought about pivoting but then came up with this:
DECLARE #Table TABLE ( --Table to generate input you need
[Col] nvarchar(3)
)
DECLARE #query nvarchar(max) -- a variable that will store dynamic SQL query
DECLARE #table_name nvarchar(max) = 'Temp' --A name of table to create
INSERT INTO #Table VALUES
('DLX'),
('SUI'),
('PRE'),
('TWQ')
SELECT #query = ISNULL(#query,'CREATE '+#table_name+' TABLE (') + QUOTENAME([Col]) + ' nvarchar(max),'
FROM #Table
SELECT #query = SUBSTRING(#query,1,LEN(#query)-1) +')'
EXEC sp_executesql #query
That will execute a query (PRINT #query to see the result below):
CREATE Temp TABLE ([DLX] nvarchar(max),[SUI] nvarchar(max),[PRE] nvarchar(max),[TWQ] nvarchar(max))
Which will create a temp table for you.
Then you can insert in that table in the pretty same way.

Dynamic SQL Result INTO Temporary Table

I need to store dynamic sql result into a temporary table #Temp.
Dynamic SQL Query result is from a pivot result, so number of columns varies(Not fixed).
SET #Sql = N'SELECT ' + #Cols + ' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
--#Cols => Column Names which varies in number
Now I have to insert dynamic sql result to #Temp Table and use this #Temp Table with another existing table to perform joins or something else.
(#Temp table should exist there to perform operations with other existing tables)
How can I Insert dynamic SQL query result To a Temporary table?
Thanks
Can you please try the below query.
SET #Sql = N'SELECT ' + #Cols + '
into ##TempTable
FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
You can then use the ##TempTable for further operations.
However, do not forget to drop the ##TempTable at the end of your query as it will give you error if you run the query again as it is a Global Temporary Table
As was answered in (https://social.msdn.microsoft.com/Forums/sqlserver/en-US/144f0812-b3a2-4197-91bc-f1515e7de4b9/not-able-to-create-hash-table-inside-stored-proc-through-execute-spexecutesql-strquery?forum=sqldatabaseengine),
you need to create a #Temp table in advance:
CREATE TABLE #Temp(columns definition);
It seems that the task is impossible, if you know nothing about the dynamic list of columns in advance. But, most likely you do know something.
You do know the types of dynamic columns, because they come from PIVOT. Most likely, you know the maximum possible number of dynamic columns. Even if you don't, SQL Server has a limit of 1024 columns per (nonwide) table and there is a limit of 8060 bytes per row (http://msdn.microsoft.com/en-us/library/ms143432.aspx). So, you can create a #Temp table in advance with maximum possible number of columns and use only some of them (make all your columns NULLable).
So, CREATE TABLE will look like this (instead of int use your type):
CREATE TABLE #Temp(c1 int NULL, c2 int NULL, c3 int NULL, ..., c1024 int NULL);
Yes, column names in #Temp will not be the same as in #Cols. It should be OK for your processing.
You have a list of columns in your #Cols variable. You somehow make this list of columns in some external code, so when #Cols is generated you know how many columns there are. At this moment you should be able to generate a second list of columns that matches the definition of #Temp. Something like:
#TempCols = N'c1, c2, c3, c4, c5';
The number of columns in #TempCols should be the same as the number of columns in #Cols. Then your dynamic SQL would look like this (I have added INSERT INTO #Temp (#TempCols) in front of your code):
SET #Sql = N'INSERT INTO #Temp (' + #TempCols + N') SELECT ' + #Cols + N' FROM
(
SELECT ResourceKey, ResourceValue
FROM LocaleStringResources where StateId ='
+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =''' + LTRIM(RTRIM(#CultureCode)) + '''
) x
pivot
(
max(ResourceValue)
for ResourceKey IN (' + #Cols + ')
) p ;'
Then you execute your dynamic SQL:
EXEC (#Sql) OR sp_executesql #Sql
And then do other processing using the #Temp table and temp column names c1, c2, c3, ...
MSDN says:
A local temporary table created in a stored procedure is dropped
automatically when the stored procedure is finished.
You can also DROP the #Temp table explicitly, like this:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp'
All this T-SQL code (CREATE TABLE, EXEC, ...your custom processing..., DROP TABLE) would naturally be inside the stored procedure.
Alternative to create a temporary table is to use the subquery
select t1.name,t1.lastname from(select * from table)t1.
where "select * from table" is your dyanmic query. which will return result which you can use as temp table t1 as given in example .
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
CREATE TABLE ##TmepTable (TmpCol CHAR(1))
DECLARE #SQL NVARCHAR(max) =' IF OBJECT_ID(''tempdb..##TmepTable'') IS NOT
NULL DROP TABLE ##TmepTable
SELECT * INTO ##TmepTable from [MyTableName]'
EXEC sp_executesql #SQL
SELECT Alias.* FROM ##TmepTable as Alias
IF OBJECT_ID('tempdb..##TmepTable') IS NOT NULL DROP TABLE ##TmepTable
Here is step by step solution for your problem.
Check for your temporary tables if they exist, and delete them.
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
IF OBJECT_ID('tempdb..##abc') IS NOT NULL
DROP TABLE ##abc
Store your main query result in first temp table (this step is for simplicity and more readability).
SELECT *
INTO #temp
FROM (SELECT ResourceKey, ResourceValue
FROM LocaleStringResources
where StateId ='+ LTRIM(RTRIM(#StateID)) + ' AND FormId =' + LTRIM(RTRIM(#FormID))
+ ' AND CultureCode =' + LTRIM(RTRIM(#CultureCode)) + ') AS S
Write below query to create your pivot and store result in another temp table.
DECLARE #str NVARCHAR(1000)
DECLARE #sql NVARCHAR(1000)
SELECT #str = COALESCE(#str+',', '') + ResourceKey FROM #temp
SET #sql = N'select * into ##abc from (select ' + #str + ' from (SELECT ResourceKey, ResourceValue FROM #temp) as A
Pivot
(
max(ResourceValue)
for ResourceKey in (' + #str + ')
)as pvt) as B'
Execute below query to get the pivot result in your next temp table ##abc.
EXECUTE sp_executesql #sql
And now you can use ##abc as table where-ever you want like
select * from ##abc
Hope this will help you.

Dropping a column in sql table based on its value not column name

I have a temp table with its column_names are created dynamically and values come from XML but some columns have NULL value and I would like to drop them from the table.
the command:
ALTER TABLE ##temptable
DROP COLUMN COLUMN_NAME
works perfect if I knew the column_name but since column_names are created dynamically, I would not know them. example:
animal | human | things | stars | cars
5 | name | null | bright | null
so the columns 'things' and 'cars' that created dynamically needed to be dropped, so it would look like this:
animal | human | stars
5 | name | bright
is there way to do that (I don't want to create view)?
You can use the following. Of course, you should be adding more validation to be sure it doesn't fail.
create table #tmp (user_id int, col1 int, col2 int)
declare #column_to_drop nvarchar(100) = 'col1'
declare #sql nvarchar(max) = 'ALTER TABLE #tmp DROP COLUMN ' + QUOTENAME(#column_to_drop)
exec sp_executesql #sql
select * from #tmp
Good luck.
LATER EDIT:
You can try this. The only thing needed to know is temp table name.
CREATE TABLE #tmp (a nvarchar(max), b nvarchar(10), c nvarchar(100), d nvarchar(1000), e datetime, f int)
INSERT #tmp VALUES ('1', null, '2', null, GETDATE(), null)
SELECT *
FROM #tmp
SELECT name, CAST(0 AS BIT) checked
INTO #col_names
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb..#tmp')
DELETE C
FROM ( SELECT *
FROM #tmp
FOR XML PATH(''), TYPE) AS T(XMLCol)
CROSS APPLY T.XMLCol.nodes('*') AS n(Col)
INNER JOIN #col_names C
ON c.name = Col.value('local-name(.)', 'varchar(max)')
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = COALESCE(#sql + ', ' + QUOTENAME(name), QUOTENAME(name))
FROM #col_names
SET #sql = 'ALTER TABLE #tmp DROP COLUMN ' + #sql
PRINT #sql
EXEC (#sql)
SELECT *
FROM #tmp
DROP TABLE #tmp
DROP TABLE #col_names

How to create a temp table from a dynamic list

I have a table FieldList (ID int, Title varchar(50)) and want to create a temp table with a column list for each record in FieldList with the column name = FieldList.Title and the type as varchar.
This all happens in a Stored Proc, and the temp table is returned to the client for reporting and data analysis.
e.g.
FieldList Table:
ID Title
1 City
2 UserSuppliedFieldName
3 SomeField
Resultant Temp table columns:
City UserSuppliedFieldName SomeField
You can use the following proc to do what you are wanting. It just requires that you:
Create the Temp Table before calling the proc (you will pass in the Temp Table name to the proc). This allows the temp table to be used in the current scope as Temp Tables created in Stored Procedures are dropped once that proc ends / returns.
Put just one field in the Temp Table; the datatype is irrelevant as the field will be dropped (you will pass in the field name to the proc)
[Be sure to change the proc name to whatever you like, but the temp proc name is used in the example that follows]
CREATE PROCEDURE #Abracadabra
(
#TempTableName SYSNAME,
#DummyFieldName SYSNAME,
#TestMode BIT = 0
)
AS
SET NOCOUNT ON
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = COALESCE(#SQL + N', [',
N'ALTER TABLE ' + #TempTableName + N' ADD [')
+ [Title]
+ N'] VARCHAR(100)'
FROM #FieldList
ORDER BY [ID]
SET #SQL = #SQL
+ N' ; ALTER TABLE '
+ #TempTableName
+ N' DROP COLUMN ['
+ #DummyFieldName
+ N'] ; '
IF (#TestMode = 0)
BEGIN
EXEC(#SQL)
END
ELSE
BEGIN
PRINT #SQL
END
GO
The following example shows the proc in use. The first execution is in Test Mode that simply prints the SQL that will be executed. The second execution runs the SQL and the SELECT following that EXEC shows that the fields are what was in the FieldList table.
/*
-- HIGHLIGHT FROM "SET" THROUGH FINAL "INSERT" AND RUN ONCE
-- to setup the example
SET NOCOUNT ON;
--DROP TABLE #FieldList
CREATE TABLE #FieldList (ID INT, Title VARCHAR(50))
INSERT INTO #FieldList (ID, Title) VALUES (1, 'City')
INSERT INTO #FieldList (ID, Title) VALUES (2, 'UserSuppliedFieldName')
INSERT INTO #FieldList (ID, Title) VALUES (3, 'SomeField')
*/
IF (OBJECT_ID('tempdb.dbo.#Tmp') IS NOT NULL)
BEGIN
DROP TABLE #Tmp
END
CREATE TABLE #Tmp (Dummy INT)
EXEC #Abracadabra
#TempTableName = N'#Tmp',
#DummyFieldName = N'Dummy',
#TestMode = 1
-- look in "Messages" tab
EXEC #Abracadabra
#TempTableName = N'#Tmp',
#DummyFieldName = N'Dummy',
#TestMode = 0
SELECT * FROM #Tmp
Output from #TestMode = 1:
ALTER TABLE #Tmp ADD [City] VARCHAR(100), [UserSuppliedFieldName]
VARCHAR(100), [SomeField] VARCHAR(100) ; ALTER TABLE #Tmp DROP COLUMN
[Dummy] ;
Create this function and pass to it the list you require. It will generate a table for you temporarily that you can use in real-time with any other SQL query. I've provided an example as well.
CREATE FUNCTION [dbo].[fnMakeTableFromList]
(#List varchar(MAX), #Delimiter varchar(255))
RETURNS table
AS
RETURN (SELECT Item = CONVERT(VARCHAR, Item)
FROM (SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM (SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(#List, #Delimiter, '</i><i>') + '</i>').query('.')) AS a
CROSS APPLY [XML].nodes('i') AS x(i)) AS y
WHERE Item IS NOT NULL);
GO
And you can use it like this ...
Parm1 = a list in a string seperated by a delimiter
Parm2 = the delimiter character
SELECT *
FROM fnMakeTableFromList('a,b,c,d,e',',')
Result is a table ...
a
b
c
d
e
With previously created table "FieldList" with fields "ID" and "Title"...
step 1: create table. The field "First" is there just to have one field, we'll delete it at the end
create table #SOE (First varchar(50))
step 2: right click and select "Result To - > Result to text", then run query:
select 'alter table #SOE add ' + Title + ' varchar(50)' from dbo.FieldList
step 3: copy-paste result and execute it
step 4: delete first field
alter table #SOE drop column First
step 5: here is your table
select * from #SOE