DekGenius.com
Team LiB
Previous Section Next Section

Dynamic Queries

Dynamic queries are queries that are non-deterministic. In other words, the actual query run, or the number of columns returned, are not known at design time but are determined by parameters at run time.

This is made possible by the ability of the EXECUTE statement and a system stored procedure called sp_executesql to execute scripts from strings.

For example, you can get the same result from

SELECT * FROM [Order Details]

by running the following

EXECUTE ('SELECT * FROM [Order Details]')

The difference is that with the EXECUTE statement, the script is in a string that we can programmatically manipulate. Consider the following:

DECLARE @SQLSCRIPT as nvarchar(1000)
DECLARE @TableName as nvarchar(100)

SET @SQLSCRIPT = 'SELECT * FROM '
SET @TableName = '[Order Details]'

EXECUTE (@SQLSCRIPT + @TableName)
Note 

Spaces in the string that you use are important.

By simply changing the value of @TableName to Orders, we can do a select on the Orders table.

DECLARE @SQLSCRIPT as nvarchar(1000)
DECLARE @TableName as nvarchar(100)

SET @SQLSCRIPT = 'SELECT * FROM '
SET @TableName = 'Orders'
EXECUTE (@SQLSCRIPT + @TableName)

This is all very fine and great, but the EXECUTE statement does have certain limitations. One of the major limitations is that the script in use cannot return any value. To overcome this, you have to use sp_executesql.

sp_executesql

sp_executesql has a simple syntax:

sp_executesql <Script>, [ <Parameter list> , Parameter1,
  n...]
  • <Script>: A string that contains the script you want to execute

  • <Parameter list>: A string that contains the declaration of parameters used in <Script>

  • Parameter1, n…: The parameters you want to pass in <Script> and/or the return value from <Script>

Consider the following situation. Let’s assume that you need a stored procedure to return the rows for a given table name, a column name, and a search value. Let’s call this stored procedure sp_getRows. The script for sp_getRows is shown in Listings D-9a to D-9c.

The first step, as usual, is to declare the stored procedure and variable required.

Listing D-9a
Start example
if exists
  (select * from dbo.sysobjects
   where id = object_id(N'sp_GetRows') and
   OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure sp_GetRows
GO

create procedure sp_GetRows
/******************************************
 Procedure:   sp_UpdateContactInfo
 Date:        16-July-2002
Author:      Ryan N Payet
 Description: Return rows for the given table and
              search criteria    

 Version History
 ---------------
 0.1, 16-July-2002, Ryan N Payet:
      Procedure Created
******************************************/
  @TableName nvarchar(100),
  @ColumnName nvarchar(100) = '1',
  @Value sql_variant = '1',
  @Operator nvarchar (15) = '='
AS
BEGIN
  /*Declare variable to hold script*/
  DECLARE @@SQLScript as nvarchar(4000)
  DECLARE @@SQLPARAMETER as nvarchar(1000)
  
  /*Declare variable to manage errors*/
  DECLARE @@ERRORMSG as varchar(256)
  DECLARE @Error_Code as integer
End example
Note 

SLQ_variant is a data type that can store values of any SQL Server data types with the exception of text, ntext, timestamp, image, and SQL_ variant.

We must make sure that the table name and column name given are valid.

Listing D-9b
Start example
  /*Check if table exist*/
  if NOT exists
     (SELECT * FROM dbo.sysobjects
      WHERE id = object_id(@TableName)
      and OBJECTPROPERTY(id, N'IsUserTable') = 1)
  BEGIN
     SET @@ERRORMSG = 'Table "' + @TableName
        + '" does not exist in database'
     RAISERROR ( @@ERRORMSG , 16, 1)
     RETURN 1
  END
  
  /*If you base a number as column name then
    do not check for existence of column.
    This allows it to return all rows
    by passing a column 1 and a value 1
    */
  IF ISNUMERIC(@ColumnName) = 0
  BEGIN
    IF NOT exists
     ( SELECT * FROM syscolumns
       WHERE (id = OBJECT_ID(@TableName))
       AND UPPER([name]) = UPPER(@ColumnName))
    BEGIN
     SET @@ERRORMSG =''
         + 'Column "' + @ColumnName
         + '" does not exist in table "'
         + @TableName + '"'
     RAISERROR (@@ERRORMSG , 16, 1)
     RETURN 1
    END /*IF NOT exists */
  END /*IF ISNUMERIC(@ColumnName) = 0*/
End example

We can then build our script. The point to note here is that @Value is not a variable but a string literal in the @@SQLScript variable. We also get the value of @@ERRORR right after the SELECT in the script. We build a parameter list to be used with sp_executesql in @@SQLPARAMETER and when it is passed in sp_executesql. @error_code is defined as an OUTPUT type. This is necessary so that we can get a value from the script passed in sp_excutesql. Also, the parameters must be used in the same order that they are defined in @@ SQLPARAMETER.

Listing D-9c
Start example
  /*Build Script*/
  SET @@SQLScript = ''
    + 'SELECT * FROM ' + @TableName + Char(13)
    + 'WHERE ' + @ColumnName
    + ' ' +  @Operator +
    + ' @Value' + Char(13)
    + 'SET @Error_Code = @@ERROR'
  /*Build parameter for script*/
  SET @@SQLPARAMETER = '@Value sql_variant,'
    + ' @error_code integer OUTPUT'

  
  /*This so that we know the script
    executed properly and set error_code to
    0 if there is no error*/
  SET @error_code = 1

  EXECUTE sp_executesql @@SQLScript,
          @@SQLPARAMETER ,
          @Value,
          @error_code OUTPUT 
 
  IF @error_code <> 0
  BEGIN
     SET @@ERRORMSG ='Error in script'
     RAISERROR (@@ERRORMSG , 16, 1)
     RETURN 1
  END

  RETURN 0

END
GO
End example

To test the stored procedure, try the
Note 

CHAR(13) is ASCII character 13 (carriage return) and CHAR(39) is single quote character, useful when writing strings within a string.

following script:

EXECUTE sp_GetRows 'Orders', 'EmployeeID', 6

With a lot of practice and creative programming, you can do a great deal more with dynamic scripts. One of the down sides of dynamic scripting, apart from the obvious complexity, is performance. Since the real SQL logic of the script is in string variables, you lose the benefit of executing plans and optimization by the database engine. This is not noticeable if the generated script is simple, but it can be an issue if the generated script is very complex.

Team LiB
Previous Section Next Section