Hi i am using the following dynamic sql:
declare #cmd nvarchar(4000)
set #cmd=
'select ''exec msdb.dbo.sp_update_job #job_id=''''''
+ convert(nvarchar(255), job_id)+ '''''',
#owner_login_name=''''sa'''';''
from msdb..sysjobs'
exec sp_executesql #cmd
but all it is doing is printing as exec .......
I want to execute the results not just print it.
What should i do?
Regards
Manjot
How about:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = COALESCE(#sql, '') + '
EXEC msdb.dbo.sp_update_job #job_id = '''
+ CONVERT(NVARCHAR(255), job_id)
+ ''', #owner_login_name = ''sa'';'
FROM msdb.dbo.sysjobs;
EXEC sp_sqlexec #sql;
NOTE that sp_sqlexec is undocumented and unsupported. To do this in a supported way, why not just create a cursor, it is much easier to follow and debug (no sea of red and no "invisible" SQL):
DECLARE #j UNIQUEIDENTIFIER;
DECLARE c CURSOR FOR
SELECT job_id FROM msdb.dbo.sysjobs;
OPEN c;
FETCH NEXT FROM c INTO #j;
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC msdb.dbo.sp_update_job
#job_id = #j,
#owner_login_name = 'sa';
FETCH NEXT FROM c INTO #j;
END
DEALLOCATE c;
Or ever easier, since msdb.dbo.sysjobs is not technically a system table, and you can edit it directly:
UPDATE msdb.dbo.sysjobs
SET owner_sid = SUSER_SID('sa');
Remove the 'Select' ! (and also remove some escaped single quotes)
In other words...
set #cmd=
'exec msdb.dbo.sp_update_job #job_id='''
+ convert(nvarchar(255), job_id)
+ ''', #owner_login_name=''sa'';'
Note: not quite sure where this last thing fits (probably at the end of the sp_update_job statement.
-- from msdb..sysjobs'
Essentially what was going on, with the SELECT statement was that the dynamic SQL being executed was a select statement which happen to select a string dynamically crafted. SQL would then print that string, just as if the SELECT statement had been, say:
select 'Once upon a time, a very poor lumberjack..."
(Whatever is in the string after select doesn't affect SQL in any way)
Now, as is more obvious with Remus' nice formatting of the question, the dyanamic SQL statement itself seems to be wrong, but at least SQL will try to execute it, now...
Related
I implement some code:
BEGIN
DECLARE
#SQL AS NVARCHAR(MAX),
#TempTable AS NVARCHAR(MAX)
SET #SQL = 'SELECT * from Employee where Instance_ID = 1';
BEGIN
CREATE TABLE ##tempResults (SQL NVARCHAR(4000))
INSERT INTO ##tempResults EXEC #SQL;
SET #TempTable= 'select * from #tempResults ORDER BY CASE WHEN ' + #index+ ' =1 THEN [First Name] END DESC '+ ',' + ' CASE WHEN ' + #index + '=2 THEN [Last name] END DESC'
END
EXEC sp_executesql #TempTable;
END
I want to insert the dynamic results into temp table but I can't execute statement and get error. Please advice me for how should I need to do ?
As the error shown:
"Msg 203 is not a valid identifier."
You should use EXEC(#SQL) See here
It is advisable to switch to exec sp_executesql #SQL, it gives you parameterization and helps agains sql injection. Especially since you already use this later on in your query (it's never a good idea to use different methods to accomplish the same thing). See here
I'm using a linked server and finding it very painful to write queries like this:
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME]
Is it possible to use a synonym for something like this:
CREATE SYNONYM [DataRelay] FOR [10.150.10.109].[lhf].[dbo]
in order to be able to query like this:
select * from DataRelay.TABLE_NAME
Without the capabilities of Intellisense, this is just painful...
No, there is no short hand for linked servers, however, you can alias tables in your queries to make it a bit easier.
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME] T
WHERE
T.FieldName=1
OR
T.FieldName=2
Now that I had a minute what I was saying in my comment is that you cannot create a synonym for just part of an object path as you desire. But you can dynamically script the drop and creation of synonyms for any object in your remote database pretty easily. here is an example of how to do if for user tables. For other objects you can use the sys.objects instead of sys.table system view.
Technique key words for more learning. Dynamic SQL, cursor, schema views.
DECLARE #ServerAndDB SYSNAME = '[10.150.10.109].[lhf]'
DECLARE #SynonymSchema SYSNAME = '[syn]'
DECLARE #ObjectPath NVARCHAR(1000)
DECLARE #SynonymName NVARCHAR(1000)
DECLARE CursorName CURSOR FOR
SELECT
#ServerAndDB + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) as ObjectPath
,#SynonymSchema + '.' + QUOTENAME(t.name) as SynonymName
FROM
[10.150.10.109].[lhf].sys.tables t
WHERE
t.type = 'U'
OPEN CursorName
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DECLARE #SQL NVARCHAR(MAX)
IF EXISTS (SELECT * FROM sys.synonyms WHERE object_id = OBJECT_ID(#SynonymName))
BEGIN
SET #SQL = 'DROP SYNONYM ' + #SynonymName
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END
SET #SQL = 'CREATE SYNONYM ' + #SynonymName + ' FOR ' + #ObjectPath
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END TRY
BEGIN CATCH
--Can do error handling here
END CATCH
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
END
CLOSE CursorName
DEALLOCATE CursorName
I have the following code (cursor):
DECLARE #SN VARCHAR(20);
DECLARE #sql NVARCHAR(MAX);
DECLARE C CURSOR LOCAL FAST_FORWARD
FOR SELECT DISTINCT(SERVERNAME) FROM INSTALLATION
where DATABASETYPE = 'MsSql' AND SERVERNAME IN ('x');
OPEN C;
FETCH NEXT FROM C INTO #SN;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #SN;
-- you could loop here for each database, if you'd define what that is
SELECT name
FROM master.dbo.sysdatabases
WHERE name not in ('master','model','msdb','tempdb');
SET #sql = N'SELECT TOP 1 NAME FROM TABLE ';
EXEC sp_executesql #sql;
FETCH NEXT FROM C INTO #SN;
END
CLOSE C;
DEALLOCATE C;
I would like to be able to loop through every server and execute a select statement on some (not all) of their databases.
The query is something like:
SELECT TOP 1 NAME FROM TABLE
The server from where I am running the cursor has all the others as linked servers.
DECLARE #SN VARCHAR(20);
DECLARE C CURSOR LOCAL FAST_FORWARD
FOR SELECT DISTINCT(SERVERNAME) FROM TABLE
where SERVERNAME NOT IN ('SRV1','SRV2','SRV3');
OPEN C;
FETCH NEXT FROM C INTO #SN;
WHILE (##FETCH_STATUS = 0)
BEGIN
PRINT #SN;
-- you could loop here for each database, if you'd define what that is
SET #sql = N'SELECT * FROM ' + #SN + '.master.dbo.TABLE;';
EXEC sys.sp_executesql #sql;
FETCH NEXT FROM C INTO #SN;
END
CLOSE C;
DEALLOCATE C;
Changes:
There is no reason to use the default cursor options here - global, updatable, dynamic, scrollable, etc. Background.
As a habit / best practice, use sp_executesql and not EXEC(). While it doesn't really matter in this case, it can matter in others, so I'd prefer to always code the same way. Background.
Also, please get in the habit of terminating your statements with semi-colons. You'll have to, eventually. Background.
EDIT
Now that we have a little more information about your actual requirements, I suggest this bit of code. Oh, and look, no cursors (well, no explicit cursor declarations and all the scaffolding that comes with them)!
SET NOCOUNT ON;
DECLARE #dbs TABLE(SERVERNAME SYSNAME, DBNAME SYSNAME);
DECLARE #sql NVARCHAR(MAX) = N'';
-- first, let's get the databases on each server:
SELECT #sql += N'SELECT ''' + SERVERNAME + ''', name FROM '
+ QUOTENAME(SERVERNAME) + '.master.sys.databases
WHERE database_id > 4
AND name NOT IN (N''somedb'',N''someotherdb'');'
FROM dbo.INSTALLATION
WHERE DATABASETYPE = 'MsSql'
AND SERVERNAME IN ('x');
INSERT #dbs EXEC sys.sp_executesql #sql;
SELECT #sql = N'';
-- now, build a command to run in each database context:
SELECT #sql += N'
EXEC ' + QUOTENAME(SERVERNAME) + '.'
+ QUOTENAME(DBNAME) + '.sys.sp_executesql #sql;'
FROM #dbs;
-- feel free to change the 3rd parameter here:
EXEC sys.sp_executesql #sql, N'#sql NVARCHAR(MAX)',
N'SELECT ##SERVERNAME, DB_NAME(), actual_columns FROM dbo.table_name;';
This will fail if table_name doesn't exist, so you may still have some work to do if you want to facilitate error handling. But this should get you started.
Also, please be conscious of, and consistently use, the schema prefix. Background.
exec sp_executesql N'SELECT (STUFF (( SELECT '',''+ NAME from Project WHERE ProjectTypeID=1 and OutputHierarchyID IN (SELECT DISTINCT HierarchyID from HierarchyNode'+ #TextVal +''''
+ N')FOR XML PATH('''')), 1, 1, ''''))AS INDUSTRIES',
N'#Industries NVARCHAR(100) output', #Industries output;
I am getting error
Incorrect syntax near '+'.
You have extra single quotes. Try this
exec sp_executesql N'SELECT (STUFF (( SELECT '',''+ NAME from Project WHERE ProjectTypeID=1 and OutputHierarchyID IN (SELECT DISTINCT HierarchyID from HierarchyNode'+ #TextVal +''
+ N')FOR XML PATH('''')), 1, 1, ''''))AS INDUSTRIES',
N'#Industries NVARCHAR(100) output', #Industries output;
Also it is good pratice to assign dynamic query into a variable and use that varialbe for execution
You can't have an expression in the parameter to any procedure, including sp_executesql (this means you can't do things there like concatenate strings). Try the following, which also allows you to debug the statement in a more simple way (I'm not convinced your query is right, because I don't know why you want to add ' after #TextVal, which I assume is some table suffix):
DECLARE #sql NVARCHAR(MAX), #Industries NVARCHAR(100);
SET #sql = N'SELECT #Industries = STUFF((SELECT '','' + NAME
from Project WHERE ProjectTypeID = 1
and OutputHierarchyID IN
(
SELECT DISTINCT HierarchyID from HierarchyNode' + #TextVal + '
) FOR XML PATH('''')), 1, 1, '''');';
PRINT #sql;
--EXEC sp_executesql #sql, N'#Industries NVARCHAR(100) output', #Industries output;
Though I think this version will be slightly more efficient:
DECLARE #sql NVARCHAR(MAX), #Industries NVARCHAR(100);
SET #sql = N'SELECT #Industries = STUFF((SELECT '','' + NAME
from Project AS p WHERE ProjectTypeID = 1
AND EXISTS
(
SELECT 1 from HierarchyNode' + #TextVal + '
WHERE HierarchyID = p.OutputHierarchyID
) FOR XML PATH('''')), 1, 1, '''');';
PRINT #sql;
--EXEC sp_executesql #sql, N'#Industries NVARCHAR(100) output', #Industries output;
You can inspect the statement and be sure it's correct instead of just blindly throwing it at SQL Server and trying to figure out what the error messages might mean. If you copy and paste the output into the top pane, the error message will point to a line number that you can actually see and will make it easier to troubleshoot your syntax. When you believe it's producing correct output, comment the PRINT and uncomment the EXEC.
If you think the +''''+ after #TextVal is necessary, please tell us the value of #TextVal and the name of the table you expect that query to run against.
I'm securing the DB by only allowing interaction with the DB through a series of Sprocs; pretty common fare.
I've dug up and modified a script which loops through and assigns the user EXECUTE permission for all non-system SProcs. It works a treat except that I'd ideally like to add it to the Master DB so that I can easily use it for any subsequent projects. Yes, I could save simple as a .sql file but I'd prefer it this way.
The problem is that I don't know how to dynamically refer to objects in another DB. For example, I can easily query on MyDB.dbo.INFORMATION_SCHEMA.ROUTINES, but if the DB name is dynamic (e.g. #MyDBName), how can I query the objects in this DB?
Edit: Thanks to the posters below, I now have a working solution:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spGrantExec]
#User sysname,
#DB varchar(50),
#Target varchar(50)
AS
/*---------------------------- SQL 2005 + -------------------------------*/
SET NOCOUNT ON
-- 1 - Variable declarations
DECLARE #SQL varchar(8000)
-- 2 - Create temporary table
Set #SQL =
'USE #DB
DECLARE #MAXOID int
DECLARE #OwnerName varchar(128)
DECLARE #ObjectName varchar(128)
DECLARE #CMD1 varchar(8000)
CREATE TABLE #StoredProcedures
(OID int IDENTITY (1,1),
StoredProcOwner varchar(128) NOT NULL,
StoredProcName varchar(128) NOT NULL)
-- 3 - Populate temporary table
INSERT INTO #StoredProcedures (StoredProcOwner, StoredProcName)
SELECT ROUTINE_SCHEMA, ROUTINE_NAME
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME LIKE ''' + #Target + '%''
AND ROUTINE_TYPE = ''PROCEDURE''
-- 4 - Capture the #MAXOID value
SELECT #MAXOID = MAX(OID) FROM #StoredProcedures
-- 5 - WHILE loop
WHILE #MAXOID > 0
BEGIN
-- 6 - Initialize the variables
SELECT #OwnerName = StoredProcOwner,
#ObjectName = StoredProcName
FROM #StoredProcedures
WHERE OID = #MAXOID
-- 7 - Build the string
SELECT #CMD1 = ''GRANT EXEC ON '' + ''['' + #OwnerName + '']'' + ''.'' + ''['' + #ObjectName + '']'' + '' TO #user''
-- 8 - Execute the string
Print #CMD1
EXEC(#CMD1)
-- 9 - Decrement #MAXOID
SET #MAXOID = #MAXOID - 1
END
-- 10 - Drop the temporary table
DROP TABLE #StoredProcedures'
Set #SQL = REPLACE(REPLACE(REPLACE(#SQL, '#DB', #DB), '#User', #User), '#Target', #Target)
--Select #SQL
--Print #SQL
Exec (#SQL)
SET NOCOUNT OFF
Similiar to #Cade's answer, the way to do this is to use dynamic sql. Before each call to a database table, add '#DbName.' Then replace the #DbName with the actual database name (the database name can't be passed as a variable in SQL, so you have to do the replace).
Also Cursors are normally considered evil for performance reasons, however using one in this case makes sense. For one, it would greatly simplify the procedure, plus since you're only going to run this once during application updates, you probably won't notice a performance hit, even if it added an extra second or two (which I doubt it would add anywhere near that much).
ALTER PROCEDURE [dbo].[spGrantExec]
#User SysName,
#DbName VarChar(512)
AS
BEGIN
DECLARE #Sql VarChar(1024)
SET #Sql = 'DECLARE #OwnerName varchar(128)
DECLARE #ObjectName varchar(128)
DECLARE #Cmd1 VarChar(128)
DECLARE ProcCursor CURSOR FOR
SELECT ROUTINE SCHEMA, ROUTINE NAME
FROM #DbName.INFORMATION SCHEMA.ROUTINES
WHERE ROUTINENAME NOT LIKE ''dt %'' AND ROUTINE TYPE = ''PROCEDURE''
OPEN ProcCursor
FETCH NEXT FROM ProcCursor INTO #OwnerName, #ObjectName
WHILE ##FETCH STATUS = 0
BEGIN
SET #CMD1 = ''GRANT EXEC ON '' + ''['' + #OwnerName + '']'' + ''.'' + ''['' + #ObjectName + '']'' + '' TO '' + ''#user''
EXEC (#CMD1)
FETCH NEXT FROM ProcCursor INTO #OwnerName, #ObjectName
END
CLOSE ProcCursor
DEALLOCATE ProcCursor
'
SET #Sql = Replace(Replace(#Sql, '#DbName', #DbName), '#user', #User)
EXEC (#Sql)
END
You can call this using: EXEC [spGrantExec] 'bob', 'Northwind'
Sorry the spacing is a little off in the sp. Developed using Sql 2005.
I found another technique, which I think is cleaner:
SELECT #sql = 'CREATE VIEW ...'
SELECT #sp_executesql = quotename(#dbname) + '..sp_executesql'
EXEC #sp_executesql #sql
This relies on setting the database context by calling sp_executesql in the other database (just like one could call an SP in any database).
In your case it would be equivalent to:
SELECT #sp_executesql = quotename(#dbname) + '..sp_executesql'
EXEC #sp_executesql #CMD1
You can use the double exec technique.
In your case, instead of just:
EXEC(#CMD1)
You would have:
SET #CMD1 =
'USE OtherDatabase;
EXEC (''' + REPLACE(#CMD1, '''', '''''') + ''')'
EXEC(#CMD1)