Join using dynamic column names SQL Server - sql

I have a table which can output any number of different columns (from 'Level1' to 'Level'N).
I need to perform a left join on each of these dynamic columns against a CTE
I have written the following script but keep getting this error:
Msg 102, Level 15, State 1, Line 15 Incorrect syntax near '10'.
To troubleshoot, I have tried removing the each of the variables in the CTE with no luck.
Any help would be much appreciated!
DECLARE #rel varchar(4) = CAST('X112' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
--create cte
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE
,NEWID
FROM Tbl
WHERE TYPE = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
SELECT ID, NEWID, Level';
--find max lvl, convert to str
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
SET #counterstring = CAST(#counter AS varchar(3))
WHILE #counter != 0
BEGIN
SET #sqltext = #sqltext + #counterstring + ' INTO tbl3 '
+ ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
END
EXEC(#sqltext)
--edited version
DECLARE #rel varchar(4) = CAST('X112' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE AS mgmt_ENDDA
,NEWID
FROM tbl
WHERE SUBTY = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
INSERT INTO tbl3
SELECT ID, NEWID, Level';
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
WHILE #counter != 0
BEGIN
SET #counterstring = CAST(#counter AS varchar(3))
SET #sqltext = #sqltext + #counterstring
+ ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
END
EXEC(#sqltext)

Since select * into query creates a new table each time, I am assuming that you are trying to create 'n' number of tables for 'n' number of levels, with the result obtained by joining with nth column. I have 2 suggestions for you
Bring the select query within the while loop and append ';' at the end of the loop to split select queries.
instead of INTO tbl3 use INTO tbl + #counterstring as select * into will create new table
Hope this helps you

Can you change it like this and try again?
--edited version
DECLARE #rel varchar(4) = CAST('A012' AS varchar(4))
DECLARE #todaysdate date = CONVERT(date,GETDATE())
DECLARE #sqltext varchar(MAX) =
' WITH CTE AS
(
SELECT
ID
,STARTDATE
,ENDDATE AS mgmt_ENDDA
,NEWID
FROM tbl
WHERE SUBTY = ''' + #rel + '''
AND ENDDATE >= ' + CAST(#todaysdate AS varchar(30)) +' AND STARTDATE <= ' + CAST(#todaysdate AS varchar(30)) +'
)
INSERT INTO tbl3';
DECLARE #counter int = (SELECT MAX(lvl) FROM tbl2)
DECLARE #counterstring varchar(3)
WHILE #counter != 0
BEGIN
SET #counterstring = CAST(#counter AS varchar(3))
SET #sqltext = #sqltext + CHAR(10) +'SELECT ID, NEWID, Level'+#counterstring
SET #sqltext = #sqltext + ' FROM tbl2 a '
+ ' LEFT JOIN CTE c ON a.Level' + #counterstring + ' = c.NEWID'
SET #counter = #counter - 1
IF #counter <> 0
SET #sqltext = #sqltext + CHAR(10) + ' UNION '
END
EXEC(#sqltext)
You can try 'UNION ALL' instead of 'UNION' if you dont want to get rid of duplicate data. Hope this helps

Related

Replace `0` with `N/A` or `-1` in pivot query result

I have Attendance table in which date and attendance is stored and I am passing date range in this query to display attendance report.
Now my question is how can I replace 0 (which I am getting as a output if the date passsed doesn't match with the date inside the Attendance table) with N/A or -1 ?
SET #query = 'SELECT RollNo,FirstName,LastName, ' + #cols + ' from
(
select S.RollNo,U.FirstName,U.LastName,
D.startdate,
convert(CHAR(10), startdate, 120) PivotDate
from #tempDates D,Attendance A, Student S, UserDetails U
where D.startdate = A.Date and A.EnrollmentNo=S.EnrollmentNo and A.EnrollmentNo=U.userID
) x
pivot
(
count(startdate)
for PivotDate in (' + #cols + ')
) p '
You could use CASE as Brandon Miller suggested. Here's another option - you can use NULLIF to replace a zero with a null value, and then replace any null value with N/A. You'll need to create a 2nd variable to represent your columns in the select statement of your dynamic query. Here's a full example with test data:
-- test data
create table #tempDates (startdate date)
create table Attendance (date date, enrollmentno int)
create table Student (rollno int, enrollmentno int)
create table UserDetails (FirstName varchar(10), LastName varchar(10), userid int)
insert into #tempDates values ('1/1/2018')
insert into Attendance values ('1/1/2018', 1)
insert into Student values (1, 1)
insert into UserDetails values ('J', 'S', 1)
declare #cols varchar(100) = '[2018-01-01],[2018-01-02]'
declare #cols_select varchar(500) = 'ISNULL(NULLIF(CAST([2018-01-01] AS VARCHAR(10)), ''0''), ''N/A'') AS [2018-01-01],ISNULL(NULLIF(CAST([2018-01-02] AS VARCHAR(10)), ''0''), ''N/A'') AS [2018-01-02]'
DECLARE #query nvarchar(max)
SET #query = 'SELECT RollNo,FirstName,LastName, '
+ #cols_select
+ 'from
(
select S.RollNo,U.FirstName,U.LastName,
D.startdate,
convert(CHAR(10), startdate, 120) PivotDate
from #tempDates D,Attendance A, Student S, UserDetails U
where D.startdate = A.Date and A.EnrollmentNo=S.EnrollmentNo and A.EnrollmentNo=U.userID
) x
pivot
(
count(startdate)
for PivotDate in (' + #cols + ')
) p '
EXEC sp_executesql #query
Outputs:
RollNo FirstName LastName 2018-01-01 2018-01-02
1 J S 1 N/A
For fun, here's a function you can use to convert the #cols variable to the #cols_select variable:
create function dbo.fn_convert_cols(#cols varchar(max)) returns varchar(max)
as
begin
declare #col varchar(20)
declare #cols_select varchar(max) = ''
declare #idx int, #idx2 int
select #idx = CHARINDEX('[', #cols), #idx2 = CHARINDEX(']', #cols)
while #idx > 0 and #idx2 > 0
begin
select #col = SUBSTRING(#cols, #idx + 1, #idx2 - #idx - 1)
select #cols_select += ',ISNULL(NULLIF(CAST([' + #col + '] AS VARCHAR(10)), ''0''), ''N/A'') AS [' + #col + ']'
select #cols = SUBSTRING(#cols, #idx2 + 1, len(#cols) - #idx2)
select #idx = CHARINDEX('[', #cols), #idx2 = CHARINDEX(']', #cols)
end
select #cols_select = SUBSTRING(#cols_select, 2, len(#cols_select) - 1)
return #cols_select
end
go
So now you can just call the function when you're building the query, like this:
SET #query = 'SELECT RollNo,FirstName,LastName, ' + dbo.fn_convert_cols(#cols)+ ' from

How to update Temp table by providing column name through variable

I have temp table generated by dynamic SQL query with different column name depends upon data. when I want to update temp table by given hard coded column name it works fine. but when I pass column name by variable it won't works
update #Temp
set #value = ''' + cast(#TempData as nvarchar(max)) + '''
where id = #CUserID
here #value holds column name like [Dusky Legend] this query won't works but
update #Temp
set [Dusky Legend] = ''' + cast(#TempData as nvarchar(max)) + '''
where id = #CUserID
this works properly ,
my problem is I have only way to provide column name by variable
this is my complete code
Declare #MarketID AS NVARCHAR(MAX) = '1.136903880';
Declare #UserID AS NVARCHAR(MAX) = '6a309d84-d1c6-434d-b9df-4f96a74da912';
declare #TempData as numeric = 1111111111;
declare ##values as NVARCHAR(MAX) ='';
DECLARE #colsSelect AS NVARCHAR(MAX);
DECLARE #colsLoop AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #colsSelect = STUFF((SELECT distinct ',' +
'00' + ' as ' + QUOTENAME(name)
from RunnersInfoes AS t where marketID =#MarketID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #colsLoop = STUFF((SELECT distinct ',' +
QUOTENAME(name)
from RunnersInfoes AS t where marketID =#MarketID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
--print #colsLoop
set #query=
'
;with cte
as
(
select
id, ParentId,0 AS Level,Share ,AccountTypeName,FirstName
from dbo.View_UserProfile
where View_UserProfile.id = ' + '''' + #UserID + '''' +'
union all
select
t.id, t.ParentId,Level + 1 AS Level,t.Share,t.AccountTypeName ,t.FirstName
from View_UserProfile t
inner join cte on t.ParentId = cte.id)
SELECT ID,AccountTypeName as Type,FirstName as Name, ' + #colsSelect + ' into #Temp from cte as t
--exec tempdb..sp_help #Temp
Declare #CUserID AS NVARCHAR(MAX)
DECLARE dynamic_cursor CURSOR FOR
select ID from #Temp
OPEN dynamic_cursor
FETCH NEXT FROM dynamic_cursor INTO #CUserID
WHILE ##FETCH_STATUS = 0
BEGIN
declare #pos as numeric = 0
declare #len as numeric = 0
declare #value as varchar(255)
WHILE CHARINDEX('','', ''' + #colsLoop +''', #pos+1)>0
BEGIN
set #len = CHARINDEX('','', ''' + #colsLoop +''', #pos+1) - #pos
set #value = SUBSTRING(''' + #colsLoop +''', #pos, #len)
PRINT #value
update #Temp set [Dusky Legend] = ''' + cast(#TempData as nvarchar(max)) + ''' where id = #CUserID
set #pos = CHARINDEX('','', ''' + #colsLoop +''', #pos+#len) +1
END
--print' + cast(#TempData as nvarchar(max)) +'
--update #Temp set [Dusky Legend] =''' + cast(#TempData as nvarchar(max)) + ''' where id = #CUserID
FETCH NEXT FROM dynamic_cursor INTO #CUserID
END
CLOSE dynamic_cursor
DEALLOCATE dynamic_cursor
select * from #Temp
'
execute (#query)
You need dynamic SQL.
declare #value varchar(64) = '[Dusky Legend]'
declare #TempData varchar(max) = 'some value'
declare #CUSerID int = 14
declare #sql varchar(max)
set #sql = '
update #Temp
set ' + #value + ' = ' + cast(#TempData as nvarchar(max)) + '
where id = ' + cast(#CUserID as varchar(256))
print(#sql)
--exec(#sql)
Side note, you may need a global TempTable depending on how you are doing this.

Dynamically decide number of joins

I have two tables.
Table 1: Question_Master which contains the questions
id question
1 Q1
2 Q2
3 Q3
Table 2: Option Master Which contains the Options
id option
1 H
2 N
3 S
I want all the combinations of options for all the questions.
Something Like this
Q1 Q2 Q3
H H H
H H N
H H s
H N H
NOTE: There can be any number of records in both table.If it has 4 records in option_master than i want all combination for 4 records.
You can do it dynamically by using some string concatenation queries to build out the Select statement based on the Question_Master table values
DECLARE #SelectSQL VARCHAR(MAX),
#JoinSQL VARCHAR(MAX),
#OrderSQL VARCHAR(MAX)
SELECT #SelectSQL = COALESCE(#SelectSQL + ',', '')
+ QUOTENAME(question) + '.[option] as ' + QUOTENAME(question),
#JoinSQL = COALESCE(#JoinSQL + ' CROSS JOIN ', '')
+ 'Option_Master as ' + QUOTENAME(question),
#OrderSQL = COALESCE(#OrderSql + ',', '')
+ QUOTENAME(question) + '.[option]'
FROM Question_Master
ORDER BY question
DECLARE #Sql AS NVARCHAR(MAX) = N'SELECT ' + #SelectSQL + ' FROM ' + #JoinSQL + ' ORDER BY ' + #OrderSQL
EXECUTE sp_executesql #Sql;
using QUOTENAME will allow you to have questions that have spaces or some other characters in the value.
SQL Fiddle Example
You need to CROSS JOIN the Option_Master with itself. And then you need to cross join the result again with Option_Master. This has to be repeated for each question. I think this has to be done by dynamically creating the SQL statement. Try this example to get an idea:
declare #NumberOfQuestions int
set #NumberOfQuestions = (
select count(*)
from question_master
)
declare #sql varchar(max)
set #sql = 'select om1.opt '
declare #counter int
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast (#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
set #sql = #sql + '
from option_master om1 '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
cross join option_master om' + cast(#counter as varchar(1)) + ' '
set #counter = #counter + 1
end
set #sql = #sql + '
order by om1.opt '
set #counter = 2
while #Counter <= #NumberOfQuestions
begin
set #sql = #sql + '
, om' + cast(#counter as varchar(1)) + '.opt '
set #counter = #counter + 1
end
exec (#sql)
Albert

Retrieve multiple pieces of data from a single SQL Server variable

I have two variables like:
#FieldName
#values
Those two variables hold values like:
#FieldName - contains [a],[b],[c],[d]
#values - contains 5,6,7,8
Now I need to retrieve the data of column 'b' & 'd' only.
How can we get b=6 & d=8?
Thanks in advance.
well I hate to do such a things on SQL Server, but
declare #FieldName nvarchar(max) = '[a],[b],[c],[d]'
declare #values nvarchar(max) = '5,6,7,8'
declare #i int, #j int, #break int
declare #a nvarchar(max), #b nvarchar(max), #result nvarchar(max)
select #break = 0
while #break = 0
begin
select #i = charindex(',', #FieldName), #j = charindex(',', #values)
if #i > 0 and #j > 0
begin
select #a = left(#FieldName, #i - 1), #b = left(#values, #j - 1)
select #FieldName = right(#FieldName, len(#FieldName) - #i), #values = right(#values, len(#values) - #j)
end
else
begin
select #a = #FieldName, #b = #values, #break = 1
end
if #a in ('[b]', '[d]')
select #result = isnull(#result + ' & ', '') + #a + '=' + #b
end
select #result
You can also put all this list into temporary/variable table and do join.
select *
from
(
select T.<yourcolumn>, row_number() over (order by T.<yourcolumn>) as rownum
from <temptable1> as T
) as F
inner join
(
select T.<yourcolumn>, row_number() over (order by T.<yourcolumn>) as rownum
from <temptable2> as T
) as V on V.rownum = F.rownum
Or, even better, you can pass parameters into sp in xml form and not in distinct lists
Try this :
Using XML i'm are trying to spilt the values and storing the result in a table variable
DECLARE #FieldName VARCHAR(MAX),
#values varchar(max)
SET #FieldName = 'a,b,c,d';
SET #values = '5,6,7,8'
SET #FieldName = #FieldName + ',';
SET #values = #values + ',';
DECLARE #X XML
SET #X = CAST('<Item>' + REPLACE(#FieldName, ',', '</Item><Item>') + '</Item>' AS XML)
Declare #X1 XML
Set #X1=CAST('<Position>' + REPLACE(#values, ',', '</Position><Position>') + '</Position>' AS XML)
Declare #FieldSample table
(
FieldName char(1),
rowNum int
)
Declare #valueSample table
(position int,
rowNum int)
Insert into #FieldSample(rowNum,FieldName)
Select * from (
SELECT row_number() over (order by (select 0)) as rowNum, t.value('.', 'char(1)') as field
FROM #x.nodes('/Item') as x(t)
) as a
where a.field !=''
Insert into #valueSample(rowNum,position)
Select * from (
Select row_number() over (order by (select 0)) as rowNum, k.value('.', 'int') as position
from #X1.nodes('/Position') as x1(k)
) as b
where b.position !=0
Basically the last logic you can change it based on how you intend to get the data
Select a.FieldName,b.position from #FieldSample as a
inner join #valueSample as b
on a.rowNum=b.rowNum
where b.position = 6 or b.position =8

How to grab the value of the output parameter in execute sp_executesql?

Please forgive newbie's ignorance!
How do I grab the value of the output parameter in execute sp_executesql?
I can see the output but cannot get to it:
DECLARE #LastActivity nvarchar(100)
DECLARE #LastActivityDate datetime
DECLARE #sql nvarchar(MAX)
DECLARE #RowsToProcess int
DECLARE #CurrentRow int
DECLARE #SelectCol1 nvarchar(100)
DECLARE #SelectCol2 nvarchar(100)
DECLARE #SelectCol3 nvarchar(100)
DECLARE #LastDate TABLE (RowID int not null primary key identity(1,1), col4 nvarchar(MAX), col5 sql_variant)
DECLARE #table1 TABLE (RowID int not null primary key identity(1,1), col1 nvarchar(100),col2 nvarchar(100),col3 nvarchar(100))
INSERT into #table1 (col1,col2,col3)(SELECT t.name AS col1, c.name AS col2, m.Field1 as col3
FROM sys.columns c INNER JOIN
sys.tables t ON c.object_id = t.object_id INNER JOIN
sys.schemas s ON t.schema_id = s.schema_id INNER JOIN
dbo.MERGE_TABLES m ON m.Table_Name=t.name
WHERE c.name LIKE '%[_]DATE%' and m.[Enabled]='Y')
SET #RowsToProcess=##ROWCOUNT
SET #CurrentRow=0
WHILE #CurrentRow<#RowsToProcess
BEGIN
SET #CurrentRow=#CurrentRow+1
SELECT #SelectCol1=col1,#SelectCol2=col2,#SelectCol3=col3 FROM #table1 WHERE RowID=#CurrentRow
SET #sql='SELECT ' + '[dbo].[ConvertToDatetime](MAX(' + #SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
Declare #params as nvarchar(MAX)
Set #params = '#date sql_variant output'
Declare #date as sql_variant;
execute sp_executesql
#sql
,#params
,#date output
Select #date
INSERT into #LastDate VALUES (#sql, #date)
end
select col4,col5 from #LastDate
select col4,col5 from #LastDate gives me the SQL script in clo4 but col5 is empty! I need to store the #date as I still need to get the Max(#date)
Thanx a million.
SET #sql='set #date =('SELECT ' + '[dbo].[ConvertToDatetime](MAX(' +
#SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3
+ ' = ' + '''0722607QZ''' ) '
the above sql gives me error: Incorrect syntax near '.'
SET #sql='set #date =(SELECT [dbo].[ConvertToDatetime](MAX( + #SelectCol2 + ))
FROM #SelectCol1 Where #SelectCol3 ''=0722607QZ'' ) '
The above sql gives the error: Must declare the scalar variable "#SelectCol2"
SET #sql='SELECT ' + #date + '=convert(nvarchar(100), [dbo].[ConvertToDatetime](MAX(' + #SelectCol2 + ')))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
the above produces the error : Implicit conversion from data type sql_variant to nvarchar is not
allowed. Use the CONVERT function to run this query.
SET #sql='SELECT ' + #date + '=convert(nvarchar(MAX),(MAX(' + #SelectCol2 + '))' + ' FROM ' + #SelectCol1 + ' Where ' + #SelectCol3 + ' = ' + '''0722607QZ'''
the above produces no error but all output is NULL, no values.
Your syntax looks ok but you never assign to the output variable #date hence no value.
Instead of;
SET #sql='SELECT ...'
You need;
SET #sql='set #date = (SELECT ...'
Can't you use a better type than sql_variant?