Dynamic Stored Procedure with View name and Date as parameters - sql

I created a stored procedure which takes a view name and date as parameters and checks if there is record for that date in the view. However, I get the following error
'Operand type clash: date is incompatible with int'.
I am hoping that if the record exists that 1 will be returned else 0 will be returned and I can use that to make a decision in another stored procedure.
The code is listed below
CREATE PROCEDURE [dbo].[usr_RecordExist]
-- Add the parameters for the stored procedure here
#ViewName SYSNAME,
#TransDate Date
--<#Param2, sysname, #p2> <Datatype_For_Param2, , int> = <Default_Value_For_Param2, , 0>
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #DATEVARCHAR nvarchar(4000);
SET #DATEVARCHAR = CONVERT(NVARCHAR, #TransDate, 103);
-- Insert statements for procedure here
DECLARE #SQLCommand NVARCHAR(MAX) =
N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName) + 'WHERE TRANSDATE = ' + '' + #DATEVARCHAR + '';
EXECUTE [dbo].[sp_executesql]
#sqlCommand;
END

The expression + '' does nothing, use + '''' to add a single quote.
... + '''' + #DATEVARCHAR + '''';

You are using the right tools but in the wrong way, You should not concatenate parameters but pass them as parameters to the system stored procedure sp_executesql as shown below:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date'
,#TransDate
END
Edit
To get the count in an output parameter you would do the following:
CREATE PROCEDURE [dbo].[usr_RecordExist]
#ViewName SYSNAME,
#TransDate Date,
#Count INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLCommand NVARCHAR(MAX);
SET #SQLCommand = N'SELECT #Count = COUNT(SYMBOL) FROM ' + QUOTENAME(#ViewName)
+ N' WHERE TRANSDATE = #TransDate';
EXECUTE [dbo].[sp_executesql] #sqlCommand
,N'#TransDate Date, #Count INT OUTPUT'
,#TransDate
,#Count OUTPUT
END

Since you used QUOTENAME() for ViewName why not QUOTENAME(#DATEVARCHAR, '''') or QUOTENAME(#DATEVARCHAR, CHAR(39))

Cosmin got it. Although I also noticed you set #DATEVARCHAR to NVARCHAR(4000) even though convert(NVARCHAR without a length defaults to 30.

Related

Is there a way for me to dynamically write a script to add a domain+user to Microsoft SQL server in a stored procedure?

I have to use this exact script below:
ALTER PROCEDURE [dbo].[sp_CheckIfSQLLoginExistsAndCreateLogin]
#SearchDomain NVARCHAR(MAX),
#SearchUsername NVARCHAR(MAX)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(max);
DECLARE #params NVARCHAR(MAX);
IF #SearchUsername != ''
BEGIN
SET #sql = N'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = #Domain\#Username) CREATE LOGIN [#Domain\#Username] FROM WINDOWS';
SET #params = N'#Username NVARCHAR(MAX), #Domain NVARCHAR(MAX)';
END
exec sp_executesql #sql, #params, #Username=#SearchUsername, #Domain=#SearchDomain
END
The problem I am having is that every time this SP is called I get the following error:
Using this data: #SearchDomain = OFFICE and #SearchUsername = BJackson
The problem here is you think SQL is a scripting language; it is not. For example '#Domain' means the literal string '#Domain' not replace the literal string '#Domain' with the value in the variable #Domain.
If you have EXEC sys.sp_executesql N'SELECT '#V1 + #V2';', N'#V1 varchar(30), #V2 varchar(30)','This', 'works'; you don't get the value 'Thisworks' you get the value '#V1 + #V2' Why? Because they are literals.
What you need to do is safely inject your parameters for the object names, and properly parametrise your WHERE:
ALTER PROCEDURE [dbo].[CheckIfSQLLoginExistsAndCreateLogin] --removed prefix
#SearchDomain sysname, --Corrected datatype
#SearchUsername sysname --Corrected datatype
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(max);
DECLARE #Login sysname = CONCAT(#SearchDomain,'\',#SearchUsername);
IF #Login != ''
BEGIN
SET #sql = N'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = #Login) CREATE LOGIN ' + QUOTENAME(Login) + N' FROM WINDOWS';
END
EXEC sys.sp_executesql #sql, N'#Login sysname', #Login;
END;
Here is a simple example:
CREATE OR ALTER PROCEDURE [dbo].[sp_CheckIfSQLLoginExistsAndCreateLogin]
#SearchDomain NVARCHAR(MAX),
#SearchUsername NVARCHAR(MAX)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(max);
DECLARE #params NVARCHAR(MAX);
IF #SearchUsername != ''
BEGIN
SET #sql = N'IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE [name] = '''+ #SearchDomain +'\'+ #SearchUsername +''') CREATE LOGIN [#Domain\#Username] FROM WINDOWS';
SET #params = N'#Username NVARCHAR(MAX), #Domain NVARCHAR(MAX)';
END
exec sp_executesql #sql, #params, #Username=#SearchUsername, #Domain=#SearchDomain
END

Invalid column name error when using QUOTENAME

I have several tables having the same structure. The tables are named by year that is 2001,2002 and so on. I am in need to search a column for a value in each table and get the count for each table.
I have created a stored procedure below but I keep getting an error
Invalid column 'lol'
This is the stored procedure used:
CREATE PROCEDURE [dbo].[CountSP]
#TableName NVARCHAR(128),
#SearchParam NVARCHAR(50),
#SearchInput NVARCHAR(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N'SELECT COUNT('+QUOTENAME(#SearchParam)+') FROM ' + QUOTENAME(#TableName) +'WHERE'+QUOTENAME(#SearchParam)+'LIKE '+QUOTENAME(#SearchInput)+
+ N' SELECT * FROM '+QUOTENAME(#TableName)
EXECUTE sp_executesql #Sql
END
Executing it:
DECLARE #return_value INT
EXEC #return_value = [dbo].[CountSP]
#TableName = N'1999',
#SearchParam = N'USERDESC',
#SearchInput = N'lol'
SELECT 'Return Value' = #return_value
I don't know why you are using LIKE operator there while you don't use wildcards, also use SysName datatype directly for object names.
Create PROCEDURE [dbo].[CountSP]
(
#TableName SysName,
#SearchInput NVARCHAR(50),
#SearchParam SysName
)
AS
SET NOCOUNT ON;
DECLARE #SQL NVARCHAR(MAX) = N'SELECT COUNT(' +
QUOTENAME(#SearchParam) +
N') FROM ' +
QUOTENAME(#TableName) +
N' WHERE ' +
QUOTENAME(#SearchParam) +
N' = ' + --You can change it to LIKE if needed
QUOTENAME(#SearchInput, '''') +
N';';
-- There is no benifits of using LIKE operator there
EXEC sp_executesql #SQL;
Then you can call it as
EXEC [dbo].[CountSP] N'YourTableNameHere', N'SearchInput', N'ColumnName';
This is because it is currently translated to :
SELECT COUNT([USERDESC]) FROM [1999] WHERE [USERDESC] LIKE [lol]
this means that it is comparing the "USERDESC" column with the "lol" column but from what I am understanding lol isn't a column but a value? which means you should lose the QUOTENAME for that variable.
See the documentation here : https://learn.microsoft.com/en-us/sql/t-sql/functions/quotename-transact-sql?view=sql-server-2017
You need to pass your parameter #SearchInput as a parameter to sp_execute:
CREATE PROCEDURE [dbo].[CountSP] #TableName sysname, --This is effectively the same datatype (as sysname is a synonym for nvarchar(128))
#SearchParam sysname, --Have changed this one though
#SearchInput nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql nvarchar(MAX);
SET #Sql = N'SELECT COUNT(' + QUOTENAME(#SearchParam) + N') FROM ' + QUOTENAME(#TableName) + N'WHERE' + QUOTENAME(#SearchParam) + N' LIKE #SearchInput;' + NCHAR(13) + NCHAR(10) +
N'SELECT * FROM ' + QUOTENAME(#TableName);
EXECUTE sp_executesql #SQL, N'#SearchInput nvarchar(200)', #SearchInput;
END;
QUOTENAME, by default, will quote a value in brackets ([]). It does accept a second parameter which can be used to define a different character (for example QUOTENAME(#Value,'()') will wrap the value in parentheses). For what you want though, you want to parametrise the value, not inject (a quoted) value.

Execute stored procedure using openrowset without doing with result sets for every column

When I execute a stored procedure using openrowset, which has dynamic SQL in it, it throws an error
Contains dynamic SQL. Consider using WITH RESULT SETS
An example is as follows.
select output.*
from openrowset ('SQLOLEDB','SERVER=(local);Trusted_Connection=yes;',
'SET FMTONLY OFF;SET NOCOUNT ON; exec storedproc ') as output
Since I have many output parameters, is there a simple way to display all the columns?
Note: I have to use openrowset as the stored procedure is being executed inside a script (R).
I also tried the following but didn't work.
declare #sqlstmt nvarchar(max)
declare #queryout nvarchar(max)
set #queryout = 'storedproc #parameter1=''''D'''', #param2=''''08/19/2018'''', '
set #queryout = '''exec ' + #queryout
set #sqlstmt = N'select outputprod.* from openrowset (''SQLOLEDB'',''SERVER=(local);Trusted_Connection=yes;'' , ' + #queryout + ''') as outputprod'
print(#sqlstmt)
exec (#sqlstmt)
You can use Dynamic Query with full select statement inside varchar variable then execute it with EXEC. see here.
for example
DECLARE #sqlCommand varchar(1000)
DECLARE #columnList varchar(75)
DECLARE #city varchar(75)
SET #columnList = 'CustomerID, ContactName, City'
SET #city = '''London'''
SET #sqlCommand = 'SELECT ' + #columnList + ' FROM customers WHERE City = ' + #city
EXEC (#sqlCommand)
Modify the Stored procedure with output parameter to return the generated query, to avoid dynamic stored procedure being passed.
DECLARE #queryout NVARCHAR(max)
DECLARE #sqlstmt NVARCHAR(max)
EXEC storedproc #queryout OUTPUT
SET #sqlstmt = N'SET FMTONLY OFF;SET NOCOUNT ON;' + #queryout
select output.* from openrowset (
'SQLOLEDB','SERVER=(local);Trusted_Connection=yes;', #sqlstmt) as output

Dynamic SQL Server stored procedure with table name as input param

I have the following stored procedure
CREATE PROCEDURE [dbo].[Insert]
#Service varchar(max),
#TableName varchar(100),
#Query varchar(250),
#Results varchar(max),
#CreatedDate datetime,
#ExpirationDate datetime
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
DECLARE #SQL NVARCHAR(MAX), #ParmDefinition NVARCHAR(MAX)
DECLARE #q1 VARCHAR(MAX), #rez1 VARCHAR(MAX),
#date1 DATETIME, #date2 DATETIME
DECLARE #tablename VARCHAR(MAX) = #Service + '.' + #TableName
SET #SQL = N'if not EXISTS (select #q from ' + #tablename + ' where Query = #q) insert into ' + #tablename + ' values(#q, #rez, #date1, #date2)'
SET #ParmDefinition = N'#q varchar(max), #rez varchar(max),
#date1 datetime, #date2 datetime'
EXECUTE sp_executeSQL -- Dynamic T-SQL
#SQL,
#ParmDefinition,
#q = #Query,
#rez = #Results,
#date1= #CreatedDate,
#date2 = #ExpirationDate
COMMIT TRANSACTION
END
When I try to execute it, it doesn't insert anything, 0 rows
If I execute the code without the stored procedure, like a single query it inserts
Am I missing something?
There are a lot of things you have done in your question which doesnt make any sense to me, Why do you need to declare all these variables inside your procedure.
Yes true you are using parametrised query to protect yourself against sql injection attack, yet you left a hole by concatenating the object names (Table and database name), yes you will need to concatenate them but you can use QUOTENAME() function around them passed parameters and enforce sql server to put square brackets around these parameters and force sql server to treat them as nothing else but object names.
And Selecting a variable in IF EXISTS not make much sense. Select 1 which returns true if a row is found with matching criteria , and if no row is found it will simply insert a row.
Only declare variables that needs to declared, otherwise this make it look like a mess and difficult to debug. As they say Keep it simple :)
Also use appropriate data types for your parameters, #Service I believe is your database name why does it need to be a VARCHAR(MAX) data type, use the data type specific to store Sql Server Object names SYSNAME.
CREATE PROCEDURE [dbo].[Insert]
#Service SYSNAME,
#TableName SYSNAME,
#Query varchar(250),
#Results varchar(max),
#CreatedDate datetime,
#ExpirationDate datetime
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION
DECLARE #SQL NVARCHAR(MAX), #ParmDefinition NVARCHAR(MAX)
SET #SQL = N'IF NOT EXISTS (select 1 from ' + QUOTENAME(#Service) + '.' + QUOTENAME(#TableName)
+ N' where Query = #q) '
+ N'insert into ' + QUOTENAME(#Service) + '.' + QUOTENAME(#TableName)
+ N' values(#q, #rez, #date1, #date2)'
SET #ParmDefinition = N'#q varchar(250), #rez varchar(max),
#date1 datetime, #date2 datetime'
EXECUTE sp_executeSQL #SQL
,#ParmDefinition
,#q = #Query
,#rez = #Results
,#date1= #CreatedDate
,#date2 = #ExpirationDate
COMMIT TRANSACTION
END

Can I use variable in condition statement as value with where condition that test that value

I have the following stored procedure:
ALTER proc [dbo].[insertperoll] #name nvarchar(50) , #snum int , #gnum int
as
DECLARE #value nvarchar(10)
SET #value = 's'+CONVERT(nvarchar(50),#snum)
DECLARE #sqlText nvarchar(1000);
DECLARE #sqlText2 nvarchar(1000);
DECLARE #sqlText3 nvarchar(1000);
declare #g nvarchar(50) = '''g1'''
SET #sqlText = N'SELECT ' + #value + N' FROM dbo.GrideBtable'
SET #sqlText2 = ' where Gnumber = '+#g --here is the problem it error invalid column name -- the #g is value from the table condition
set #sqlText3 = #sqlText+#sqlText2
Exec (#sqlText3) -- here how can i save the result of the exec into varibale
declare #sal nvarchar(50) = #sqlText3
insert employ (name,Snumber,Gnumber,Salary) values(#name,#snum,#gnum,#sal)
QUESTION: How to put in condition variable gets value from the table when i exec it it think that the #g is column but its not its a value from the table to test it so i display one value after the exec the other QUESTION is how to save the result from the exec in variable and then use that value
I'm using SQL Server 2008 (9.0 RTM)
This will be a stored procedure
Thanks in advance
Not sure why you would go through all the loops to insert into the table where you can have a simple insert query like ..
ALTER PROC dbo.[insertperoll] #name nvarchar(50) , #snum int , #gnum int
AS
insert employ (name, Snumber, Gnumber, Salary)
select #name
, #sum
, #gnum
, case when #snum = 1 then s1
when #snum = 2 then s2
when #snum = 3 then s3
when #snum = 4 then s4
end as Salary
from dbo.GrideBtable
where Gnumber = #gnum
If your intent is to have the proc retrieve a salary value from a column determined from the parameter snum and then make an insert into employ using the values passed as parameters and the salary retrieved I think you could refactor your procedure to this:
CREATE proc [dbo].[insertperoll] #name nvarchar(50) , #snum int , #gnum int AS
DECLARE #g NVARCHAR(50) = 'g1'
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'INSERT employ (name,Snumber,Gnumber,Salary) '
SET #sql += N'SELECT ' + QUOTENAME(#name, '''')
SET #sql += N', ' + CAST(#snum AS NVARCHAR(50))
SET #sql += N', ' + CAST(#gnum AS NVARCHAR(50))
SET #sql += N', s' + CAST(#snum AS NVARCHAR(50))
SET #sql += N' FROM dbo.GrideBtable'
SET #sql += N' WHERE Gnumber = ' + QUOTENAME(#g, '''')
EXEC (#sql)
Of course you could add the #g variable to the procedure parameters instead of having it hard coded in the procedure and call it as:
EXEC insertperoll #name='john', #snum=10, #gnum=100, #g='g1'
Sample SQL Fiddle (with some assumptions made about table structure)
You could do this using sp_executesql instead of exec() since this will allow you to use parameters, you can use an output parameter to get the value from the query:
DECLARE #SQL NVARCHAR(MAX) = N'SELECT #val = ' + CONVERT(NVARCHAR(10),#snum) +
N' FROM dbo.GrideBtable WHERE Gnumber = #G1';
DECLARE #val INT; -- NOT SURE OF DATATYPE REQUIRED
EXECUTE sp_executesql #SQL, N'#G1 VARCHAR(20), #val INT OUT', 'G1', #val OUT;