Check the role of a user in a database - sql-server

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;

Related

SQL Server Dynamic creation of global temp table and insert data issue

My requirement is to create a global temporary table and store data there which I would access later. I give a dynamic name to my global temporary table and getting error that
Invalid object name '##Tmp1_84'.
84 is #SPID
here is my script. please have a look and tell me what to rectify in code to get rid of runtime error Invalid object name '##Tmp1_84'
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
DECLARE #SPID VARCHAR(MAX)
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR)
SET #SQL = N'SELECT * INTO ##Tmp1_'+#SPID+' FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = '''+#Ticker+''' AND ClientCode = '''+#ClientCode+''' AND GroupName='''+#GroupName+''') f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X';
--EXEC sys.sp_executesql #SQL
EXEC(#SQL)
EXEC('select * from ##Tmp1_'+#SPID+' ORDER BY Broker')
EXEC('DROP TABLE IF EXISTS ##Tmp1_'+#SPID)
George has mentioned why your attempt doesn't work in their answer, so I'm not going to touch on that.
I'm instead going to fix the problem, which I touch on in my comments. In truth, there is no need for a (global) temporary table, you just SELECT ... INTO it and then SELECT from it; you make no further transformations making it pointless. AS such you could just SELECT the data in the first place, no temporary table needed.
I also fix your injection issue; this is a fatal flaw. Dynamic SQL accepts parameters and you using them is a must; not using parameters and instead using injection opens you up to all sorts of errors and security issues.
I can't test this, but I suspect this will work. If not, use your best friend to debug and propagate fixes to the dynamic SQL on any bits I've missed (or provide code we can run above with an MRE).
CREATE Proc USP_GetValuationValue
(
#Ticker VARCHAR(10),
#ClientCode VARCHAR(10),
#GroupName VARCHAR(10)
)
AS
BEGIN
DECLARE #SPID VARCHAR(MAX), --Is this even used now?
#SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SPID=CAST(##SPID AS VARCHAR);
SET #SQL = N'SELECT * FROM (SELECT min(id) ID,f.ticker,f.ClientCode,f.GroupName,f.RecOrder,' + STUFF((SELECT N',' + #CRLF + N' ' +
N'MAX(CASE FieldName WHEN ' + QUOTENAME(FieldName,'''') + N' THEN FieldValue END) AS ' + QUOTENAME(FieldName)
FROM tblValuationSubGroup g
WHERE ticker=#Ticker AND ClientCode=#ClientCode AND GroupName=#GroupName
GROUP BY FieldName
ORDER BY MIN(FieldOrder)
FOR XML PATH(''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,10,N'') + #CRLF +
N'FROM (select * from tblValuationFieldValue' + #CRLF +
N'WHERE Ticker = #Ticker AND ClientCode = #ClientCode AND GroupName= #GroupName) f' + #CRLF +
N'GROUP BY f.ticker,f.ClientCode,f.GroupName,f.RecOrder) X' + #CRLF +
N'ORDER Y Broker;';
--PRINT #SQL; --YOur best friend
--EXEC sys.sp_executesql #SQL --Why did you comment this out? This is correct!
EXEC sys.sp_executesql #SQL, N'#Ticker varchar(10), #ClientCode varchar(10), #GroupName varchar(10)', #Ticker, #ClientCode, #GroupName
END;

Select same table name from all databases in SQL Server

I have this dynamic query, that is union from all my databases (that is start with "Db") the same table ("Tbl_SameTable").
DECLARE #tableName nvarchar(256) = 'Tbl_SameTable'
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + CASE WHEN #sql <> '' THEN 'UNION ALL ' ELSE '' END
+ 'SELECT * FROM [' + dbs.name + ']..[' + #tableName + '] '
FROM sys.sysdatabases dbs
WHERE left(dbs.name,2) = 'Db'
EXEC(#sql)
I want to add two things to this query:
Add a column of database name
Assign the query result to a "temp table" or "table variable"
I do not know if this is important but, the "Tbl_SameTable" is a 5 column table (int, nvarchar, int,nvarchar,nvarchar)
This is untested, however, you'll want something like this. As this is pseudo SQL, you'll need to replace {Columns} with the actual names (not *) for it to work. For the CREATE TABLE you'll need to define the data type of said columns too.
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
CREATE TABLE #Temp (DatabaseName sysname,
{Columns});
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF + N'UNION ALL' + #CRLF +
N'SELECT N' + QUOTENAME(d.[name],'''') + N' AS DatabaseName, {Columns}' + #CRLF +
N'FROM ' + QUOTENAME(d.[name]) + N'.' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName)
FROM sys.databases d
WHERE d.[name] LIKE 'Db%'
ORDER BY database_id
FOR XML PATH(N''),TYPE).value('(./text())[1]','nvarchar(MAX)'),1,13, N'') + N';'
--PRINT #SQL; --Your Best Friend
INSERT INTO #Temp(DatabaseName, {Columns})
EXEC sys.sp_executesql #SQL;
And, of course, if it doesn't work your best friend will be there to help you out.
How about using sp_MSforeachdb and adding the results to another table?
DROP TABLE IF EXISTS #tmp
CREATE TABLE #tmp (col1 INT, col2 INT,...)
DECLARE #command varchar(1000)
SELECT #command = 'IF ''?'' LIKE ''Db%'' BEGIN USE ?
EXEC(''INSERT INTO #tmp (col1, col2,...) SELECT col1, col2,... from Tbl_SameTable'') END'
EXEC sp_MSforeachdb #command
SELECT * FROM #tmp

Dynamic Where Condition Based on Parameter Value in sql

I am doing a search Criteria Form in that i have various types of search options,Instead of doing a If condition in code based on the control select, we can check by the parameter values and update the where condition
Declare #Amount varchar(Max);
SET #Amount= 'and Amount=500';
Select Processor, [Stmt Date], Description, Amount, [Allocation Date],
Entity, URN, [Customer Acc], [Invoice Number],Lamount, DumpEntity as [Received Entity]
from tbl_Employee_Salary
WHERE 1=1 +CONVERT(int,CASE when #Amount IS NOT null then #Amount Else ' ' END)
Conversion failed when converting the varchar value 'and Amount=500'
to data type int.
Warning: This is completely untested.
I hate these types of queries, but what you are after is something like this. I'm not going to explain it, sorry, but it's up to you to understand the below and support it yourself. This is known as a catch-all query or a kitchen sink query.
--All below datatypes are ASSUMED
--Declare a variable for every column (you won't need all of these if they aren't going to all be used)
DECLARE #Processor varchar(50),
#StmtDate date,
#Description varchar(100),
#Amount int,
#AllocationDate date,
#Entity int,
#URN int,
#CustomerAcc int,
#InvoiceNumber int,
#Lamount decimal(10,2),
#DumpEntity varbinary(8);
--Set your values here (I assume this will actually be an SP or something)
SET #Amount = 500;
--Create the initial SQL statement
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT Processor,' + NCHAR(13) + NCHAR(10) +
N' [Stmt Date],' + NCHAR(13) + NCHAR(10) +
N' Description,' + NCHAR(13) + NCHAR(10) +
N' Amount,' + NCHAR(13) + NCHAR(10) +
N' [Allocation Date],' + NCHAR(13) + NCHAR(10) +
N' Entity,' + NCHAR(13) + NCHAR(10) +
N' URN,' + NCHAR(13) + NCHAR(10) +
N' [Customer Acc],' + NCHAR(13) + NCHAR(10) +
N' [Invoice Number],' + NCHAR(13) + NCHAR(10) +
N' Lamount,' + NCHAR(13) + NCHAR(10) +
N' DumpEntity AS [Received Entity]' + NCHAR(13) + NCHAR(10) +
N'FROM tbl_Employee_Salary'
--Now you need to start creating the WHERE
DECLARE #Where nvarchar(MAX);
SET #Where = N'WHERE ' +
NULLIF(STUFF(CASE WHEN #Processor IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND Processor = #Processor' ELSE N'' END +
CASE WHEN #StmtDate IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND [Stmt Date] = #StmtDate' ELSE N'' END +
CASE WHEN #Description IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND [Description] = #Description' ELSE N'' END +
CASE WHEN #Amount IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND Amount = #Amount' ELSE N'' END +
CASE WHEN #AllocationDate IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND [Allocation Date] = #AllocationDate' ELSE N'' END +
CASE WHEN #Entity IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND Entity = #Entity' ELSE N'' END +
CASE WHEN #URN IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND URN = #URN' ELSE N'' END +
CASE WHEN #CustomerAcc IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND [Customer Acc] = #CustomerAcc' ELSE N'' END +
CASE WHEN #InvoiceNumber IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND [Invoice Number] = #InvoiceNumber' ELSE N'' END +
CASE WHEN #Lamount IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND Lamount = #Lamountcessor' ELSE N'' END +
CASE WHEN #DumpEntity IS NOT NULL THEN NCHAR(13) + NCHAR(10) + N' AND DumpEntity = #DumpEntity' ELSE N'' END,1,8,N''),N'');
--Now add the 2 values together
SET #SQL = #SQL + ISNULL(#Where,N'') + N';';
--Create the parameter string
DECLARE #Params nvarchar(MAX);
--All following datatypes are ASSUMED
SET #Params = N'#Processor varchar(50),#StmtDate date,#Description varchar(100),#Amount int,#AllocationDate date, #Entity int,#URN int,#CustomerAcc int,#InvoiceNumber int,#Lamount decimal(10,2),#DumpEntity varbinary(8)'
PRINT #SQL; --Your debugging best friend
--And execute the dynamic SQL
EXEC sp_executesql #SQL, #Params, #Processor, #StmtDate, #Description ,#Amount, #AllocationDate ,#Entity, #URN , #CustomerAcc, #InvoiceNumber, #Lamount, #DumpEntity;
GO
Good luck!

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;

Database Name as Variable -Dynamic SQL inside stored procedure

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.

Resources