I am writing a program in C# that runs some select statements using parameters passed to sp_executesql. One issue that I'm running into when testing is that, whether I get the the commands executed from SQL Profiler or from a watch in Visual Studio, the values of the parameters are specified at the end of statement rather than being explicitly specified in-line in the query. For testing purposes, I would like a quick way to substitute the parameter values for the parameters.
So, instead of:
exec sp_executesql N'
SELECT CustomerName
FROM CustomerTable ct WITH(NOLOCK)
WHERE ct.CustomerId <> #CustomerId
AND ct.ItemId <> #ItemId
AND ct.TransactionId = #TransactionId'
,N'#CustomerId bigint,#ItemId nvarchar(1),#TransactionId nvarchar(30), #CustomerId = 3000, #ItemId = N'4', #TransactionId=N'43281'
I want:
exec sp_executesql N'
SELECT CustomerName
FROM CustomerTable ct WITH(NOLOCK)
WHERE ct.CustomerId = 3000
AND ct.ItemId <> N'4'
AND ct.TransactionId = N'43281''
Please don't pay too much attention to the syntax of the example, since it is just being used to demonstrate the concept. Does anyone know a fast way to do this? Basically, I would like to have it substituted for testing purposes, as it will make it easier for me to modify conditions to test how they affect the results returned. I would appreciate any help anyone can give. Thanks.
Parameterized sp_executesql has many benefits, including
By explicit parameterizing you are giving the chance for SQL to cache decent query plans on definite types
By parameterizing it helps prevent nasties like SQL injection attacks, but also avoids the need to escape problematic characters.
So even if you do manage to 'unparameterize' the generated sp_executesql, if you execute the inline sql, the query plan could be significantly different to the parameterized version, and you would also need to do escaping etc (i.e. it wouldn't be suitable for apples vs apples testing).
The only reason I can think of why you wouldn't want parameterized sp_executesql would be for ease of readability?
Edit: Trying to substitute would be dependent on what technology you are using
As #mellamokb suggested, if you are using ExecuteReader this could be quite straightforward
Assuming your code was something like
string sqlCmd = "SELECT CustomerName
FROM CustomerTable ct WITH(NOLOCK)
WHERE ct.CustomerId <> #CustomerId
AND ct.ItemId <> #ItemId
AND ct.TransactionId = #TransactionId";
cmd.CommandText = sqlCmd;
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("CustomerId", DbType.Int32, myCustomerId));
cmd.Parameters.Add(new SqlParameter("ItemId", DbType.String, myItemId));
..
cmd.ExecuteReader()
You could then add code to build your test query:
string sqlMyTest = sqlCmd.Replace("#CustomerId", myCustomerId.ToString());
sqlMyTest = sqlMyTest.Replace("#ItemId", specialEscapeFunction(myItemId));
.. do something with sqlMyTest
However an ORM like Linq2SQL or EF would not be as easy
customerTable.Where(c => (c.CustomerId != myCustomerId) && (c.ItemId != myItemId) && (c.TransactionId == myTransactionId))
Possibly a tool like LinqPad might help?
Related
I have a stored procedure that accepts an optional #ID param. When the param is passed in, I want the WHERE statement to include something like id = #ID, otherwise, when #ID is null, I don't want it to be filtered.
For example:
#ID BIGINT = NULL
SELECT * from myTable
WHERE
CASE
WHEN #ID IS NOT NULL THEN mytable.id = #ID
END
I am running this in SQL server 2016 and it says bad syntax near mytable.id = #ID. Can CASE be used in this way or should I try a different SQL method for this?
The only other option I considered to accomplish this was by using IF conditions in my stored procedure, but that didn't seem possible either based on my searches.
CASE is an expression, not a statement. It is not used to control flow like this and it will not work.
Your logic would need to be something like this.
Where mytable.id = ISNULL(#ID, mytable.id)
I should caution you that this pattern can lead to some poor performance. For a more detailed explanation and some other options you should check out this article. http://www.sqlinthewild.co.za/index.php/2009/03/19/catch-all-queries/
A bad-performance approach would be:
WHERE ISNULL(#ID,mytable.id) = mytable.id
A better-performance approach would be:
if(#ID IS NULL)
select * from ... without the WHERE condition
else
do your query with the condition mytable.id = #ID
Or build the query dynamically in the stored proc and execute it through sp_executesql passing parameters
Note: If the table is small enough, stick to simplicity and use the first option.
How do I check if a table has contents? Honestly I still don't have any initial codes for it. Do I code it in VB or just use a query?
you definitely need to ask SQL server, so why not just querying 'SELECT COUNT(*) FROM TABLE" ?
which you can put it in a stored procedure.
even you can parametrise the procedure with table name and run exec sql command.
I would not use SELECT COUNT(*) unless you actually care about the actual count - this can be an expensive operation on large tables. If all you care about is whether there are rows or not, much better to use:
IF EXISTS (SELECT TOP (1) NULL FROM dbo.MyTable)
BEGIN
PRINT 'There are rows.';
END
ELSE
BEGIN
PRINT 'There are no rows.';
END
If you don't need to be up-to-the-second, you can use the DMVs for this kind of check. Specifically:
SELECT SUM(row_count)
FROM sys.dm_db_partition_stats
WHERE [object_id] = OBJECT_ID('dbo.MyTable');
The DMV is not always precise due to in-flight transactions and deferred updates, but is generally reliable for ballpark estimates.
Install Microsoft SQL Server Management Studio. You can then view the contents and structure of your tables, easily, through the GUI.
Dim con = New SqlConnection("Data Source=servername;Initial Catalog=myDb;Integrated Security=True")
Dim cmd = New SqlCommand("SELECT Count(*) FROM myTable", con)
con.Open()
Dim count As Integer = CInt(cmd.ExecuteScalar())
con.Close()
If I have a stored proc in SQL Server 2008, I know I can run it from management studio like so:
exec rpt_myproc #include_all = 1, #start_date = '1/1/2010'
But I'm using an ad-hoc query tool that wasn't returning any results. So I asked it to give me the SQL it was running and it returns this:
SELECT DISTINCT TOP 100000
[dbo].[rpt_myproc].[company_name] AS 'company name',
[dbo].[rpt_myproc].[order_number] AS 'order number]
FROM [dbo].[rpt_myproc]
WHERE
([dbo].[rpt_myproc].[PARAM_start_date] IN ('1/1/2010'))
AND ([dbo].[rpt_myproc].[PARAM_include_all] IN ('1'))
I'm not familiar with that syntax. Is that even possible? The ad-hoc tool isn't failing, but it may be swallowing that error. Then again, maybe it's just giving me a shorthand which it will use translate to the proper syntax later. But if so, why would it give it to me in this form?
I can't seem to get that SQL to execute in Management Studio, so I was wondering if something like that were possible?
I understand that this is more than 3 years old, but in case anybody else is looking for an answer to this question. I had to deal with this reporting platform, Izenda, and have found that stored procedures are treated differently than the output from the "sql" icon. Here is what happens when you select sp as data source
A dynamic sql is build
It creates a two temporary tables with all of the columns that your sp is returning
The first temp table is populated with the result from your stored procedure
The second temp table is populated with the result plus the value of your input parameter.
A statement is created that queries these two temporary tables
Please note that if you don't feed it a parameter it will execute with a default value of empty string '' which will most likely return no data.
In my opinion, horrible idea to handle stored procs which is a good reason why we are planning to drop them for some other reporting solution.
You can insert the first result set of a stored procedure into a temporary table:
SELECT *
INTO #YourProc
FROM OPENROWSET('SQLNCLI',
'server=SERVERNAME\INSTANCENAME;trusted_connection=yes',
'set fmtonly off; exec rpt_myproc')
There's like 3 ways to do this, see this blog post. If you know the output beforehand, you can do it without the remote query.
What tool are you using? You should be able to specify the query type (i.e. SQL, or stored proc, etc)
Haven't used that tool before but a quick google came up with this example (not sure if it will help you)
Using a stored procedure in 5.x
This example uses a stored procedure to populate a table before report design or execution. As shown in the comments, the table StoredProcResults must already exist. Every time a report is created or viewed this stored procedure will update the results of the StoredProcResults table. For 6.x follow these instructions but treat the SP as a regular datasource.
// Customize a report on the fly prior to execution on a per user basis
public override void PreExecuteReportSet(Izenda.AdHoc.ReportSet reportSet){
/*this sample uses the adventure works database Here is the definition of the table and stored procedure created for this report.
CREATE TABLE [dbo].[StoredProcResults](
[ProductID] [int] NOT NULL,
[OrderQuantity] [int] NOT NULL,
[Total] [int] NOT NULL,
[DueDate] [smalldatetime] NOT NULL
) ON [PRIMARY]
CREATE PROCEDURE DoCustomAction (
#date1 as smalldatetime,
#date2 as smalldatetime
) AS
BEGIN
insert into StoredProcResults
select ProductID,OrderQty,LineTotal,ModifiedDate
from Sales.SalesOrderDetail
where ModifiedDate >= #date1 and ModifiedDate <= #date2
END
*/
string currentReportName = HttpContext.Current.Request.QueryString["rn"];
if (currentReportName == "StoredProcExample") {
SqlConnection myConnection = new SqlConnection(Izenda.AdHoc.AdHocSettings.SqlServerConnectionString);
SqlCommand myCommand = new SqlCommand("DoCustomAction", myConnection);
// Mark the Command as a SPROC
myCommand.CommandType = System.Data.CommandType.StoredProcedure;
// Add Parameters to SPROC
SqlParameter parameterdate1 = new SqlParameter("#date1", System.Data.SqlDbType.SmallDateTime);
parameterdate1.Value = "1/1/2003";
myCommand.Parameters.Add(parameterdate1);
SqlParameter parameterdate2 = new SqlParameter("#date2", System.Data.SqlDbType.SmallDateTime);
parameterdate2.Value = "12/31/2003";
myCommand.Parameters.Add(parameterdate2);
try{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally{
myConnection.Close();
}
}
}
Are you sure it is a sproc? I've never heard or seen a usage of doing a direct select from a sproc.
What I have seen that works and functions exactly as your code seems to be working is table-valued functions, which are functions, that can take parameters and return a "SELECT FROMable" table just like this (in essence giving you a 'parameterized' view).
I have an application that needs to return search results from a SQL Server 2008 database. I would like to use a single stored procedure to return the results but I am finding that as I build the stored procedure it is full of many Else .. Else If statements with the query repeated over and over with slight variations depending on the users search criteria.
Is there a better way to go about this? I am trying to avoid writing dynamic SQL because I would like the benefits of an execution plan but I am thinking there must be a better way. Does anyone have any suggestions or perhaps examples of how best to design a stored procedure that has to deal with many search parameters, many of which may be NULL? Thank you.
Not really.
With SQL Server 2005 and above with statement level recompilation then there is less of a penalty with OR clauses, just maintenance complexity.
Using Richard Harrison's approach makes it worse because OR is not-sargable, runs slowly, most likely won't use indexes.
Dynamic SQL opens up SQL injection, quoting and caching issues.
This leaves sp_executesql as per CountZero's answer which still requires building up strings.
The solution may not be code based... do you really need to search on all fields at any one time? I'd try to split into simple and advanced searches, or work out what the most common are and try to cover these queries.
I've always done this by using default values and conditions; e.g.
CREATE PROCEDURE [dbo].[searchForElement]
(
#Town nvarchar(100) = '',
#County nvarchar(100) = '',
#postcode nvarchar(100) = ''
)
AS
BEGIN
SET NOCOUNT ON;
SELECT <fields>
FROM table
WHERE
(#Town = '' OR Town LIKE '%'+#Town+'%')
AND (#County = '' OR County LIKE '%'+#County+'%')
AND (#postcode = '' OR postcode LIKE '%'+#PostCode +'%')
END
Edit:
As #gbn correctly advises the above will result in an index scan which may be a problem for large tables. If this is a problem the solution is to below using ISNULL and the fact that adding NULL to anything results in NULL it will allow an index seek because the '%' is understood by the optimiser (tested on SQL2008). This may be less readable but it makes better use of the indexes.
CREATE PROCEDURE [dbo].[searchForElement]
(
#Town nvarchar(100) = NULL,
#County nvarchar(100) = NULL,
#postcode nvarchar(100) = NULL
)
AS
BEGIN
SET NOCOUNT ON;
SELECT <fields>
FROM table
WHERE Town LIKE ISNULL('%'+#Town+'%', '%')
AND County LIKE ISNULL('%'+#County+'%', '%')
AND Postcode LIKE ISNULL('%'+#PostCode +'%', '%')
END
I always run into this problem myself. Tend to use dynamic SQL, as long as you use the sp_executesql then the optimizer will try to use the same execution plan.
http://ayyanar.blogspot.com/2007/11/performance-difference-between-exec-and.html
I'm after a simple stored procedure to drop tables. Here's my first attempt:
CREATE PROC bsp_susf_DeleteTable (#TableName char)
AS
IF EXISTS (SELECT name FROM sysobjects WHERE name = #TableName)
BEGIN
DROP TABLE #TableName
END
When I parse this in MS Query Analyser I get the following error:
Server: Msg 170, Level 15, State 1, Procedure bsp_susf_DeleteTable, Line 6
Line 6: Incorrect syntax near '#TableName'.
Which kind of makes sense because the normal SQL for a single table would be:
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'tbl_XYZ')
BEGIN
DROP TABLE tbl_XYZ
END
Note the first instance of tbl_XYZ (in the WHERE clause) has single quotes around it, while the second instance in the DROP statement does not. If I use a variable (#TableName) then I don't get to make this distinction.
So can a stored procedure be created to do this? Or do I have to copy the IF EXISTS ... everywhere?
You should be able to use dynamic sql:
declare #sql varchar(max)
if exists (select name from sysobjects where name = #TableName)
BEGIN
set #sql = 'drop table ' + #TableName
exec(#sql)
END
Hope this helps.
Update: Yes, you could make #sql smaller, this was just a quick example. Also note other comments about SQL Injection Attacks
Personally I would be very wary of doing this. If you feel you need it for administrative purposes, please make sure the rights to execute this are extremely limited. Further, I would have the proc copy the table name and the date and the user executing it to a logging table. That way at least you will know who dropped the wrong table. You may want other protections as well. For instance you may want to specify certain tables that cannot be dropped ever using this proc.
Further this will not work on all tables in all cases. You cannot drop a table that has a foreign key associated with it.
Under no circumstances would I allow a user or anyone not the database admin to execute this proc. If you havea a system design where users can drop tables, there is most likely something drastically wrong with your design and it should be rethought.
Also, do not use this proc unless you have a really, really good backup schedule in place and experience restoring from backups.
You'll have to use EXEC to execute that query as a string. In other words, when you pass in the table name, define a varchar and assign the query and tablename, then exec the variable you created.
Edit: HOWEVER, I don't recommend that because someone could pass in sql rather than a TableName and cause all kinds of wonderful problems. See Sql injection for more information.
Your best bet is to create a parameterized query on the client side for this. For example, in C# I would do something like:
// EDIT 2: on second thought, ignore this code; it probably won't work
SqlCommand sc = new SqlCommand();
sc.Connection = someConnection;
sc.CommandType = Command.Text;
sc.CommandText = "drop table #tablename";
sc.Parameters.AddWithValue("#tablename", "the_table_name");
sc.ExecuteNonQuery();