-4

I have a stored procedure that looks like this:

create stored procedure aaa 
     @columnName nvarchar(10), 
     @comparisonParam nvarchar(10),
     @val nvarchar(100) 
as
    declare @date date
    set @date = convert(@val, date)

    exec('select * from Sheep where ' + @columnName + @comparisonParam  + @date )

When actually the query is supposed to be like this:

select * from Sheep where birth_date = 12-12-2000

When I run the procedure it doesn't work with date value, but with string and int it works.

halfer
  • 19,824
  • 17
  • 99
  • 186

5 Answers5

1

The date value must be quoted.

On a side note, I'd warn against doing this. If you need to build up dynamic sql you need to consider the risks such as: sql injection attacks, bad syntax, invalid semantics etc.

Consider using an existing component to build the query. A few examples:

.NET LINQ (to SQL/Entities) http://msdn.microsoft.com/en-us/library/bb397926.aspx

.NET SqlCommandBuilder http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommandbuilder.aspx


See Best way of constructing dynamic sql queries in C#/.NET3.5?

Community
  • 1
  • 1
Paul Fleming
  • 24,238
  • 8
  • 76
  • 113
0

Your date literal needs to be surrounded in single quotes (I use CHAR(39) usually since it is easier to read and doesn't require escaping). Otherwise you are saying:

WHERE birth_date = (12) - (12) - (2000)

Which resolves to:

WHERE birth_date = -2000

Which resolves to DATEADD(DAY, -2000, '1900-01-01') or:

WHERE birth_date = '1894-07-11'

This is probably not going to yield the results you want.

With typical SQL injection warnings in place of course, and assuming that @columnName is always a string or date/time column, here is how I would re-write your stored procedure (though I would probably try to avoid the dynamic SQL altogether if I could).

ALTER PROCEDURE dbo.aaa 
  @columnName       NVARCHAR(10), 
  @comparisonParam  NVARCHAR(10),
  @val              NVARCHAR(100)
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql NVARCHAR(MAX);

  SET @sql = N'SELECT * FROM dbo.Sheep WHERE '
    + QUOTENAME(@columnName) + @comparisonParam + CHAR(39) 
    + REPLACE(@val, CHAR(39), CHAR(39) + CHAR(39)) 
    + CHAR(39);

  EXEC sp_executesql @sql;
END
GO

In order to thwart potential issues you may want to add validation for columns and data types, and ensure that the operation is one you expect. e.g.

CREATE PROCEDURE dbo.bbb
  @columnName       NVARCHAR(10), 
  @comparisonParam  NVARCHAR(10),
  @val              NVARCHAR(100)
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @delimiter CHAR(1);

  SELECT @delimiter = CASE 
    WHEN [system_type_id] IN 
      (104,48,52,56,127,59,60,62,106,108,122) THEN '' -- numeric
    WHEN [system_type_id] IN 
      (35,40,41,42,43,58,61,99,167,175,231,239) THEN CHAR(39) -- string
    END FROM sys.columns WHERE [object_id] = OBJECT_ID(N'dbo.Sheep')
    AND name = @columnName;

  IF @delimiter IS NULL
  BEGIN
    RAISERROR('Column ''%s'' was not found or an unexpected data type.', 11, 1, 
      @columnName);
    RETURN;
  END

  IF @comparisonParam NOT IN (N'=', N'>=', N'<=', N'<', N'>', N'LIKE')
  BEGIN
    RAISERROR('Comparison param ''%s'' was not valid.', 11, 1, @comparisonParam);
    RETURN;
  END

  DECLARE @sql NVARCHAR(MAX);

  SET @sql = N'SELECT * FROM dbo.Sheep WHERE '
           + QUOTENAME(@columnName) + ' ' + @comparisonParam + ' ' 
           + @delimiter + REPLACE(@val, CHAR(39), CHAR(39) + CHAR(39)) 
           + @delimiter;

  EXEC sp_executesql @sql;
END
GO

Now make sure you use an unambiguous date format for your string literals. 12-12-2000 is not a good choice. 20001212 is much better.

There are possibly some ways to do this without dynamic SQL - I gave a very simplified answer here. This may be feasible depending on the data types, the number of potential columns, and the number of operations you want to support.

Community
  • 1
  • 1
Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
0

Build your dynamic SQL using a typed date parameter. Use sp_executesql which allows to pass parameter definitions and parameter values to the embedded SQL:

create procedure aaa 
   @columnName nvarchar(10), 
   @comparisonParam nvarchar(10),
   @val nvarchar(100)
as
    declare @date date, @sql nvarchar(max);
    set @date = convert(@val, date);

    -- Note how @date is a *variable* in the generated SQL:
    set @sql =N'select * from Sheep where ' + 
            quotename(@columnName) + @comparisonParam  + N'@date';

    -- Use sp_executesql and define the type and value of the variable
    exec sp_executesql @sql, N'@date date', @date;
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • The reason I didn't use a parameter for the value is that I'm not convinced the column being specified is always going to be a date. – Aaron Bertrand Jul 15 '12 at 14:42
  • @aaron I haven't considered that, true. I wanted to show that there is a way to avoid locale issues completely, by going strong typed. Of course, the parameter type should match the column type for added sargability. – Remus Rusanu Jul 15 '12 at 14:46
  • Locale can still be an issue if the date is passed in to @val as a string. – Aaron Bertrand Jul 15 '12 at 16:32
  • True, but that is beyond my control. The point is that one can build a type safe, locale agnostic, dynamic SQL. – Remus Rusanu Jul 15 '12 at 16:40
0
create stored procedure aaa 
     @columnName nvarchar(10), 
     @comparisonParam nvarchar(10),
     @val nvarchar(100) 
as
    declare @date date
    set @date = convert(@val, date)

    exec('select * from Sheep where ' + @columnName + @comparisonParam  + @date )
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
VINAI
  • 1
-2

You need to create table valued function for this rather than creating a stored procedure.

You can use any table valued function like

SELECT * from dbo.CallMyFunction(parameter1, parameter2

eg.

CREATE FUNCTION Sales.ufn_SalesByStore (@storeid int)
RETURNS TABLE
AS
RETURN 
(
    SELECT P.ProductID, P.Name, SUM(SD.LineTotal) AS 'Total'
    FROM Production.Product AS P 
    JOIN Sales.SalesOrderDetail AS SD ON SD.ProductID = P.ProductID
    JOIN Sales.SalesOrderHeader AS SH ON SH.SalesOrderID = SD.SalesOrderID
    JOIN Sales.Customer AS C ON SH.CustomerID = C.CustomerID
    WHERE C.StoreID = @storeid
    GROUP BY P.ProductID, P.Name
);
GO

See this for reference http://msdn.microsoft.com/en-us/library/ms191165(v=sql.105).aspx

EDIT

Instead of using dynamic sql try giving a thought on

SELECT * FROM 
FROM    [dbo].[Person]
WHERE   ([PersonID] = @PersonID
         OR @AreaID IS NULL
        )
        AND (([Code] BETWEEN @Code AND CHAR(255))
             OR @Code IS NULL
            )
        AND (([Name] BETWEEN @Name AND CHAR(255))
             OR @Name IS NULL
            )
        AND (([Notes] BETWEEN @Notes AND CHAR(255))
             OR @Notes IS NULL
            )
Shantanu Gupta
  • 20,688
  • 54
  • 182
  • 286
  • guys' i have a problem' the procedure cuts of part of my column name' why is that?? – Rahelly Gelfanboim Jul 23 '12 at 05:21
  • @RahellyGelfanboim for a string column I have used `[Code] BETWEEN @Code AND CHAR(255)) OR @Code IS NULL` for string filtering. Use `=` in place of `BETWEEN` i.e. `[Code] = @Code OR @Code IS NULL` – Shantanu Gupta Jul 23 '12 at 05:43