Database Name as Variable -Dynamic SQL inside stored procedure - sql-server

I'm having a hard time to solve this issue. My project requirement need to be able to use database name as a parameter. I thought it will be easy since in a stored procedure, we can use it like [dbname].[dbo].tblname. But when I replace the dbname with variable, it's not working.
So after sometime googling, I decided to go with dynamic SQL in the stored procedure. But, currently I'm having a hard time to debug this part. On my program it raise a syntax error. I'm hoping if someone could give me a hint, or a better idea for my project. Thanks in advance!
SELECT #DBName=DBName FROM Client WHERE ClientCode = #ClientCode
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * FROM ' + #DBName +
N'.[dbo].Users AS A INNER JOIN'+ #DBName +
N'.[dbo].UserRoles AS B On B.RoleCode = A.UserRole INNER JOIN ' + #DBName +
N'.[dbo].Branch AS C On C.BranchCode = A.BranchCode WHERE Username= ' + #UserName +
N' AND Password = ' + #Password
Declare #ParamDefinition AS NVarchar(2000)
Set #ParamDefinition = N' #ClientCode VARCHAR(20),' +
N' #UserName Varchar(15),' +
N' #Password NVARCHAR(200)'
exec sp_executesql #SQL,#ParamDefinition

use QUOTENAME in your dynamic SQL, this is very important to prevent SQL Injection
username and password can and should stay parameters
pass in the declared parameters
So like this:
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * FROM ' + QUOTENAME(#DBName) +
N'.[dbo].Users AS A INNER JOIN '+ QUOTENAME(#DBName) +
N'.[dbo].UserRoles AS B On B.RoleCode = A.UserRole INNER JOIN ' + QUOTENAME(#DBName) +
N'.[dbo].Branch AS C On C.BranchCode = A.BranchCode WHERE Username= #UserName AND Password = #Password'
exec sp_executesql #SQL, N'#UserName VARCHAR(15), #Password NVARCHAR(200)', #UserName, #Password
Finally: never store passwords in the database. Use instead a salted hash.

You are missing a space on the second line of your dynamic SQL generation.
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * FROM ' + #DBName +
N'.[dbo].Users AS A INNER JOIN ' + #DBName + -- space added
N'.[dbo].UserRoles AS B On B.RoleCode = A.UserRole INNER JOIN ' + #DBName +
N'.[dbo].Branch AS C On C.BranchCode = A.BranchCode WHERE Username= ' + #UserName +
N' AND Password = ' + #Password

I rewrite like this so is easy to read
DECLARE #SQL NVARCHAR(MAX) = N'SELECT * FROM ' +
#DBName + N'.[dbo].Users AS A INNER JOIN ' +
^^^ space add here
#DBName + N'.[dbo].UserRoles AS B On B.RoleCode = A.UserRole INNER JOIN ' +
#DBName + N'.[dbo].Branch AS C On C.BranchCode = A.BranchCode ' +
N' WHERE Username= ' + #UserName +
N' AND Password = ' + #Password
and like Joe R say, was a missing space.

Related

Check the role of a user in a database

So, the question is:
How can I set up a query to tell me if a certain user has a certain role in a database?
I came up with this:
SELECT #sql = 'USE [' + #nameDB + '];'
EXEC sp_sqlexec #sql
if (select COUNT(*) from sys.database_principals princ left join sys.database_permissions perm on perm.grantee_principal_id = princ.principal_id where name = 'User') = 0
BEGIN
SELECT #sql = 'USE [' + #nameDB + '];'
EXEC sp_sqlexec #sql
CREATE USER [User] FOR LOGIN [User]
ALTER ROLE [db_datareader] ADD MEMBER [User]
END
I want to know if "User" is a db_datareader for a series of databases. If it's not, grant him the role. The problem is that the USE doesn't point me on the right database, but sticks on master, so the IF block is never executed. Any suggestions?
This should get you the result you are after, provided the name of user is actually hard coded to [User]. If it isn't you'll need to further parametrise your query and quote the relevant object names:
DECLARE #nameDB sysname = N'YourDB';
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'USE ' + QUOTENAME(#nameDB) + N';' + NCHAR(13) + NCHAR(10) +
N'IF (DATABASE_PRINCIPAL_ID(N''User'') IS NULL) BEGIN' + NCHAR(13) + NCHAR(10) +
N' CREATE USER [User] FOR LOGIN [User];' + NCHAR(13) + NCHAR(10) +
N' ALTER ROLE db_datareader ADD MEMBER [User];' + NCHAR(13) + NCHAR(10) +
N'END;';
PRINT #SQL; --Your best friend.
EXEC sp_executesql #SQL;
Edit: USER_ID is deprecated, I should know better.
Example of parametrised version for the User:
DECLARE #nameDB sysname = N'YourDB';
DECLARE #User sysname = N'User'
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'USE ' + QUOTENAME(#nameDB) + N';' + NCHAR(13) + NCHAR(10) +
N'IF (DATABASE_PRINCIPAL_ID(#User) IS NULL BEGIN' + NCHAR(13) + NCHAR(10) +
N' CREATE USER ' + QUOTENAME(#User) + N' FOR LOGIN ' + QUOTENAME(#User) + N';' + NCHAR(13) + NCHAR(10) +
N' ALTER ROLE db_datareader ADD MEMBER ' + QUOTENAME(#User) + N';' + NCHAR(13) + NCHAR(10) +
N'END;';
EXEC sp_executesql #SQL, N'#User sysname', #User = #User;

How to SELECT and UNION from a group of tables in schema in SQL Server 2008 R2 using a variable to define the database

This is a progression from the question asked here: How to SELECT and UNION from a group of Tables in the schema in SQL Server 2008 R2
I would like to do very much the same thing and the answer given by MarkD works perfectly for the database I am currently working with. Although admittedly I'd like to understand exactly how. How does the query below build the union query from the list of tables returned by the information_schema?
DECLARE #Select_Clause varchar(600) = N'SELECT [Patient_Number] AS [ID number]
,[Attendance Date] AS [Date Seen]
,[Attendance_Type] AS [New/Follow up]
,[Episode Type] AS [Patient Type]
,[Local Authority District]
,Postcode, N''Shaw'' AS Clinic '
,#Where_Clause varchar(100) = N' WHERE [EPISODE TYPE] LIKE N''HIV'''
,#Union_Clause varchar(100) = N' UNION ALL '
,#Query nvarchar(max) = N''
,#RawDataBase varchar(50) = N'BHT_1819_RawData'
,#Schema varchar(50) = N'HIVGUM'
,#Table_Count tinyint;
DECLARE #Table_Count_def nvarchar(100) = N'#TableSchema varchar(50)
,#Table_CountOUT tinyint OUTPUT'
,#Start_Position int = LEN(REPLACE(#Select_Clause, N' ', N'-'))
,#Length int;
SET #Query = N'SELECT #Table_CountOUT = COUNT(*) FROM ' + #RawDataBase +
N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE #TableSchema';
EXEC sp_executesql #query, #Table_Count_def, #TableSchema=#Schema,
#Table_CountOUT=#Table_Count OUTPUT;
SET #Query = N'';
IF #Table_Count > 0
Begin
IF OBJECT_ID(N'dbo.HIV_Cumulative', N'U') is not null
DROP TABLE dbo.HIV_Cumulative;
SELECT #Query = #Query + #Select_Clause + N' FROM ' + #RawDataBase +
N'.HIVGUM.' + TABLE_NAME + #Where_Clause + #Union_Clause
FROM BHT_1819_RawData.INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA LIKE #Schema;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position+1, #Length)
SET #Query = #Select_Clause + N' INTO BHT_SLR..HIV_Cumulative ' + #QUERY
+ #Where_Clause;
EXEC sp_executesql #Query
End
ELSE
PRINT N'No tables present in database ' + #RawDataBase + N' for Schema ' +
#Schema + N'. You must import source data first.';
The added complication is that I am querying the tables on a separate DB - currently BHT_1819_RawData - so have hard coded the database where it queries the information_schema. What I would really like to do is to specify the separate database using a variable. So that it can be reconfigured to extract from BHT_1920_RawData. I am fairly familiar with exec and sp_executesql, but have only occasionally used output parameters so am not sure what is required here. The attempts that I have made haven't worked. Once I have got this right, I will need to create several other similar scripts that work on the same principle.
Once I realised what needed to happen, I went through some trial and error and came up with a solution:
SET #ParmDef = N'#QueryOut nvarchar(2500) OUTPUT';
SET #sql_string = N'SELECT #QueryOut = #QueryOut + N'''
+ #Select_Clause + ' FROM '
+ #RawDataBase
+ N'.[' + #Schema + N'].'' + TABLE_NAME + N'' '
+ #Where_Clause
+ #Union_Clause
+ N''' FROM '
+ #RawDataBase
+ N'.INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA LIKE '''
+ #Schema
+ N''' AND TABLE_NAME NOT LIKE N''%_YTD%''';
EXEC sp_executesql #sql_string, #ParmDef, #QueryOut=#Query OUTPUT;
SET #Length = LEN(REPLACE(#query, N' ', N'-')) - #Start_Position -
LEN(REPLACE(#Where_Clause + #Union_Clause, N' ', N'-'));
SELECT #Query = SUBSTRING(#QUERY , #Start_Position, #Length+1);
SET #Query = REPLACE(#Select_Clause, N'''''', '''') + N' INTO ' + #New_Table + N' ' +
#QUERY + REPLACE(#Where_Clause, N'''''', '''');
EXEC sp_executesql #Query;

Variable Syntax for server name

I'm trying to pass the server name as a variable in a Exec(#sqlstring) stored procedure. However I cannot get the correct syntax and I'm pretty much all out of ideas.
The server name is UK-DATA-SQL-P01 or UK-DATA-SQL-P02 which is why I need to use it as a variable so the user can select.
The syntax I'm trying to use to pass the variable is:
INNER JOIN ' + #ServerName + '.' + #DBName + '.[dbo].
I'm sure that this is simple but any help would be much appreciated.
Thanks in advance
You can also use OPENDATASOURCE. Something like this :
INNER JOIN OPENDATASOURCE('SQLOLEDB','Data Source = ServerName; User ID = Username; Password = Password').[DatabaseName].[DatabaseOwner].[TableName] Z ON...
PS. You'd need to enable Ad Hoc Distributed Queries for this to work.
How To Enable Ad Hoc Distributed Queries - For Reference
Hope it helps.
I've been using Synonyms to achieve similar:
Create Procedure ReturnServerDbTable (#Server as nvarchar(50), #DB as nvarchar(50), #Table as nvarchar(50))
as
begin
declare #sql as nvarchar(500)
set #sql = 'CREATE SYNONYM ServerDBTable FOR ' + #Server + '.' + #db + '.dbo.' + #table
exec(#sql)
select * from ServerDBTable
DROP SYNONYM ServerDBTable
end
Use something like this just a sample then you can extend
create proc sampleProc
#tableName varchar(100) ,
#serverName varchar(100)
as
begin
declare #tableName1 varchar(100)
set #tableName1='Sales'
declare #fullname varchar(500)
set #fullname = #serverName+'.'+ #tableName
print (#fullname)
DECLARE #SQLQuery AS NVARCHAR(500)
SET #SQLQuery = ' SELECT * FROM '+ #fullname + ' inner join ' + #tableName1 + ' on id=custid'
print (#SQLQuery)
--EXECUTE(#SQLQuery)
end
--usage like this
exec sampleProc 'server','customer'
declare #tableName varchar(100)
set #tableName='Customers'
-- this is previous answer
declare #tableName1 varchar(100)
set #tableName1='Sales'
DECLARE #SQLQuery AS NVARCHAR(500)
SET #SQLQuery = ' SELECT * FROM '+ #tableName + ' inner join ' + #tableName1 + ' on id=custid'
print (#SQLQuery)
EXECUTE(#SQLQuery)

Using EXEC to create stored procedure to run update string column in SQL Server 2008

I have the following modification of my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spTMSA_Test_Run]
#TableName nvarchar(200) = 'MyTable',
#Parent int = 1145,
#Name nvarchar(100) = '''Test''',
#KPI nvarchar(max) = '''Test''',
#IDCount int = 1137
AS
BEGIN
EXEC('UPDATE ' + #TableName + ' SET Parent = ' + #Parent + ', Name = ' + #Name + ' , KPI = ' + #KPI + ' WHERE IDCount = ' + #IDCount)
END
This procedure is executed successfully if I gave ''' before and after the string value. In case I left ' before and after the string value it will cause error.
Please help me find the reason why and solution as well. Thanks
This procedure is an open door for SQL injection attacks.
Unless you have a really good reason why you need it to create dynamic SQL, I would suggest avoiding it.
If you can't avoid using dynamic sql, the least you can do is to use quotename to keep your procedure a little safer.
As to the problem you state in your question - just move the ''' to the query body:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spTMSA_Test_Run]
#TableName nvarchar(200) = 'MyTable',
#Parent int = 1145,
#Name nvarchar(100) = 'Test',
#KPI nvarchar(max) = 'Test',
#IDCount int = 1137
AS
BEGIN
EXEC('UPDATE QUOTENAME(' + #TableName + ')
SET Parent = ' + #Parent + ',
Name = ''' + #Name + ''' ,
KPI = ''' + #KPI + '''
WHERE IDCount = ' + #IDCount)
END
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spTMSA_Test_Run]
#TableName nvarchar(200) = 'MyTable',
#Parent int = 1145,
#Name nvarchar(100) = '''Test''',
#KPI nvarchar(max) = '''Test''',
#IDCount int = 1137
AS
BEGIN
  declare #sql nvarchar(4000)
  set #sql='UPDATE ' + #TableName + ' SET Parent = ' + #Parent + ', Name = ' + #Name + ' , KPI = ' + #KPI + ' WHERE IDCount = ' + #IDCount
  print #sql --find reason in the sql statement
EXEC(#sql)
END
I suggest to use sp_executesql and CAST int to nvarchar before executing query and QUOTENAME the #tablename.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spTMSA_Test_Run]
#TableName nvarchar(200),
#Parent int,
#Name nvarchar(100),
#KPI nvarchar(max),
#IDCount int
AS
BEGIN
DECLARE #sql nvarchar(max)
SELECT #sql = '
UPDATE ' + QUOTENAME(#TableName) + '
SET Parent = ' + CAST(#Parent as nvarchar(10))+ ',
Name = ''' + #Name + ''',
KPI = ''' + #KPI + '''
WHERE IDCount = ' + CAST(#IDCount as nvarchar(10)) + ';'
EXEC sp_executesql #sql
END

How to create a table on a linked server with a variable server name?

I've seen some solutions on how to create tables on linked servers using EXEC('some sql') AT [LINKED_SERVER_NAME] and they work manually.
I can use DML queries such as
EXEC ( 'select * from [' + #DbServerName + '].[' + #DbName + '].dbo.someTable' )
how can I do something similar for DDL queries like
EXEC ( 'CREATE TABLE [' + #DbServerName + '].[' + #DbName + '].dbo.someTable ( id int null) ' )
I've toyed around with select * from openquery(linkedservername, query) and exec(sql) AT [linkedservername], but every time I try to make the server name a variable it fails for me.
I can run all these commands manually at in Query Analyzer, but whenever I try to make the linked server name a variable they fail for me. What I'm trying to do is something like this...
DECLARE #LinkedServerName nvarchar(100)
DECLARE #LinkedDbName nvarchar(100)
SET #LinkedServerName = 'SVR2'
SET #LinkedDbName = 'DB2'
DECLARE #DDL_QUERY nvarchar(1000)
SET #DDL_QUERY = 'CREATE TABLE [' + #LinkedDbName + '].dbo.T1 ( id int null )'
-- Current failed ideas
EXEC( #DDL_QUERY ) AT #LinkedServerName
SELECT * FROM OPENQUERY(#LinkedServerName, #DDL_QUERY)
EXEC( 'CREATE TABLE [' + #LinkedServerName + '].[' + #LinkedDbName + '].dbo.T1( id int null )'
Is it possible to dynamically create a table when the linked server name, and database name on that linked server are both declared variables?
Assuming the linked server is also SQL Server (or possibly Sybase):
DECLARE #table NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #table = N'CREATE TABLE dbo.T1(id INT NULL);';
SET #sql = N'EXEC ' + QUOTENAME(#LinkedServerName) + N'.'
+ QUOTENAME(#LinkedDbName) + N'.sys.sp_executesql #table;';
EXEC sys.sp_executesql #sql, N'#table NVARCHAR(MAX)', #table;
Slightly less verbose syntax:
DECLARE #sql NVARCHAR(MAX), #exec NVARCHAR(MAX);
SET #sql = N'CREATE TABLE dbo.T1(id INT NULL);';
SET #exec = QUOTENAME(#LinkedServerName) + N'.'
+ QUOTENAME(#LinkedDbName) + N'.sys.sp_executesql';
EXEC #exec #sql;

Resources