Av rating:
Total votes: 11
Total comments: 6


Arthur Fuller
A case for canned SQL
18 January 2006

Database Opinion: A case for canned SQL

Like a Phoenix, the dynamic SQL versus canned procedures and user functions argument has resurfaced on the SQL newsgroups. Many of the proponents of the dynamic argument are web or Access developers, or developers of some other front end. Developers in the canned camp tend to be DBAs or developers with some depth of knowledge in stored procedures and functions.

My characterization has, of course, loaded the dice in favor of the latter group, of which I consider myself a member. After exploring some of my reasons and experiences, I’ll cite a simple example to illustrate my conclusion that canned SQL is always the better option.

How they differ

Many of the forms of dynamic SQL I use in my web and client applications enable the user to specify complex selection combinations. In fact, there might be 20 such controls on the selection form.

Given this complexity, I have no choice but to examine each control in the front end. If it is not null, then I create a clause in the SELECT statement that corresponds to the column and the value specified. I build up the statement control by control, then issue it.

Conversely, canned SQL is available to any and all front ends that need it, without duplicating the logic in each front end. It also runs more quickly and precludes SQL injection almost entirely.

I admit that in some instances there is no alternative but to write dynamic SQL statements and send them to the back end for execution. But in my experience, these instances are few and far between.

Code is worth a thousand words

Below is an example that uses only three controls, but should be sufficient to illustrate my point. It also reveals a cool syntax trick that you can apply to your own projects.

Suppose your application presents a form with three lists, from which the user can select a maximum of one item from each. Let’s also suppose that this form talks to a single table—or view or UDF—with columns A, B and C.

The possible combinations of the three columns are:

A

all As.

AB

all specified As that are also Bs.

ABC

all specified As that are also Bs and also Cs.

B

all Bs.

BC

all specified Bs that are also Cs.

(BA

same as AB above.)

C

all Cs.

CA

all Cs that are also As.

(CB

same as BC above.)

To construct the dynamic SQL necessary to accommodate these variations, you would examine each control, determine if it is not NULL, and gradually build up the SQL statement.

My counter proposal is a single procedure or user function that can handle all combinations of these values. I have chosen to write a user function because table user functions can be invoked as part of a view, stored procedure, or user function. But it could just as easily be a stored procedure.

Here is the code to create the sample table:

/****** Object: Table [dbo].[ABC] Script Date: 01/07/2006 20:22:20 ******/

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[ABC]
(

  [PK] [int] IDENTITY(1,1) NOT NULL,
[A] [int] NOT NULL,
[B] [int] NOT NULL,
[C] [int] NOT NULL, 
CONSTRAINT [PK_ABC] PRIMARY KEY CLUSTERED

  [PK] ASC 
)
WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
)
ON [PRIMARY]

Here are a few sample rows:

PK  A B C 
 
1 1 1 1
2 1 2 1
3 1 2 3
4 2 1 1
5 2 2 1
6 2 2 2
7 3 1 1
8 3 1 2
9 3 2 1
10 3 2 2
11 3 2 3
12 3 3 3
13 2 2 3
14 2 3 2
15 1 3 2
16 1 1 3

Here is the code to create the table function:

— ============================================= 
— Author: Arthur Fuller
— Create date: 2006/01/07
— Description: Optional Parameters
— =============================================
CREATE FUNCTION [dbo] .[ABC_fnt]
(

  — Add the parameters for the function here
@A int,
@B int,
@C int

)
RETURNS TABLE
AS
RETURN
(

  — Add the SELECT statement with parameter references here
SELECT *
FROM ABC
WHERE

    (A = @A OR @A IS NULL) 
  AND 
    (B = @B OR @B IS NULL)

  AND
    (C = @C OR @C IS NULL)

)    

The trick in this function is the comparison between the expected parameters and themselves. Think about the logic for a moment before proceeding.

Finally, here are several sample calls:

SELECT * FROM dbo .ABC_fnt ( NULL, 1 , 1 ) 
SELECT * FROM dbo .ABC_fnt ( 1 , 1 , 1 )
SELECT * FROM dbo .ABC_fnt ( NULL, NULL, 1 )
SELECT * FROM dbo .ABC_fnt ( 1 , NULL, 1 )
SELECT * FROM dbo .ABC_fnt ( NULL, NULL, 2 )
SELECT * FROM dbo .ABC_fnt ( 1 , NULL, 3 )
 

Conclusion

My simple example uses only three arguments and assumes exact matches on each specified argument. But it should be obvious that you can do the same thing with 20 arguments. With a little more work, the function can be modified to handle LIKE arguments and their converse as well.

My point is not that this approach will cover every possible situation, but that it will cover many situations in which you might have used dynamic SQL instead. By now, the gains should be obvious.



This article has been viewed 4781 times.
Arthur Fuller

Author profile: Arthur Fuller

Arthur Fuller has been developing database applications for more than 20 years. He frequently works with Access ADPs, Microsoft SQL 2000 and 2005, MySQL, and .NET.

Search for other articles by Arthur Fuller

Rate this article:   Avg rating: from a total of 11 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: great
Posted by: Anonymous (not signed in)
Posted on: Friday, September 08, 2006 at 6:47 AM
Message: You are my guru

Subject: Combination
Posted by: GSquared (view profile)
Posted on: Thursday, February 22, 2007 at 4:20 PM
Message: Have the stored procedure/function determine which rows to display, have the front-end dynamically determine which columns to return and in what sequence. Best of both worlds.

Subject: Problem with the table scan function
Posted by: papabear (view profile)
Posted on: Friday, March 09, 2007 at 9:55 AM
Message: This is useful on small tables, but the larger the table the more overhead occurs. For example, running a test against a database that has 11,000 entries against an indexed key results in a estimated query cost of 96.51% vs 3.49% where no (@myparam is null) is used.

This is because the sql "Select myId from myTable where myId = @myId" uses an Index Seek whereas "Select myId from myTable where (@myId is null or myId = @myId)" uses an Index Scan.

On the flipside this is very useful if you want to avoid dynamic sql and are willing to sacrifice some performance to minimize stored procedure overhead.

Subject: fyi - here is a good link on more info on this format
Posted by: papabear (view profile)
Posted on: Friday, March 09, 2007 at 10:06 AM
Message: http://www.sommarskog.se/dyn-search.html.

Again, I really like this format because of the ease of maintenance, plus you are returning the same basic table regardless of how many parameters you call (which can make coding on the back end a lot easer!). But remember that this will cause a performance hit.

Subject: What about dynamic columns
Posted by: Anonymous (not signed in)
Posted on: Monday, April 30, 2007 at 9:22 AM
Message: What about dynamic columns you let the user decide what columns they want to see on a form?

Subject: Can this reduce overhead performance?
Posted by: Samip (view profile)
Posted on: Tuesday, May 15, 2007 at 3:55 AM
Message: can this effect performance than oring condition?

CREATE FUNCTION [dbo] .[ABC_fnt] (
— Add the parameters for the function here
@A int,
@B int,
@C int )
RETURNS TABLE AS RETURN
(
— Add the SELECT statement with parameter peferences here
SELECT * FROM ABC WHERE (A = isnull(@A, A))AND (B = isnull(@B, B)) AND (C = isnull(@C, C))

We are facing lots of problems for this. Please send any better solution, if its there.

 









Phil Factor
The Data Center that Exploded
 A while back, in a Simple-Talk editorial meeting, someone bet Phil that he couldn't come up with a Halloween story.... Read more...



 View the blog
SQL Toolbelt 2008: Predominantly an Engineering Task
 The conversion of the Red-Gate tools to be compatible with SQL Server 2008 might not seem, on first... Read more...

Audit Crosschecks
 In this short article, the second of a 2-part series, William suggests a solution, using SQL Data... Read more...

SQL Response: The dim sum interview
 Richard Morris met David and Nigel of the SQL Response team, in a dim sum Restaurant in Cambridge. They... Read more...

XML Jumpstart Workbench
 In which Robyn and Phil decide that the best way of starting to learn XML is to jump in and take a ride... Read more...

Discovering Security Uses for SQL Compare
 Much of the security of SQL Server is implemented as part of the database schema. This provides some... Read more...

Beginning SQL Server 2005 Reporting Services Part 1
 Steve Joubert begins an in-depth tour of SQL Server 2005 Reporting Services with a step-by-step guide... Read more...

Ten Common Database Design Mistakes
 Database design and implementation is the cornerstone of any data centric project (read 99.9% of... Read more...

SQL Server Full Text Search Language Features
 SQL Full-text Search (SQL FTS) is an optional component of SQL Server 7 and later, which allows fast... Read more...

Beginning SQL Server 2005 Reporting Services Part 2
 Continuing his in-depth tour of SQL Server 2005 Reporting Services, Steve Joubert demonstrates the most... Read more...

Executing SSIS Packages
 Nigel Rivett demonstrates how to execute all SSIS packages in a given folder using either an SSIS... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk