SQL Server Profiler shows that our ERP application sends quite a lot commands to DB server which look like:
declare #p1 int
set #p1=NULL
declare #p5 int
set #p5=16388
declare #p6 int
set #p6=8196
exec sp_cursorprepare #p1 output,NULL,N'SELECT * FROM SomeTable WHERE 0 = 1',1,#p5 output,#p6 output
select #p1, #p5, #p6
To emphasize:
exec sp_cursorprepare and then SELECT (...) WHERE 0 = 1
Does it make any sense? Is this a kind of trick?
When an app generates SQL statements with "WHERE 0=1" or "WHERE 1=1", it's usually being used as placeholder so that the app can dynamically build the rest of the WHERE clause using AND or OR without having to figure out which condition is first and needs to immediately follow "WHERE".
So yes, it's a trick to make building dynamic WHERE clauses easier for the app developer. I've used it myself.
Related
On SQL Server, I am testing to create a Plan guide to force a query from Java to use a specific optimal execution-plan always. Some how the SQL query not using this Plan Guide
I followed, https://technet.microsoft.com/en-us/library/ms190772%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396
SQL comes from Java,
declare #p1 int
set #p1=1986
exec sp_prepexec #p1 output,N'#P0 nvarchar(4000)',N'SELECT Top 1 Amount,col2 from dbo.table1
where ExId=#P0
order by id
',N'0a8e8e31-2a05-0000-8ece-0003fd69e692'
select #p1
Plan Guide I created,
DECLARE #xml_showplan nvarchar(max);
SET #xml_showplan = (select cast(query_plan as nvarchar(max)) from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_sql_text (cp.plan_handle) st
cross apply sys.dm_exec_query_plan (cp.plan_handle) qp
where st.text like '%Top 1 Amount%'
and objtype='Prepared')
--select (#xml_showplan)
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from dbo.table1 where ExId=#P0 order by id',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
Appreciate if you can help me to get thru the plan guide used by SQL query from java.
Created a Plan Guide with the SQL collected from SQL Profiler,
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from table1
where ExId=#P0
order by ID
',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
GO
Please read the article sp_create_plan_guide (Transact-SQL) pay attention to Remarks:
Plan Guide Matching Requirements
For plan guides that specify #type = 'SQL' or #type = 'TEMPLATE' to
successfully match a query, the values for batch_text and
#parameter_name data_type [,...n ] must be provided in exactly the
same format as their counterparts submitted by the application. This
means you must provide the batch text exactly as the SQL Server
compiler receives it. To capture the actual batch and parameter text,
you can use SQL Server Profiler. For more information, see Using SQL
Server Profiler to Create and Test Plan Guides.
This means that the statement should exactly match to what is written in your plan guide, but in your code plan guide is created for a statement that differs from those sending by java: it has only one row while java code has 4 rows with CR and additional spaces.
If you evaluate any hash from java statement and your statement provided in sp_create_plan_guide you'll see they are different.
Another thing I suspect that may be wrong is how you get a "good plan". Since you just assign #xml_showplan the result of a query that can produce many rows, I'm not sure you get the plan you want. How do you ensure that you capture the "good£ plan and not the same plan that java code produced?
The first query retrieves the list of installed instances:
SET NOCOUNT ON
DECLARE #GetInstances TABLE
( Value nvarchar(100),
InstanceNames nvarchar(100),
Data nvarchar(100))
Insert into #GetInstances
EXECUTE xp_regread
#rootkey = 'HKEY_LOCAL_MACHINE',
#key = 'SOFTWARE\Microsoft\Microsoft SQL Server',
#value_name = 'InstalledInstances'
Select
InstanceNames
From
#GetInstances
SET NOCOUNT OFF
The second query could be anything but for the sake of argument, this one will do; it retrieves the list of trace ids for each trace instance:
select distinct
info.eventid
from
sys.traces as tr
cross apply sys.fn_trace_geteventinfo (tr.id) as info
So in summary, I want to feed the resultant instances from the first query into the second query so that I return all the values for all of the installed instances. There could be just one default instance but there could also be several.
Sounds like you could use a Cursor.
Note: Cursors are kinda slow, and generally you want to keep them to a minimum.
Check out this link to learn about them in depth.
But to be short: they can let you loop over a result set and execute tsql per row
Your code:
declare #GetInstances table
(
Value nvarchar(100),
InstanceNames nvarchar(100),
Data nvarchar(100)
)
insert into #GetInstances
exec xp_regread
#rootkey = 'HKEY_LOCAL_MACHINE',
#key = 'SOFTWARE\Microsoft\Microsoft SQL Server',
#value_name = 'InstalledInstances'
declare #Cursor as cursor;
declare #Server varchar(50);
set #Cursor = cursor for
select
InstanceNames
from
#GetInstances
open #Cursor;
fetch next from #Cursor into #Server;
while ##fetch_status = 0
begin
print #Server
fetch next from #Cursor into #Server;
END
close #Cursor;
deallocate #Cursor;
edit:
To use this server name to execute a proc, we need 2 things.
We need to make sure the server is setup as a linked server
We need to write a tiny bit of dynamic SQL (This is simple, don't worry)
Linked server
Linked servers are pretty simple in concept. Basically, they allow communication between severs via t-sql.
To setup a linked server, Microsoft has documentation here, but basically, in ssms, right click Server Objects > New > Linked Server and then follow all the steps to setup a sql server linked server.
And then boom. You now have a linked server.
Dynamic sql
Dynamic sql is cool and useful, and can really help in situations like this.
Normally, how we would execute a stored procedure on a linked server is this:
exec [RemoteServer].DatabaseName.Schema.StoredProcedureName
but, in our case, we don't know what [RemoteServer] is at time of writing. So what we can do, is write some Dynamic sql. With dynamic sql, what we can do is execute a string of tsql on our db, so we can do something like
exec(#Server + '.DatabaseName.Schema.StoredProcedureName')
and it runs the proc on the remote server.
How do I write using T-SQL, variable values and query results to a text file, I want to actually keep appending.
You can use SQL CLR to write whatever you want out to a file. You can either code this yourself or you can use the SQL# library (I am the author and it is not free -- for this function -- but not expensive either) and use the File_WriteFile function to write out the text. It allows for appending data. The library is free for most functions but not for the File System functions. It can be found at: http://www.SQLsharp.com/
If you want to try coding this yourself, I found this example which is the basic concept I think you are asking for: http://www.mssqltips.com/tip.asp?tip=1662
This works but it's a little arcane. Adjust to your query. Essentially you'd be creating command shell echo lines for execution through xp_cmdshell and looping through them.
declare #cmd varchar(500), #minid int, #maxid int;
declare #output table(
id int identity(1,1) primary key,
echo varchar(500) not null
)
set #cmd = 'select top 10 ''ECHO ''+name+'' >> C:\test.txt'' from master.dbo.sysobjects'
insert into #output(echo)
exec(#cmd)
select #minid=Min(ID),#maxid=Max(ID) from #output
while #minid <= #maxid
begin
select #cmd = echo from #output where id = #minid;
exec xp_cmdshell #cmd;
set #minid=#minid+1;
end
Enabling and using xp_cmdshell exposes your instance to additional security risk. Using CLR is safer but also not advised for this purpose. These two options only make a poor design decision worse.
Why are you trying to write to a file from T-SQL? Resist the urge to turn SQL Server into a scripting platform. Use an application language like SSIS, PowerShell or C# to interact with SQL Server and write output to a log file from there as needed.
I had problem in this subject, so I want to propose what I did.
I Declared a Cursor for Select results and then I write them to file. For example:
DECLARE #cmd NVARCHAR(400)
DECLARE #var AS NVARCHAR(50)
,#Count = (SELECT COUNT(*) FROM Students)
DECLARE Select_Cursor CURSOR FOR
SELECT name FROM Students OPEN Select_Cursor
FETCH NEXT FROM Select_Cursor INTO #var
WHILE (#Count>0)
SET #cmd = 'echo ' + #var +' >> c:\output.txt'
EXEC master..xp_cmdshell #cmd
SET #Count = #Count - 1
FETCH NEXT FROM Select_Cursor INTO #var
END
CLOSE SelectCursor
DEALLOCATE SelectCursor
I'm running a SQL Server 2008 64 bit Developer Edition with Service Pack 1 installed.
I have a SQL Server Agent Job. Within this job I want to get the job_id of my own job.
On MSDN (http://msdn.microsoft.com/en-us/library/ms175575(v=SQL.100).aspx) you can find a description of using tokens in job steps. Wow, great, that's what I'm looking for!! Just use (JOBID).
Since SQL Server 2005 SP1 you have to use macro like $(ESCAPE_NONE(JOBID)). No problem.
But if you try the example:
DECLARE #name NVARCHAR(128)
select #name = name from msdb.dbo.sysjobs where job_id = $(ESCAPE_SQUOTE(JOBID))
PRINT #name
you get:
Incorrect syntax near 'ESCAPE_SQUOTE'. (Microsoft SQL Server, Error: 102)
Ok, now from the scratch:
PRINT N'$(ESCAPE_SQUOTE(JOBID))'
results in 0xE33FE637C10B3C49A6E958BB3EF06959 but the job_id is
37E63FE3-0BC1-493C-A6E9-58BB3EF06959
The "N'" I think makes an implicit conversion to NVARCHAR of the (JOBID)...
Ok, I think I have to care about the datatype of (JOBID). In the book "SQL Server 2008 Administration" on page 168/169 there's also an example of using (JOBID):
declare #jobid binary(16)
SELECT #jobid =Convert(Uniqueidentifier,$(ESCAPE_NONE(JOBID)))
results in:
Incorrect syntax near '('. (Microsoft SQL Server, Error: 102)
I'm totally confused now. Could please someone help me with a good advice or solution. Every kind of help is appreciated.
Best regards
Helmut
We had trouble with this recently and did not go the route you found in MSDN. Instead, we recovered the jobid from dbo.sysjobs by name directly (the opposite of your example) and then used that within the job to check execution status (exiting out of long running while loop if job state had changed).
declare #jobid uniqueidentifier
SELECT #jobid = job_id from msdb.dbo.sysjobs where name = '[blah]'
thanks for your answers. The problem is that I tried to parse the statement in job step. Then I got this error. While running the job there's no problem.
My very best solution now is:
declare #JobID uniqueidentifier
SELECT #JobID = $(ESCAPE_NONE(JOBID));
PRINT 'My JobID is ' + Convert(char(255), #JobID)
Now you handle with #JobID, but as far as I know until now you have to convert always to char(255).
Thanks to user state_dba on MSDN.
Just forget what parser is saying - variable resolution is done at runtime. Parser does not know about that.
This may sound obvious, but I get the error you quoted from your first sample, if I run it in a query window, but it runs perfectly well when I paste that script into a job step.
You can only use these tokens within job steps. And, given that we're not expecting any quotes in the jobid token, I'd use ESCAPE_NONE whenever you reference it.
For those that need an alternative method to get your own job ID without macros, for example, from within a stored procedure that a job step calls. I found the following here
DECLARE #SQL NVARCHAR(72),
#jobID UNIQUEIDENTIFIER,
#jobName SYSNAME
IF SUBSTRING(APP_NAME(),1,8) = 'SQLAgent'
BEGIN
SET #SQL = 'SET #guid = CAST(' + SUBSTRING(APP_NAME(), 30, 34) + ' AS UNIQUEIDENTIFIER)'
EXEC sp_executesql #SQL, N'#guid UNIQUEIDENTIFIER OUT', #guid = #jobID OUT
SELECT #jobName = name
FROM msdb..sysjobs
WHERE job_id = #jobID
END
I have used sp_addlinkedserver to access the remote machines db now i am writing queries explicitly on database like,
select * from [server\instance].database.owner.tablename
Now with this,
[Server\instance] : this has to be provided explicitly
[database] : can we find databases on specified instance using query like ms_ForEachDB ?
[owner] : Can we find the database owner name using query ?
If these values are found using queries do we need to use EXEC() to execute this or we can still achieve it using nice queries ?
Thanks all,
The "nice" format you mention is simply a 4 part object reference.
select * from [server\instance].database.owner.tablename
3 part
select * from database.owner.tablename
2 part
select * from owner.tablename
If you want to dynamically change any of the server, db or schema values then you have one option:
EXEC (#sqlstring)
However, if you only access stored procs remotely...
DECLARE #RemoteSP varchar(500)
SET #RemoteSP = '[server\instance].database2.schema.proc2'
EXEC #RemoteSP #p1, #p2, #p3 OUTPUT
SET #RemoteSP = '[server\instance].database1.schema.proc1'
EXEC #RemoteSP #p4, #p5, #p6 OUTPUT
However, changing the components of the object reference makes no sense arguably: if you know you're going to query a table then just call that table in that database...
you should make a query string and then run it by exec() function.
getting server name :
SELECT ##SERVERNAME
getting current db name :
SELECT DB_NAME() AS DataBaseName
You do not have to use EXEC you could use something like select * from openquery(MyLinkedServer,#sql) THough i prefer EXEC(#sql) AT MyLinkedServer
But all work
If it happens that you need to use some sort of variable in your arguments(e.g. collect remote's server updates since yesterday):
DECLARE #yesterday NVARCHAR(20) = '2016-09-23 08:16:20';
DECLARE #sql NVARCHAR(MAX) = N'SELECT * FROM database.targetTable AS origin
WHERE origin.columnWithDateTime >'''+#yesterday+''';';
PRINT #sql;
EXEC(#sql) AT linkedServer
______
Where:
database.targetTable : For some reason SSMS 2008 R2 returns error if you describe it as [database].[targetTable], and i don't know why that happens.
#yesterday: Is the variable you want to insert (this case, a string containing datetime-like element)
PRINT #sql: Just to verify if the quotes are correctly placed.
columnWithDateTime: Should be a column with datetime format (e.g. "timestamp", or similar to the #yesterday variable format.
"OPENQUERY does not accept variables for its arguments.": See Here (MSDN: OPENQUERY (Transact-SQL)).