I am writing the following dynamic SQL, and getting an error in the PIVOT section of the View creation.
Incorrect syntax near ','
FOR [Accounts] IN (
' + #pivotColumns + '
I know why, its because there shouldn't be a comma that precedes the FIRST #pivotColumn (see image below for example). However, the column is a result of dynamic SQL, how do I go about resolving this? And I cant use trailing comma instead because then I will have a similar problem with the last column.
Basically, I need a way to make
SELECT #pivotColumns =....
not append a leading comma to the FIRST column/Account selected.
The comma is added at the end at this part of #pivotColumns:
',', quotename
Full Query:
DECLARE #sqlCommand NVARCHAR(MAX) = '',
#columnList NVARCHAR(MAX) = '',
#pivotColumns NVARCHAR(MAX) = '';
SELECT #columnList = STUFF(
(SELECT DISTINCT concat(CHAR(9), ',', 'COALESCE(', quotename(AccTbl.Account), ', 0)', quotename(AccTbl.Account), CHAR(10))
FROM [Accounts] AccTbl
--WHERE AccTbl.Active = 'YES'
WHERE AccTbl.Account NOT IN ('WS')
FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');
SELECT #pivotColumns = STUFF(
(SELECT DISTINCT concat(CHAR(9), ',', quotename(AccTbl.Account), CHAR(10))
FROM [Accounts] AccTbl
WHERE AccTbl.Account NOT IN ('WS')
FOR XML Path(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 1, '');
/*
EXEC the sqlCommand as separate batches to prevent this error: 'CREATE VIEW' must be the first statement in a query batch.
https://stackoverflow.com/a/39135516/8397835
*/
SET #sqlCommand = '
USE [ABC_DB]
--GO
DROP VIEW IF EXISTS [dbo].[Piv];
--GO
SET ANSI_NULLS ON
--GO
SET QUOTED_IDENTIFIER ON
--GO
';
Execute sp_executesql #sqlCommand;
SET #sqlCommand = '
CREATE VIEW [dbo].[Piv]
AS
(SELECT
[Style Code],
' + #columnList + '
FROM [History]
PIVOT (SUM([Value]) FOR [Accounts] IN (
' + #pivotColumns + '
)
)
AS Piv);
';
PRINT #sqlCommand;
Execute sp_executesql #sqlCommand;
In other words, whats happening right now is something like this:
Related
Im new to Stack overflow and SQL and I'm trying to replicate a function in Microsoft Power Query to use in SQL instead.
I know how to Unpivot and keep 1 Column in SQL and then had to reference all the other columns by name to make the unpivot.
Now I need to Keep 3 ID columns and Unpivot the rest of the columns
(This table has 355 columns right now and will change)
Can anyone help me with this?
This is the furthest I got (Thanks to RAV DBLearning on Youtube), but I cant seem to find a way to Convert the columns types to 1 type.
DECLARE
#SQLSTRING NVARCHAR(MAX),
#COLUMNLIST NVARCHAR(1000) = ''
SELECT
#COLUMNLIST = #COLUMNLIST + QUOTENAME(NAME) + ','
FROM
sys.columns
WHERE
OBJECT_ID = OBJECT_ID('xp.XPROPERTYVALUES') AND
--COLUMN_ID NOT IN(1,2,3)
COLUMN_ID IN(452,453,454)
SELECT
#COLUMNLIST = LEFT(#COLUMNLIST,LEN(#COLUMNLIST)-1)
SET
#SQLSTRING =
'
SELECT
upv.id,
upv.item_id,
upv.itemtype_id,
upv.X_Category,
upv.X_Values
FROM
xp.XPROPERTYVALUES
UNPIVOT
(
X_Values FOR X_Category
IN
(' + #COLUMNLIST + ')
) AS upv
'
PRINT
(#SQLSTRING)
EXECUTE
sp_executesql #SQLSTRING
It just needs a source query.
And you can re-use the calculated column list for that.
DECLARE #SQLSTRING NVARCHAR(MAX),
#COLUMNLIST NVARCHAR(MAX);
DECLARE #TABLENAME VARCHAR(30) = 'xp.XPROPERTYVALUES';
SELECT #COLUMNLIST = CONCAT(#COLUMNLIST + ', ', QUOTENAME(NAME))
FROM sys.columns
WHERE OBJECT_ID = OBJECT_ID(#TABLENAME)
AND LOWER(NAME) NOT LIKE '%id';
SET #SQLSTRING = N'SELECT upv.id
, upv.item_id, upv.itemtype_id
, upv.X_Category, upv.X_Values
FROM
(
SELECT id, item_id, itemtype_id,
'+ #COLUMNLIST + N'
FROM '+ #TABLENAME +N'
) src
UNPIVOT
(
X_Values FOR X_Category IN (' + #COLUMNLIST + N')
) upv';
-- SELECT #SQLSTRING;
EXECUTE sp_executesql #SQLSTRING;
db<>fiddle here
I have to write update using dynamic sql becaus i know only name of column that I want to update and names of columns which I will use to join tables in my update. But I don't know the numbers of tables and names. Names of tables I will get in parameter of my procedure in this way
declare #Tables = N'Customer,Employee,Owner'
So I want to have update like this:
update t
set [Status] = 100
from
TemporaryTable t
left join Customer t1 on t1.RecordId = t.RecordId
left join Employee t2 on t2.RecordId = t.RecordId
left join Owner t3 on t3.RecordId =t.RecordId
where
t1.RecordId is null
and t2.RecordId is NULL
and t3.RecordId is null
I know that each table will have column RecordId and want to left join this tables to my TemporaryTable on this column but I don't know the names and numbers of tables. For example I will have one, two, or ten tables with different names. I know that this tables names will be save in parameter #Tables in that way:
#Tables = N'Customer,Employee,Owner'
There is possilble to write this update in dynamic way?
This is an answer, which helps ... to write update using dynamic sql ... and only shows how to generate a dynamic statement. It's based on string splitting. From SQL Server 2016+ you may use STRING_SPLIT() (because here the order of the substrings is not important). For previous versions you need to find a string splitting function.
T-SQL:
DECLARE #Tables nvarchar(max) = N'Customer,Employee,Owner'
DECLARE #join nvarchar(max) = N''
DECLARE #where nvarchar(max) = N''
DECLARE #stm nvarchar(max) = N''
SELECT
#join = #join + CONCAT(
N' LEFT JOIN ',
QUOTENAME(s.[value]),
N' t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N' ON t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N'.RecordId = t.RecordId'
),
#where = #where + CONCAT(
N' AND t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N'.RecordId is NULL'
)
FROM STRING_SPLIT(#Tables, N',') s
SET #stm = CONCAT(
N'UPDATE t SET [Status] = 100 ',
N'FROM TemporaryTable t',
#join,
N' WHERE ',
STUFF(#where, 1, 5, N'')
)
PRINT #stm
EXEC sp_executesql #stm
Notes:
One note, that I think is important - consider passing tables names using table value type for parameter, not as comma-separated text.
It seems like this will suit your needs, though I don't fully understand what you're trying to do. Here we're constructing the final SQL in two pieces (#s and #where) and then concatenating into the final SQL at the end.
declare #Tables varchar(100) = N'Customer,Employee,Owner'
declare #tablenames table (tablename nvarchar(100))
insert #tablenames (tablename)
select value
from string_split(#Tables, ',');
declare #where varchar(max) = ''
declare #s varchar(max) = '
update t
set [Status] = 100
from TemporaryTable t'
select #s += '
left join ' + tablename + ' on ' + tablename + '.RecordId = t.RecordId'
, #where += case when #where = '' then '' else ' and ' end + tablename + '.RecordId is null
'
from #tablenames
print #s + char(13) + ' where ' + #where
exec( #s + char(13) + ' where ' + #where)
USE [prjMarlin]
GO
/****** Object: StoredProcedure [dbo].[CriticalDateDiary] Script Date: 19/07/2017 3:55:51 PM ******/
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[CriticalDateDiary]
AS
SET NOCOUNT ON
DECLARE #ColumnsTable TABLE ([ColumnName] VARCHAR(50));
INSERT INTO #ColumnsTable ([ColumnName])
SELECT DISTINCT '[' + CONVERT(VARCHAR(48), WFStepName) + ']'
FROM vwTenureWorkFlowStepReport
--where WorkFlowId='a5a23e7e-31ea-439e-b823-179a3bd731ec'
DECLARE #PivotColumns VARCHAR(MAX), #TotalColumn VARCHAR(MAX), #SQL VARCHAR(MAX);
SET #PivotColumns = (SELECT STUFF((SELECT DISTINCT ', ' + CONVERT(VARCHAR(50), [ColumnName])
FROM #ColumnsTable
FOR XML PATH('')), 1, 2, ''));
SET #TotalColumn = (SELECT STUFF((SELECT DISTINCT ' + ISNULL(' + CONVERT(VARCHAR(50), [ColumnName]) + ', 0)'
FROM #ColumnsTable
FOR XML PATH('')), 1, 3, ''));
SET #SQL = 'SELECT *, (' + #TotalColumn + ') AS [Total]
FROM (SELECT Tenure,GETDATE() AS CurrentTime,[WFStepName],[StepExpectedEndTime]
FROM [vwRunningTenureWorkFlowStepReport]) AS t
PIVOT (MAX([StepExpectedEndTime]) FOR WFStepName IN (' + #PivotColumns + ')) AS p;';
exec(#SQL)
When i use database first through entity framework, this stored procedure gets detected as a return type of integer instead of table data? Any suggestion?
Found no satisfactory method that wan't messy, hence i created a script to generate a table as the end result.
I have some bulk views to create for an entire database.
To create a view the general syntax is as follows:
CREATE VIEW [TABLE_NAME]
AS
SELECT [COLUMN1], [COLUMN2], [COLUMN3], [COLUMN4]
FROM [TABLE_NAME]
WITH CHECK OPTION;
I would like to set the column names in the script above by querying the column names ([COLULMN1], [COLUMN2], etc) from INFORMATION_SCHEMA.COLUMNS.
Is there a way to achieve this by table name?
COALESCE is your friend good programmer. What you want to do is get a csv list of COLUMNS. Then using dynamic sql you can auto generate the rest of the code.
declare #columns AS VARCHAR(MAX)
SELECT #COLUMNS = NULL
select #COLUMNS = coalesce(#columns+',','')+c.name from syscolumns as c
inner join sysobjects as o on c.id = o.id
WHERE O.NAME = 'change me to your table name'
SELECT #COLUMNS
SELECT ' CREATE VIEW ' + 'COOL VIEW NAME' + ' AS ' +
' SELECT ' + #COLUMNS +
' FROM '+ ' change me to your table name '+
' WITH CHECK OPTION;'
EDIT
I purposely didn't declare the view anywhere. If you want to declare the view just execute the scripts like so. BUT YOU SHOULD NEVER just execute code on your servers without reading it all I purposely excluded the execution part as I think it is bad judgement just to cut and paste code and execute it without understanding/testing.
DECLARE #sql varchar(max)
SELECT #sql = ' CREATE VIEW ' + 'COOL VIEW NAME' + ' AS ' +
' SELECT ' + #COLUMNS +
' FROM '+ ' change me to your table name '+
' WITH CHECK OPTION;'
EXEC(#sql);
Here's one option... replace "MyTableName" with the table name you want, or wrap it in a cursor that reads TABLE_NAME from INFORMATION_SCHEMA.VIEWS into #tableName:
DECLARE #tableName sysname;
DECLARE #sql nvarchar(max);
DECLARE #columnList nvarchar(max);
SELECT #tableName = 'MyTableName';
SELECT #columnList = '';
SELECT #columnList += CASE WHEN LEN(#columnList) = 0 THEN '' ELSE ', ' END + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #tableName ORDER BY ORDINAL_POSITION;
SELECT #sql = 'CREATE VIEW [TABLE_NAME] AS
SELECT ' + #columnList + '
FROM [' + #tableName + ']
WITH CHECK OPTION;'
PRINT #sql
EXEC(#sql);
I have a need to query several tables, with similar data, but I don't want to write a lengthy UNION statement. Also, the number of tables can vary. Currently I accomplish this using Coldfusion by:
looping over a list of tablenames
performing the UNION inside the loop
Like this
set i = 0
set table_suffixes = "blue,green,red,yellow"
loop list="table_suffixes" index="idx_suffix"
set tName = "table_" & idx_suffix
if i > 0
UNION
end if
select *
FROM tName
end loop
Can this be done in T-SQL? I have not been able to find any articles about using a list to control a loop. The closest I could find would be to place a query in the loop. Like This
While (SELECT table_suffix FROM vendor_name WHERE discipline = 'electrical')
BEGIN
*union query...*
END
Any help to point me in the right direction is much appreciated. I am using SQL-Server 2008
Although generally nor recommended, here is an example of generating and executing dynamic SQL within T-SQL.
Note I have used UNION ALL. You should understand the differences between this and UNION
Once you have this working you could wrap it in a a stored procedure that accepts a parameter to use for DISCIPLINE
Also please note, the fact you have to do this implies that you have some serious design flaws in your database.
DECLARE #TableName VARCHAR(100)
DECLARE #DSQL VARCHAR(4000)
SET #DSQL = ''
DECLARE cTableList CURSOR FOR
SELECT table_suffix FROM vendor_name WHERE discipline = 'electrical'
OPEN cTableList
FETCH NEXT FROM cTableList INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #DSQL = #DSQL + 'SELECT * FROM ' + #TableName + ' UNION ALL '
END
CLOSE cTableList
DEALLOCATE cTableList
-- Remove the last UNION ALL
IF LEN(#DSQL) > 11 SET #DSQL = LEFT(#DSQL,LEN(#DSQL) - 11)
-- Print it out for debugging purposes
PRINT (#DSQL)
-- Execute it
EXEC (#DSQL)
Try this one -
Query:
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL' + CHAR(13) + 'SELECT * FROM [' + s.name + '].[' + o.name + ']'
FROM sys.objects o
JOIN sys.schemas s ON o.[schema_id] = s.[schema_id]
WHERE o.[type] = 'U'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
Output:
SELECT * FROM [dbo].[test1]
UNION ALL
SELECT * FROM [dbo].[MyTable]
UNION ALL
...
In your case see something like this -
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL' + CHAR(13) + 'SELECT * FROM ' + table_suffix
FROM vendor_name
WHERE discipline = 'electrical'
WHERE o.[type] = 'U'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
PRINT #SQL