Related
I have the following example of script. It works fine for this example as it is small and basic. I have a far bigger script with around 50 steps that works fine on its own. As in, hit F5 and does everything, checks if table exists, deletes, writes to tables, drops temps and all results are written to the necessary places. I can't seem to place this large script into this small example:
alter procedure james_tester
#tablename nvarchar(200)
as
BEGIN
declare #sql nvarchar(max)
set #sql =
---->
'select * from'
---->
+ #tablename
EXECUTE sp_executesql #sql
END
---When executing:
execute james_tester 'dbo.calendar_delete'
In my case the section in between the arrows is large and will have numerous variables. I just want to know if there is a function or possibly another way to place a large piece of script at a time in that single quote part. I hope I am describing this sufficiently. What affects the entire script currently from just putting a single quote before and after, is that there are already many comments, and single quotes used in the script that seem to stop the entire script from being highlighted red as text and working fine.
James
It is in fact only the presence of single quotes that will cause problems. Take the following illustration:
declare #bigText varchar(max);
SET #bigText = '"The time has come," the Walrus said,
"To talk of many things:
Of shoes and ships and sealing-wax --SQL Comment has no impact
Of cabbages and kings /* c/c++ comment has no impact */
And why the sea is boiling hot //c# comment has no impact
And whether pigs have wings."';
SELECT #bigText;
Single quotes can be doubled using regex or some other string replace function, so that should not be too hard either.
BUT (and there always is a but of course) whether the remaining text is a legal SQL string, which you can execute is an entirely different question. The presence of extraneous comments etc. will almost certainly bite you.
In case anyone comes to this post and needs to know how this ended. I have no real security risks here of SQL injections etc on the database. The procedure takes a few minutes to run now and just takes this execute in the first two rows to run, with the variables being the periods of SAP backup I need to extract:
execute jc_tester '01' , '02' , '03'
go
alter procedure jc_tester
#Period1 varchar (3) ,#Period2 varchar (3) , #Period3 varchar (3)
as
begin
declare #sql nvarchar(max)
set #sql =
replace('...... | | | | ','|','''') + ......... #period1 +
replace('.....','|','''')
execute sp_executesql #sql
end
Each section of text contained ' that I could not seem to avoid and caused havoc when handling as a string. These had to be replace with pipes, then included in a replace statement.
Some other things I learnt along the way that threw my progress out. Don't use Go's in the script to be used in #sql. I did not know this. Rather use ;. Also remove any comments that use the leading '---'. Instead wrap all comments in the script with /...../ . I could swear this made a difference as so many of the comments in the script in #sql were all trailing ---'s.
This process has saved time and now that it is complete I can explore other options other than dynamic sql in a stored procedure and learn how to do it in probably a more appropriate manner. But I'm glad this ran eventually.... Thanks for the guidance.
I've got a search screen on which the user can specify any combination of first name, last name, semester, or course. I'm not sure how to optimally code the SQL Server 2005 stored procedure to handle these potentially optional parameters. What's the most efficient way? Separate procedures for each combination? Taking the items in as nullable parms and building dynamic SQL?
I'd set each parameter to optional (default value being null)
and then handle it in the WHERE....
FirstName=ISNULL(#FirstName,FirstName)
AND
LastName=ISNULL(#LastName,LastName)
AND
SemesterID=ISNULL(#SemesterID,SemesterID)
That'll handle only first name, only last name, all three, etc., etc.
It's also a lot more pretty/manageable/robust than building the SQL string dynamically and executing that.
The best solution is to utilize sp_execute_sql. For example:
--BEGIN SQL
declare #sql nvarchar(4000)
set #sql =
'select * from weblogs.dbo.vwlogs
where Log_time between #BeginDate and #EndDate'
+ case when #UserName is null then '' else 'and client_user = #UserName' end
sp_execute_sql
#sql
, #params = '#UserName varchar(50)'
, #UserName = #UserName
--END SQL
As muerte mentioned, this will have a performance benefit versus exec()'ing a similar statement.
I would do it with sp_executesql because the plan will be cached just for the first pattern, or the first set of conditions.
Take a look at this TechNet article:
sp_executesql can be used instead of stored procedures to execute a Transact-SQL statement many times when the change in parameter values to the statement is the only variation. Because the Transact-SQL statement itself remains constant and only the parameter values change, the SQL Server query optimizer is likely to reuse the execution plan it generates for the first execution.
Was just posting the same concept as Kevin Fairchild, that is how we typically handle it.
You could do dynamic sql in your code to create the statement as required but if so you need to watch for sql injection.
As muerte points out, the plan will be cached for the first set of parameters. This can lead to bad performance when its run each subsequent time using alternate parameters. To resolve that use the WITH RECOMPILE option on the procedure.
further to these two questions, is there a way to disable the messages that may get sent along with the resultset in SQL2008?
(please note this is nothing to do with the ANSI_WARNINGS setting. Or NOCOUNT.)
Thanks for any help.
Edit: It's not a problem with compatibility settings or table owners. And it's nothing to do with NOCOUNT. Trust me.
No, there's not a way to disable all messages that get sent along with the result sets. Set nocount on/off doesn't have an effect on these types of messages.
You need the NOCOUNT in the body of the Sproc anyway (I appreciate that you've tested it with and without)
In circumstances like this I get the actual call to the Sproc (either from a Debug in my APP, or using SQL Profiler) and then plug that into SSMS or whatever IDE you use, wrapping it in a ROLLBACK transaction (so it can't accidentally make any changes). Note: Log on to SQL Server, with your IDE, using the same credentials as the App will use.
BEGIN TRANSACTION
EXEC StaffEnquirySurnameSearch #searchterm = 'FOOBAR'
ROLLBACK
and see what you get. Use TEXT mode for output, rather than GRID mode which might hide something
Just to show how I think NOCOUNT shoud be added to your SProc:
CREATE PROCEDURE StaffEnquirySurnameSearch
#searchterm varchar(255)
AS
SET NOCOUNT ON
SELECT AD.Name, AD.Company, AD.telephoneNumber, AD.manager, CVS.Position,
CVS.CompanyArea, CVS.Location, CVS.Title, AD.guid AS guid,
AD.firstname, AD.surname
FROM ADCVS AD
LEFT OUTER JOIN CVS ON
AD.Guid=CVS.Guid
WHERE AD.SurName LIKE #searchterm
ORDER BY AD.Surname, AD.Firstname
GO
I note that you are not prefixing the tables with a database owner (most commonly "dbo") which might mean that there are additional copies owned by whomever and that they turn out to be the default from the applications permissions perspective, although I don't think that will change the resultsets [between SQL versions], However, same thing applies to ownership of the Sproc, and there you might be calling some earlier version, created for a different owner.
Ditto where your Sproc name is defined in your ASP.NET code (which I can't seem to find in your linked question) should also have the owner defined, i.e.
EXEC dbo.StaffEnquirySurnameSearch #searchterm = 'FOOBAR'
Did you change the compatibility level when you upgraded from SQL 2000 to 2008? If it is some sort of backward compatibility warning message that might cure it.
Have you tried running the same CONTAINS query without the "OR"?
i.e.:
SELECT * FROM my_table
WHERE CONTAINS(my_column, 'a monkey') -- "a" is a noise word
instead of
SELECT * FROM my_table
WHERE CONTAINS(my_column, 'a OR monkey') -- "a" is a noise word
You can wrap it in a try catch... more info in books online
For example:
CREATE TABLE Test_ShortString(
ShortString varchar(10) NULL
)
begin Try
insert into
Test_ShortString (ShortString)
values ('123456789012345')
End Try
Begin catch
--Select Error_Number() as ErrorNumber
end catch
I have a single windows shell command I'd like to run (via EXEC master..xp_cmdshell) once for each row in a table. I'm using information from various fields to build the command output.
I'm relativity new to writing T-SQL programs (as opposed to individual queries) and can't quite get my head around the syntax for this, or if it's even possible/recommended.
I tried creating a single column table variable, and then populating each row with the command I want to run. I'm stifled at how to iterate over this table variable and actually run the commands. Googling around has proven unhelpful.
Thanks in advance for any help!
You could always use a cursor:
USE Northwind
DECLARE #name VARCHAR(32)
DECLARE #command VARCHAR(100)
DECLARE shell_cursor CURSOR FOR
SELECT LastName FROM Employees
OPEN shell_cursor
FETCH NEXT FROM shell_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #command = 'echo ' + #name
EXEC master.dbo.xp_cmdshell #command
FETCH NEXT FROM shell_cursor INTO #name
END
CLOSE shell_cursor
DEALLOCATE shell_cursor
GO
Is this a once-off job? If so, you might be better off coming from the reverse direction. That is to say, instead of writing a stored procedure to call XP_CMDSHELL to run some program against table data, you should consider writing a program to work against the table data directly. If one scripting product comes to mind, it's PowerShell. It has integrated support for any database that the windows platform supports and you'll find a ton of scripts on www.poshcode.org to do that kind of thing.
On the other hand, if this is something that is to be scheduled, I guess there's nothing hugely wrong with your idea, apart from the fact that XP_CMDSHELL is disabled out of the box with SQL Server these days. Re-enabling it is opening your server to a whole new world of exploits, especially if that table data is sourced from a web page form or some other questionable source.
-Oisin
Ok, this is a curly one. I'm working on some Delphi code that I didn't write, and I'm encountering a very strange problem. One of my stored procedures' parameters is coming through as null, even though it's definitely being sent 1.
The Delphi code uses a TADOQuery to execute the stored procedure (anonymized):
ADOQuery1.SQL.Text := "exec MyStoredProcedure :Foo,:Bar,:Baz,:Qux,:Smang,:Jimmy";
ADOQuery1.Parameters.ParamByName("Foo").Value := Integer(someFunction());
// other parameters all set similarly
ADOQuery1.ExecSQL;
Integer(SomeFunction()) currently always returns 1 - I checked with the debugger.
However, in my stored proc ( altered for debug purposes ):
create procedure MyStoredProcedure (
#Foo int, #Bar int, #Baz int,
#Qux int, #Smang int, #Jimmy varchar(20)
) as begin
-- temp debug
if ( #Foo is null ) begin
insert into TempLog values ( "oh crap" )
end
-- do the rest of the stuff here..
end
TempLog does indeed end up with "oh crap" in it (side question: there must be a better way of debugging stored procs: what is it?).
Here's an example trace from profiler:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL
declare #p3 int
set #p3=NULL
exec sp_executesql
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
N'#P1 int OUTPUT,#P2 int,#P3 int,#P4 int,#P5 int,#P6 int',
#p3 output,1,1,1,0,200
select #p3
This looks a little strange to me. Notice that it's using #p3 and #P3 - could this be causing my issue?
The other strange thing is that it seems to depend on which TADOConnection I use.
The project is a dll which is passed a TADOConnection from another application. It calls all the stored procedures using this connection.
If instead of using this connection, I first do this:
ConnectionNew := TADOQuery.Create(ConnectionOld.Owner);
ConnectionNew.ConnectionString := ConnectionOld.ConnectionString;
TADOQuery1.Connection := ConnectionNew;
Then the issue does not occur! The trace from this situation is this:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL
declare #p1 int
set #p1=64
exec sp_prepare #p1 output,
N'#P1 int,#P2 int,#P3 int,#P4 int,#P5 int,#P6 varchar(20)',
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
1
select #p1
SET FMTONLY ON exec sp_execute 64,0,0,0,0,0,' ' SET FMTONLY OFF
exec sp_unprepare 64
SET NO_BROWSETABLE OFF
exec sp_executesql
N'exec MyStoredProcedure #P1,#P2,#P3,#P4,#P5,#P6',
N'#P1 int,#P2 int,#P3 int,#P4 int,#P5 int,#P6 varchar(20)',
1,1,1,3,0,'400.00'
Which is a bit much for lil ol' me to follow, unfortunately. What sort of TADOConnection options could be influencing this?
Does anyone have any ideas?
Edit: Update below (didn't want to make this question any longer :P)
In my programs, I have lots of code very similar to your first snippet, and I haven't encountered this problem.
Is that actually your code, or is that how you've represented the problem for us to understand? Is the text for the SQL stored in your DFM or populated dynamically?
I was wondering if perhaps somehow the Params property of the query had already got a list of parameters defined/cached, in the IDE, and that might explain why P1 was being seen as output (which is almost certainly causing your NULL problem).
Just before you set the ParamByName.Value, try
ParamByName("Foo").ParamType=ptInput;
I'm not sure why you changing the connection string would also fix this, unless it's resetting the internal sense of the parameters for that query.
Under TSQLQuery, the Params property of a query gets reset/recreated whenever the SQL.Text value is changed (I'm not sure if that's true for a TADOQuery mind you), so that first snippet of yours ought to have caused any existing Params information to have been dropped.
If the 'ParamByname.ParamType' suggestion above does fix it for you, then surely there's something happening to the query elsewhere (at create-time? on the form?) that is causing it to think Foo is an output parameter...
does that help at all? :-)
caveat: i don't know delphi, but this issue rings a faint bell and so i'm interested in it
do you get the same result if you use a TADOStoredProc instead of a TADOQuery? see delphi 5 developers guide
also, it looks like the first trace does no prepare call and thinks #P1 is an output paramer in the execute, while the second trace does a prepare call with #P1 as an output but does not show #P1 as an output in the execute step - is this significant? it does seem odd, and so may be a clue
you might also try replacing the function call with a constant 1
good luck, and please let us know what you find out!
I suspect you have some parameters mismatch left over from the previous use of your ADOQuery.
Have you tried to reset your parameters after changing the SQL.Text:
ADOQuery1.Parameters.Refresh;
Also you could try to clear the parameters and explicitly recreate them:
ADOQuery1.Parameters.Clear;
ADOQuery1.Parameters.CreateParameter('Foo', ftInteger, pdInput, 0, 1);
[...]
I think changing the connection actually forces an InternalRefresh of the parameters.
ADOQuery1.Parameters.ParamByName("Foo").Value = Integer(someFunction());
Don't they use := for assignment in Object Pascal?
#Constantin
It must be a typo from the Author of the question.
#Blorgbeard
Hmmm... When you change SQL of a TADOQuery, is good use to
clear the parameters and recreate then using CreateParameter.
I would not rely on ParamCheck in runtime - since it leaves
the parameters' properties mostly undefined.
I've had such type of problem when relying on ParamCheck to
autofill the parameters - is rare but occurs.
Ah, if you go the CreateParameter route, create as first
parameter the #RETURN_VALUE one, since it'll catch the returned
value of the MSSQL SP.
The only time I've had a problem like this was when the DB Provider couldn't distinguish between Output (always sets it to null) and InputOutput (uses what you provide) parameters.
Ok, progress is made.. sort of.
#Robsoft was correct, setting the parameter direction to pdInput fixed the issue.
I traced into the VCL code, and it came down to TParameters.InternalRefresh.RefreshFromOleDB. This function is being called when I set the SQL.Text. Here's the (abridged) code:
function TParameters.InternalRefresh: Boolean;
procedure RefreshFromOleDB;
// ..
if OLEDBParameters.GetParameterInfo(ParamCount, PDBPARAMINFO(ParamInfo), #NamesBuffer) = S_OK then
for I := 0 to ParamCount - 1 do
with ParamInfo[I] do
begin
// ..
Direction := dwFlags and $F; // here's where the wrong value comes from
// ..
end;
// ..
end;
// ..
end;
So, OLEDBParameters.GetParameterInfo is returning the wrong flags for some reason.
I've verified that with the original connection, (dwFlags and $F) is 2 (DBPARAMFLAGS_ISOUTPUT), and with the new connection, it's 1 (DBPARAMFLAGS_ISINPUT).
I'm not really sure I want to dig any deeper than that, for now at least.
Until I have more time and inclination, I'll just make sure all parameters are set to pdInput before I open the query. Unless anyone has any more bright ideas now..?
Anyway, thanks everyone for your suggestions so far.
I was having a very similar issue using TADOQuery to retrieve some LDAP info, there is a bug in the TParameter.InternalRefresh function that causes an access violation even if your query has no parameters.
To solve this, simply set TADOQuery.ParamCheck to false.