All,
I am trying to script out a database in SQL Server Management Studio 2005.
In my database users belong to different roles.
Unfortunately I can't find how to script out the relationship between users and roles.
Thanks,
M
The role membership is stored in sys.database_role_members:
select u.name as UserName,
r.Name as RoleName
from sys.database_principals u
join sys.database_role_members m on u.principal_id = m.member_principal_id
join sys.database_principals r on m.role_principal_id = p.principal_id;
User may still get additional privileges by their server fixed roles membership, which is stored in sys.server_role_memebers and needs to be joined with sys.server_principals.
Here's some code we use to first check if a user is already mapped to a role, and if not, to do the mapping. You should be able to take the TSQL at the top that uses database_prinicipals and database_role_members and use that to extract out the relationships you have in your databaase.
SELECT #sql = ' IF EXISTS (SELECT * FROM ' + #DatabaseName + '.sys.database_principals a
JOIN ' + #DatabaseName + '.sys.database_role_members b ON a.principal_id = b.role_principal_id
JOIN ' + #DatabaseName + '.sys.database_principals c ON b.member_principal_id = c.principal_id
WHERE a.Type = ''R'' AND a.Name = ''' + #CurrentDbRole + '''
AND c.type IN ( ''U'', ''S'') AND c.name = ''' + #MappedUser + ''')'
+ ' BEGIN
PRINT '''';
PRINT N''The [' + #MappedUser + '] user is already a member of the ['
+ #CurrentDbRole + '] role in the [' + #DatabaseName + '] database. Skipping Role Member creation.'';
PRINT '''';
END
ELSE
BEGIN
PRINT '''';
PRINT N''Adding the [' + #MappedUser + '] database user as member of the [' + #CurrentDbRole
+ '] role in the [' + #DatabaseName + '] database... '';
PRINT '''';
USE ' + #DatabaseName +';
EXECUTE sp_addrolemember [' + #CurrentDbRole + '], [' + #MappedUser + '];
PRINT '''';
PRINT ''Completed adding the user to the role.'';
PRINT '''';
END; ';
Related
This is the query I have now that can return results for one table at a time.
use *database_name*
select
'*dbo.table_name*' as Table_,
min(*primarykey_columnname*) Min_,
max(*primarykey_columnname*) Max_,
count(*) Count_
from *dbo.table_name*
I have this requirement in both SQL Server and PostgreSQL. It would be great help if you can help in solution for any of them.
You must do this with dynamic SQL. Assum that ALL TABLES have a unique column in the key definition...
For MS SQL Server :
DECLARE #SQL NVARCHAR(max) = N''
SELECT #SQL = #SQL + N'SELECT ''' + KCU.TABLE_SCHEMA + '.' + KCU.TABLE_NAME + ''' AS TBL, '
+ '''' + KCU.COLUMN_NAME + ''' AS PK, MIN("' + KCU.COLUMN_NAME + '") AS MIN_PK, '
+ 'MAX("' + KCU.COLUMN_NAME + '") AS MAX_PK, '
+ 'COUNT("' + KCU.COLUMN_NAME + '") AS NBR_PK FROM "' + KCU.TABLE_SCHEMA + '"."' + KCU.TABLE_NAME + '" UNION ALL '
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
ON TC.CONSTRAINT_SCHEMA = KCU.CONSTRAINT_SCHEMA AND TC.CONSTRAINT_NAME = KCU.CONSTRAINT_NAME
WHERE TC.CONSTRAINT_TYPE = 'PRIMARY KEY';
SET #SQL = LEFT(#SQL, LEN(#SQL) - 10);
EXEC (#SQL);
Esay to adapt to PG... But will be much verbose !
I have a role, it has some select perms on various tables. I would like to remove all the select permissions that the role has across all tables. Eg,
revoke all from my_role_name;
But this doesn't seem to work. How can I do this?
I just had the need to do this.
You can use something along the lines of the below
DECLARE #RevokeScript NVARCHAR(MAX);
DECLARE #PrincipalName SYSNAME = 'my_role_name'
SELECT #RevokeScript = STRING_AGG(CAST('REVOKE ' + permission_name
+ CASE class_desc
WHEN 'OBJECT_OR_COLUMN' THEN ' ON ' + QUOTENAME(OBJECT_SCHEMA_NAME(major_id)) + '.' + QUOTENAME(OBJECT_NAME(major_id))
WHEN 'SCHEMA' THEN ' ON SCHEMA::' + SCHEMA_NAME(major_id)
WHEN 'DATABASE' THEN ''
END
+ ' TO ' + QUOTENAME(#PrincipalName) COLLATE SQL_Latin1_General_CP1_CI_AS AS NVARCHAR(MAX)), ';')
FROM sys.database_permissions AS pe
WHERE pe.grantee_principal_id = DATABASE_PRINCIPAL_ID (#PrincipalName);
PRINT #RevokeScript
EXEC (#RevokeScript)
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;
I am trying to run a query to drop server logins coming from windows and recreate them. I run this query to drop logins and it works fine
USE master
SELECT
'IF EXISTS (SELECT * FROM sys.server_principals WHERE name = ' +
'''' + [name] + '''' + ')' +
' BEGIN DROP LOGIN ' + QUOTENAME([name]) + ' END; ' +
'CREATE LOGIN ' + QUOTENAME([name]) + ' FROM WINDOWS; '
FROM sys.server_principals
WHERE type_desc IN ('WINDOWS_GROUP' , 'WINDOWS_LOGIN')
But the following query
USE master
SELECT
'ALTER LOGIN ' + QUOTENAME([name]) + ' WITH DEFAULT_DATABASE = ' +
spr.default_database_name
+ 'GRANT ' + spe.permission_name + ' to ' + QUOTENAME([name])
+ CASE WHEN spr.default_database_name IS NOT NULL
THEN ' WITH DEFAULT DATABASE = ' + spr.default_database_name
ELSE
''
END
FROM sys.server_principals spr INNER JOIN
sys.server_permissions spe ON spr.principal_id = spe.grantee_principal_id
WHERE spr.type_desc IN ('WINDOWS_GROUP', 'WINDOWS_LOGIN')
is returning this error
Cannot resolve collation conflict for column 1 in SELECT statement.
So when I investigated the issue it is coming from the sysname column default_database_name
The query ran when I used spr.default_database_name COLLATE Latin1_General_CI_AI
My three parts question is: Did I get the problem because of a row or because the column data type is sysname?. Is COLLATE Latin1_General_CI_AI the correct syntax for this scenario?.
when I run this query
SELECT name, description
FROM sys.fn_helpcollations()
The results are showing many collations for different languages. So how did I get this error for a completely English database.
Edit:
I ran these two queries to check the collation property for database and server
SELECT DATABASEPROPERTYEX(N'master', N'Collation')
SELECT SERVERPROPERTY(N'Collation')
and the result in both of them was
SQL_Latin1_General_CP1_CI_AS
It may help to bring the default collate for database and server instance, changing the query as shown below:
SELECT
'ALTER LOGIN ' + QUOTENAME([name]) + ' WITH DEFAULT_DATABASE = ' +
spr.default_database_name COLLATE database_default
+ 'GRANT ' + spe.permission_name + ' to ' + QUOTENAME([name])
+ CASE WHEN spr.default_database_name IS NOT NULL
THEN ' WITH DEFAULT DATABASE = ' + spr.default_database_name COLLATE database_default
ELSE
''
END
FROM sys.server_principals spr INNER JOIN
sys.server_permissions spe ON spr.principal_id = spe.grantee_principal_id
WHERE spr.type_desc IN ('WINDOWS_GROUP', 'WINDOWS_LOGIN')
It looks like someone at MS has fixed the collation on sys.server_permissions.state_desc as Latin1_General_CI_AS_KS_WS
I've tried on a number of instances (SQL 2016) and found the same problem. Putting "collate database_default" works.
Solution for your question: Specify collation for two columns where you CONCAT
USE master
GO
SELECT
'ALTER LOGIN ' + QUOTENAME([name]) COLLATE SQL_Latin1_General_CP1_CI_AS +
' WITH DEFAULT_DATABASE = ' +
spr.default_database_name COLLATE SQL_Latin1_General_CP1_CI_AS
+ 'GRANT ' + spe.permission_name + ' to ' + QUOTENAME([name])
+ CASE WHEN spr.default_database_name IS NOT NULL
THEN ' WITH DEFAULT DATABASE = ' + spr.default_database_name
ELSE
''
END
FROM sys.server_principals spr INNER JOIN
sys.server_permissions spe ON spr.principal_id = spe.grantee_principal_id
WHERE spr.type_desc IN ('WINDOWS_GROUP', 'WINDOWS_LOGIN')
I am in a situation where I must update an existing database structure from varchar to nvarchar using a script. Since this script is run everytime a configuration application is run, I would rather determine if a column has already been changed to nvarchar and not perform an alter on the table. The databases which I must support are SQL Server 2000, 2005 and 2008.
You can run the following script which will give you a set of ALTER commands:
SELECT 'ALTER TABLE ' + isnull(schema_name(syo.id), 'dbo') + '.' + syo.name
+ ' ALTER COLUMN ' + syc.name + ' NVARCHAR(' + case syc.length when -1 then 'MAX'
ELSE convert(nvarchar(10),syc.length) end + ');'
FROM sysobjects syo
JOIN syscolumns syc ON
syc.id = syo.id
JOIN systypes syt ON
syt.xtype = syc.xtype
WHERE
syt.name = 'varchar'
and syo.xtype='U'
There are, however, a couple of quick caveats for you.
This will only do tables. You'll want to scan all of your sprocs and functions to make sure they are changed to NVARCHAR as well.
If you have a VARCHAR > 4000 you will need to modify it to be NVARCHAR(MAX)
But those should be easily doable with this template.
If you want this to run automagically you can set it in a WHILE clause.
The issue with Josef's answer is that it would change NOT NULL fields to NULL after executing the queries. The following manipulation fixes it:
SELECT cmd = 'alter table [' + c.table_schema + '].[' + c.table_name
+ '] alter column [' + c.column_name + '] nvarchar('
+CASE WHEN CHARACTER_MAXIMUM_LENGTH<=4000
THEN CAST(CHARACTER_MAXIMUM_LENGTH as varchar(10)) ELSE 'max' END+')'
+ CASE WHEN IS_NULLABLE='NO' THEN ' NOT NULL' ELSE '' END,*
FROM information_schema.columns c
WHERE c.data_type='varchar'
ORDER BY CHARACTER_MAXIMUM_LENGTH desc
Credits to Igor's answer
The following query should get you what you need:
IF EXISTS
(SELECT *
FROM sysobjects syo
JOIN syscolumns syc ON
syc.id = syo.id
JOIN systypes syt ON
syt.xtype = syc.xtype
WHERE
syt.name = 'nvarchar' AND
syo.name = 'MY TABLE NAME' AND
syc.name = 'MY COLUMN NAME')
BEGIN
ALTER ...
END
Fixed the space issue and added schema
SELECT 'ALTER TABLE [' + isnull(schema_name(syo.object_id), sysc.name) + '].[' + syo.name
+ '] ALTER COLUMN ' + syc.name + ' NVARCHAR(' + case syc.max_length when -1 then 'MAX'
ELSE convert(nvarchar(10),syc.max_length) end + ');'
FROM sys.objects syo
JOIN sys.columns syc ON
syc.object_id= syo.object_id
JOIN sys.types syt ON
syt.system_type_id = syc.system_type_id
JOIN sys.schemas sysc ON
syo.schema_id=sysc.schema_id
WHERE
syt.name = 'varchar'
and syo.type='U'
Further updated to fix MAX being replaced with -1.
SELECT cmd = 'ALTER TABLE [' + c.table_schema + '].[' + c.table_name
+ '] ALTER COLUMN [' + c.column_name + '] NVARCHAR('
+CASE WHEN CHARACTER_MAXIMUM_LENGTH<=4000 THEN
CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN
'MAX' ELSE CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10)) END ELSE 'MAX' END+')'
+ CASE WHEN IS_NULLABLE='NO' THEN ' NOT NULL' ELSE '' END,*
FROM information_schema.columns c
WHERE c.data_type='VARCHAR'
ORDER BY CHARACTER_MAXIMUM_LENGTH DESC
Credit to Nezam's Answer
And another one to manage default values:
SELECT cmd =
CASE WHEN name IS NOT NULL THEN
'ALTER TABLE ' + c.table_name + ' DROP CONSTRAINT ' + d.name + '; ' +
'ALTER TABLE [' + c.table_schema + '].[' + c.table_name + '] ALTER COLUMN [' + c.column_name + '] ' +
'NVARCHAR(' +
CASE WHEN CHARACTER_MAXIMUM_LENGTH <= 4000 THEN
CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN
'MAX'
ELSE
CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10))
END
ELSE
'MAX'
END
+ ')' +
CASE WHEN IS_NULLABLE='NO' THEN ' NOT NULL' ELSE '' END + '; ' +
'ALTER TABLE '+ c.table_name + ' ADD CONSTRAINT ' + d.name +' DEFAULT '+ c.column_default + ' FOR ' + c.column_name + ';'
ELSE
'ALTER TABLE [' + c.table_schema + '].[' + c.table_name + '] ALTER COLUMN [' + c.column_name + '] ' +
'NVARCHAR(' +
CASE WHEN CHARACTER_MAXIMUM_LENGTH<=4000 THEN
CASE WHEN CHARACTER_MAXIMUM_LENGTH = -1 THEN
'MAX'
ELSE
CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10))
END
ELSE
'MAX'
END
+ ')' +
CASE WHEN IS_NULLABLE='NO' THEN ' NOT NULL' ELSE '' END
END,d.name, c.*
FROM information_schema.columns c
LEFT OUTER JOIN sys.default_constraints d ON d.parent_object_id = object_id(c.table_name)
AND d.parent_column_id = columnproperty(object_id(c.table_name), c.column_name, 'ColumnId')
WHERE c.data_type='VARCHAR'
ORDER BY CHARACTER_MAXIMUM_LENGTH DESC