I have mssql2008 r2 sql server
The problem:
User has some column permissions on the table. He could update some of the columns of the table (not all). We need to create UPDATE statement so that it will not violate permissions.
Preferably without dynamic query.
Is there this ability in MSSQL server?
Without dynamic SQL (or dynamic query construction in the app or API layer)? I don't think it will be very pretty. The UPDATE command doesn't have any inherent knowledge of what permissions the user might have on the affected column(s). It is going to submit the query to the engine and hope for the best. If the user doesn't have permissions on all the columns, it's going to return an error, not try to circumvent that by altering the intended statement. I think this would actually be a very bad thing to continue with the update even though not all intended columns have been updated.
That all said, I suppose you could do something like this, but it is not going to be pretty at all - in fact it will be a lot easier if you are not relying on database principals:
DECLARE
#dpid INT = DATABASE_PRINCIPAL_ID(),
#obj INT = OBJECT_ID('dbo.foo'),
#col SYSNAME = N'bar';
UPDATE dbo.foo SET bar = CASE
WHEN EXISTS -- check they've been granted UPDATE at column or table level:
(
SELECT 1
FROM sys.database_permissions AS dp
INNER JOIN sys.objects AS o
ON dp.major_id = o.[object_id]
LEFT OUTER JOIN sys.columns AS c
ON dp.minor_id = COALESCE(c.column_id, 0)
WHERE dp.grantee_principal_id = #dpid
AND o.[object_id] = #obj
AND (c.name = #col OR c.column_id IS NULL)
AND dp.[permission_name] = 'UPDATE'
AND dp.[state] = 'G' -- GRANT
)
AND NOT EXISTS -- since DENY trumps GRANT, make sure that doesn't also exist:
(
SELECT 1
FROM sys.database_permissions AS dp
INNER JOIN sys.objects AS o
ON dp.major_id = o.[object_id]
LEFT OUTER JOIN sys.columns AS c
ON dp.minor_id = COALESCE(c.column_id, 0)
WHERE dp.grantee_principal_id = #dpid
AND o.[object_id] = #obj
AND (c.name = #col OR c.column_id IS NULL)
AND dp.[permission_name] = 'UPDATE'
AND dp.[state] = 'D' -- DENY
)
THEN #bar ELSE bar END
-- WHERE...
;
This isn't exactly what you're asking for; technically it updates the column but sets it to itself (so it will still be indicated as an updated column in a trigger, for example) but it prevents the input from being applied to the table. I also did not check against permissions granted in ways other than an explicit GRANT UPDATE or DENY UPDATE to the specified user or role - for example GRANT ALL, or permissions inherited by AD group membership, can complicate this. Of course it is not going to be much fun at all to manage this if you have multiple columns to check.
You may want to add other conditionals to the WHEN clause, e.g. to avoid the check for dbo (who ) or users you want to explictly bypass the check, you could have:
CASE
WHEN DATABASE_PRINCIPAL_ID() = 1 THEN #bar
WHEN SUSER_SNAME = 'some_user' THEN #bar
WHEN (...stuff from above...)
ELSE bar
END
-- WHERE...
;
Related
select a_table.fname,a_table.lname,a_view.tell
from a_table
if exists(select 1 from sys.views where name='a_view' and type='v')
{
inner join a_view on a_table.id=a_view.id
}
My questions:
inner join my table with a view if i created view before
and if a_view exist then show a_view.tell because a_view is in a_view!
its incorrect if i select a_view.tell when a_view is not exist
A dynamic sql could be used for this.
DECLARE #DynSql VARCHAR(max);
SET #DynSql = 'select fname, lname from a_table';
IF OBJECT_ID('a_view', 'v') IS NOT NULL
BEGIN
SET #DynSql = 'select
t.fname, t.lname, v.tell
from a_table t
join a_view v on v.id = t.id';
END
EXEC (#DynSql);
This is too long for a comment.
Create the view! Don't make your queries conditional on whether or not tables or views exist.
A database is supposed to have a structure, and there is little to no reason to not have the tables and views that you need.
So, long before you get to where this query is needed, be sure the view exists. I'm not sure what this requires in your environment but it might include:
Creating the view with the right permissions so it cannot be accidentally deleted.
Scheduling a job that checks to be sure the view exists and/or updates the view periodically.
Creating the view earlier in the script where this should be used.
I am just starting creating some unit tests for my database.
If I have faked a table,
EXEC tSQLt.FakeTable
#TableName = 'dbo.[My Table]',
#Identity = 0,
#ComputedColumns = 0,
#Defaults = 0
Can I check if it has been faked?
Note that documentation on the FakeTable SP can be found here.
Motivation
I want to be able to do this as I imagine creating several stored procedures which populate these faked tables so I can perform tests.
However I do not want to handle faking the tables in the stored procedures (so I can call them multiple times entering different info each time).
I don't want to have the possibility that I forget to fake the table before adding the data (as would almost certainly cause me to fail my test).
tSQLt adds an extended property to a fake table to track the table it fakes. This is easily tested using the function tSQLt.Private_GetOriginalTableName:
SELECT tSQLt.Private_GetOriginalTableName('dbo','[My Table]')
This will return NULL if the table isn't faked.
If you want to do something more complex, you can query sys.extended_properties directly. See the contents of the tSQLt.class.sql script (in the tSQLt distribution) for the definition of tSQLt.Private_GetOriginalTableName.
You can check for the existence of the table at the beginning of your stored procedure(s).
If Not Exists ( Select 1 From Sys.Objects Where [Name] = 'YOURTABLENAME' And [Type] = 'U')
Begin
-- Your Create Table Statement Here
ENd
Based on your comments, the tool has to be doing something like this, using schema:
Create table dbo.MisterPositive ( test int )
Create table developers.MisterPositive (test Int )
-- Both statements below work
Select * From dbo.MisterPositive
Select * From developers.MisterPositive
-- Use this to look for existence prior
Select 1 from sys.objects
Inner join sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id
where sys.objects.[Name] = 'MisterPositive' And sys.schemas.name = 'dbo'
Select 1 from sys.objects
Inner join sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id
where sys.objects.[Name] = 'MisterPositive' And sys.schemas.name = 'Developers'
So yours would be
If Not Exists ( Select 1 from sys.objects
Inner join sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id
where sys.objects.[Name] = 'YOURTABLE' And sys.schemas.[Name] = 'tSQLt' )
Begin
-- create table here
End
Summary
I am trying to figure out a way I can automatically alter 100+ triggers at once or in a loop. The triggers are all identical except for the respective table that they are associated with.
Details:
I have a SQL server database and I have 300+tables and over 100+ of those tables have a trigger that prevents anyone from deleting any records.
As you can see below, trigger simply sets "IsDeleted" field to 1 if a person tries to do a delete (this way I never lose anything)
For example a trigger in my Person table would look like this:
ALTER TRIGGER [TD_Person]
ON [Person]
INSTEAD OF DELETE
AS
SET NOCOUNT ON
UPDATE x
SET [IsDeleted] = 1
FROM [Person] x
INNER JOIN
deleted d
ON x.[Id] = d.[Id]
the issue is that i now realize that I need to update another field (besides IsDeleted) called LastUpdated (which is a datetime field) as part of this trigger. This way, I am capturing the time in which the delete occurred
To make the change is quite simple. Instead of just:
SET [IsDeleted] = 1
I would do this:
SET [IsDeleted] = 1, LastUpdated = GETDATE()
in the trigger above. I tested the change and it works fine.
My issue is that i don't want to manually go into each of my table and have paste ", LastUpdated = GETDATE()" into the trigger one by one.
I can isolate these triggers by doing something like this:
Select *
FROM sys.triggers t
where t.Name like 'MyDeleteTrigger_%'
ESCAPE '\'
but I can't figure out how I could leverage this to modify each of these triggers?
Is there any way to somehow automate this update to a trigger that exists in so many tables to avoid tedious copy and paste over again?
You can use following query in order to automatically change your triggers:
DECLARE #Command NVARCHAR(MAX) = ''
SELECT #Command = #Command +
'ALTER TRIGGER [TD_'+OBJECT_NAME(t.parent_id)+']'+CHAR(13) +
'ON ['+OBJECT_NAME(t.parent_Id)+']'+CHAR(13) +
'INSTEAD OF DELETE'+CHAR(13)+
'AS'+CHAR(13)+
'SET NOCOUNT ON'+CHAR(13)+CHAR(13)+
'UPDATE x'+CHAR(13)+
'SET [IsDeleted] = 1, LastUpdated = GETDATE()'+CHAR(13)+
'FROM ['+OBJECT_SCHEMA_NAME(t.parent_id)+'].['+OBJECT_NAME(t.parent_id)+'] x'+CHAR(13)+
'INNER JOIN deleted d ON x.[Id] = d.[Id]'+CHAR(13)+
'End;'+CHAR(13)
FROM sys.triggers t
INNER JOIN sys.sql_modules m ON m.object_id = t.object_id
WHERE is_instead_of_trigger = 1
and m.definition like '%INSTEAD OF DELETE%'
--SELECT #Command
EXEC(#Command)
I want to rename tables and views which are used in stored procedures. Is there any way to find and replace table names in stored procedures, maybe there is tool for ms sql server (i'm using ms sql server 2012).
SQL Server might not allow you to directly UPDATE the object definitions (Views and Stored Proceduress in your case) present in the System catalogs even after setting the 'Allow Updates' option to 1.
The following code will generate the required ALTER Script and you can run them manually after reviewing the definitions ([ModifiedDefinition] )or u can loop through each value of [ModifiedDefinition] and run it using sp_executesql.
SELECT
b.Name AS [ObjectName],
CASE WHEN b.type ='p' THEN 'Stored Procedure'
WHEN b.type ='v' THEN 'View'
ELSE b.TYPE
END AS [ObjectType]
,a.definition AS [Definition]
,Replace ((REPLACE(definition,'OLD Value','New Value')),'Create','ALTER') AS [ModifiedDefinition]
FROM sys.sql_modules a
JOIN
( select type, name,object_id
from sys.objects
where type in (
'p' -- procedures
,'v'--views
)
and is_ms_shipped = 0
)b
ON a.object_id=b.object_id
And as always, be careful with production data and take backups before performing bulk changes on object definitions!!
You can use DBvisualizer .. it pretty much works with all databases and with ms sql too, you can do all you mentioned by using this.
I answered this on another topic (https://stackoverflow.com/a/67728039/11165834) , I do it using the following script:
DECLARE #queryDef NVARCHAR(max)
WHILE EXISTS (
SELECT 1
FROM sys.sql_modules sm
JOIN sys.objects o ON sm.object_id = o.object_id
WHERE sm.definition LIKE '%TEXT_TO_REPLACE%'
AND o.type = 'V'
)
BEGIN
-- TO ALTER THE VIEW AUTOMATICALLY
SET #queryDef = ( SELECT TOP 1 Replace (Replace (sm.definition, 'CREATE VIEW', 'ALTER VIEW'),
'TEXT_TO_REPLACE',
'NEW_TEXT')
FROM sys.sql_modules sm
JOIN sys.objects o ON sm.object_id = o.object_id
WHERE sm.definition LIKE '%TEXT_TO_REPLACE%'
AND o.type = 'V')
EXEC (#queryDef)
END
I use it to replace procedures/views when I restore a backup from production into tests databases.
As #S.A said, be verry careful because is not a verry safe way.
Change the "o.type" and "Replace (sm.definition, 'CREATE VIEW', 'ALTER VIEW'" accordingly to your need
I have a stored procedure that creates and opens some cursors. It closes them at the end, but if it hits an error those cursors are left open! Then subsequent runs fail when it tries to create cursors since a cursor with the name already exists.
Is there a way I can query which cursors exists and if they are open or not so I can close and deallocate them? I feel like this is better than blindly trying to close and swallow errors.
This seems to work for me:
CREATE PROCEDURE dbo.p_cleanUpCursor #cursorName varchar(255) AS
BEGIN
DECLARE #cursorStatus int
SET #cursorStatus = (SELECT cursor_status('global',#cursorName))
DECLARE #sql varchar(255)
SET #sql = ''
IF #cursorStatus > 0
SET #sql = 'CLOSE '+#cursorName
IF #cursorStatus > -3
SET #sql = #sql+' DEALLOCATE '+#cursorName
IF #sql <> ''
exec(#sql)
END
Look here for info on how to find cursors. I have never used any of them because I could figure out a way to get it done without going Row By Agonizing Row.
You should rebuild the sp to either
not use cursors ( we can help -
there is almost always a way to
avoid RBAR)
build it in a transaction and roll it back if there is a failure or if you detect an error. Here are some excellent articles on this. part 1 and part 2
If you have SQL2005, you can also use try catch
EDIT (in response to your post):Ideally, data generation is best handled at the application level as they are better suited for non set based operations.
Red Gate has a SQL Data generator that I have used before (its great for single tables, but takes some configuring if you have lots of FK or a wide [normalized] database).
This works on 2008R2, haven't tested on anything earlier than that:
USE MASTER
GO
select s.session_id, s.host_name, s.program_name, s.client_interface_name, s.login_name
, c.cursor_id, c.properties, c.creation_time, c.is_open, con.text,
l.resource_type, d.name, l.request_type, l.request_Status, l.request_reference_count, l.request_lifetime, l.request_owner_type
from sys.dm_exec_cursors(0) c
left outer join (select * from sys.dm_exec_connections c cross apply sys.dm_exec_sql_text(c.most_recent_sql_handle) mr) con on c.session_id = con.session_id
left outer join sys.dm_exec_sessions s on s.session_id = c.session_id
left outer join sys.dm_tran_locks l on l.request_session_id = c.session_id
left outer join sys.databases d on d.database_id = l.resource_database_id
You can use the sp_cursor_list system stored procedure to get a list
of cursors visible to the current connection, and
sp_describe_cursor, sp_describe_cursor_columns, and
sp_describe_cursor_tables to determine the characteristics of a
cursor.
(from http://msdn.microsoft.com/it-it/library/aa172595(v=sql.80).aspx )
You can use
sys.dm_exec_cursors
as described here
Basically you can run this sample query and get information about the cursors that are open in various databases
sys.dm_exec_cursors(0)