T-SQL Conditional Order By

后端 未结 5 577
闹比i
闹比i 2020-12-03 00:21

I am trying to write a stored procedure that returns a list of object with the sort order and sort direction selected by the user and passed in as sql parameters.

Le

相关标签:
5条回答
  • 2020-12-03 01:05
    declare @str varchar(max)
    set @str = 'select * from Product order by ' + @sortOrder + ' ' + @sortDir
    exec(@str)
    
    0 讨论(0)
  • 2020-12-03 01:07

    You need a case statement, although I would use multiple case statements:

    order by (case when @sortOrder = 'name' and @sortDir = 'asc' then name end)  asc,
             (case when @sortOrder = 'name' and @sortDir = 'desc' then name end) desc,
             (case when @sortOrder = 'created_date' and @sortDir = 'asc' then created_date end) asc,
             (case when @sortOrder = 'created_date' and @sortDir = 'desc' then created_date end) desc
    

    Having four different clauses eliminates the problem of converting between types.

    0 讨论(0)
  • 2020-12-03 01:09

    CASE is an expression that returns a value. It is not for control-of-flow, like IF. And you can't use IF within a query.

    Unfortunately, there are some limitations with CASE expressions that make it cumbersome to do what you want. For example, all of the branches in a CASE expression must return the same type, or be implicitly convertible to the same type. I wouldn't try that with strings and dates. You also can't use CASE to specify sort direction.

    SELECT column_list_please
    FROM dbo.Product -- dbo prefix please
    ORDER BY 
      CASE WHEN @sortDir = 'asc' AND @sortOrder = 'name' THEN name END,
      CASE WHEN @sortDir = 'asc' AND @sortOrder = 'created_date' THEN created_date END,
      CASE WHEN @sortDir = 'desc' AND @sortOrder = 'name' THEN name END DESC,
      CASE WHEN @sortDir = 'desc' AND @sortOrder = 'created_date' THEN created_date END DESC;
    

    An arguably easier solution (especially if this gets more complex) is to use dynamic SQL. To thwart SQL injection you can test the values:

    IF @sortDir NOT IN ('asc', 'desc')
      OR @sortOrder NOT IN ('name', 'created_date')
    BEGIN
      RAISERROR('Invalid params', 11, 1);
      RETURN;
    END
    
    DECLARE @sql NVARCHAR(MAX) = N'SELECT column_list_please
      FROM dbo.Product ORDER BY ' + @sortOrder + ' ' + @sortDir;
    
    EXEC sp_executesql @sql;
    

    Another plus for dynamic SQL, in spite of all the fear-mongering that is spread about it: you can get the best plan for each sort variation, instead of one single plan that will optimize to whatever sort variation you happened to use first. It also performed best universally in a recent performance comparison I ran:

    http://sqlperformance.com/conditional-order-by

    0 讨论(0)
  • 2020-12-03 01:20

    There are multiple ways of doing this. One way would be:

    SELECT *
    FROM
    (
      SELECT
      ROW_NUMBER() OVER ( ORDER BY
      CASE WHEN @sortOrder = 'name' and @sortDir = 'asc' then name
      END ASC,
      CASE WHEN @sortOrder = 'name' and @sortDir = 'desc' THEN name
      END DESC,
      CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'asc' THEN created_date
      END ASC,
      CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'desc' THEN created_date
      END ASC) RowNum
      *
    )
    order by 
    RowNum
    

    You can also do it using dynamic sql.

    0 讨论(0)
  • 2020-12-03 01:24

    I stumbled on this when I was trying to do a similar thing, however the select statement I was creating includes a rather large and ugly string concatenation which severely limited my ability to use a dynamic SQL approach. If you have simple search cases, this is a decent solution.

    That being said, another way to do this in SQL server specifically is with a table variable. This requires more overhead and complexity but it also gives you a lot of flexibility.

    DECLARE @RawProducts TABLE (
        product_id int, 
        name varchar(50), 
        value int, 
        created_date datetime
    )
    INSERT INTO @RawProducts
    SELECT * FROM [Product]
    
    IF @sortOrder = 'name' AND @sortDir = 'asc' BEGIN
        SELECT * FROM @RawProducts
        ORDER BY [name] ASC
    END
    
    IF @sortOrder = 'name' AND @sortDir = 'desc' BEGIN
        SELECT * FROM @RawProducts
        ORDER BY [name] desc
    END
    
    IF @sortOrder = 'created_date' AND @sortDir = 'asc' BEGIN
        SELECT * FROM @RawProducts
        ORDER BY [created_date] ASC
    END
    
    IF @sortOrder = 'created_date' AND @sortDir = 'desc' BEGIN
        SELECT * FROM @RawProducts
        ORDER BY [created_date] desc
    END
    

    One drawback to this methodology is that you will need to add a case block for each and every case you want to order by but you would have to do that with any solution that does not involve dynamic SQL.

    The only time I would recommend this method is if you have a relatively small data set and you have easy to discern search cases.

    0 讨论(0)
提交回复
热议问题