简体   繁体   中英

Optimizing a dynamic sorting SQL query

This query (I've simulated it to show you. Otherwise, it is longer with many more lists of columns, some more tables joined; and finally to sort by every column as required by the user) takes too long, and can timeout from the UI sometimes. I was trying to optimize the stored procedure to reduce loading time without affecting the retrieved lists. I was considering to select * from the temporary table and then Order from there. That could help somehow. I also considered using a dynamic sql without specifying the @sortidr (DESC/ASC), but the user should have the flexibility to order either way.

I was wondering if anyone can suggest better ways of optimizing it.

CREATE PROCEDURE get_lists   
    @employeeid  int
    @ColumnName varchar(100) = NULL,
    @sortidr varchar(4) = NULL, 
    @start_date datetime = NULL

AS

CREATE #tempemp
(emp_id int, first_name varchar(20), last_name varchar(20), SSN int) 

INSERT INTO #tempemp
    SELECT team_action, rec_id, total_records, cfirst_name, efirst_name, has_desc, category_name
    FROM 
        (SELECT COALESCE(lt.description, lt.action) AS team_action
        ,cl.rec_id
        ,total_records = count(*) Over()
        ,c.cfirst_name
        ,e.efirst_name
        , has_desc = cld.changelog_id IS NOT NULL THEN 1 ELSE 0 END
        , ct.category_name
        FROM claimll cll 
        JOIN itemtype lt ON cll.clLog_id = lt.clLog_id 
        LEFT JOIN categgory ct ON ct.id=clc.cat_id
        LEFT JOIN clients c ON ct.client_id = c.client_id 
        LEFT JOIN employees e ON cll.emp_id = e.emp_id 
        LEFT JOIN detail d ON d.change_id = cll.change_id
        WHERE cll.change_date > @start_date
        AND cll.emp_id = @employeeid OR cll.by_employeeid  = @employeeid 
        ) t 

ORDER BY
      CASE WHEN @sortidr = 'asc' AND @ColumnName='team_action' THEN team_action END, 
      CASE WHEN @sortidr = 'desc' AND @ColumnName = 'team_action' THEN team_action END DESC, 
      CASE WHEN @sortidr = 'asc' AND @ColumnName='rec_id' THEN  rec_id END, 
      CASE WHEN @sortidr = 'desc' AND @ColumnName = 'rec_id' THEN rec_id END DESC, 
      CASE WHEN @sortidr = 'asc' AND @ColumnName='total_records' THEN  total_records END, 
      CASE WHEN @sortidr = 'desc' AND @ColumnName = 'total_records' THEN total_records END DESC, 
      CASE WHEN @sortidr = 'asc' AND @ColumnName='cfirst_name' THEN  cfirst_name END, 
      CASE WHEN @sortidr = 'desc' AND @ColumnName = 'cfirst_name' THEN cfirst_name END DESC, 
      CASE WHEN @ColumnName IS NULL THEN first_name END DESC

I'm not at a PC so I won't be able to add code until later but you have provided enough information for me to provide some good insight.

First - the optimizer will never be able to leverage an index to prevent a sort when you order by a CASE statement. That said, there are many reasons your query could be slow; you should remove that logic then test performance to understand if that's the culprit.

Based on the limited info I have my suggestion would be update your proc to: 1. Use dynamic SQL to build your temp table, as you have done, but without the CASE statement.

  1. Included in this ^^^ dynamic SQL should be code to build a clustered index with @columnName as the cluster key and using @sortDir to dictate the index sort order.

Now you have a temp table correctly indexed based on user supplied parameters.

  1. Add a second dynamic SQL statement that does a SELECT * FROM #temp... You will again use @columnName and @sortOrder to build a SARGable ORDER BY (one which will leverage the index from Step #1.

  2. Test with different parameters always looking at the Actual Execution plan to further tune your indexes.

Hope this helps. I'll add demo code a little later if you need.

Updated On 2/22/2019 to include examples:

Here's a stored proc that does what I'm describing (note the comments)--

SET NOCOUNT ON;
USE tempdb
GO

IF OBJECT_ID('dbo.DynamicSortProc','P') IS NOT NULL DROP PROC dbo.DynamicSortProc;
GO
CREATE PROC dbo.DynamicSortProc @sortby NVARCHAR(100), @sortorder NVARCHAR(4)
AS 
BEGIN 
  -- 1. Populate the temp table (sample data)
  IF OBJECT_ID('##tmp','U') IS NOT NULL DROP TABLE ##tmp;

  SELECT   SomeId = IDENTITY(INT,1,1), ColA, ColB
  INTO     ##tmp
  FROM     (VALUES(1,3),(6,5),(2,8),(5,5),(2,6),(5,1)) AS f(ColA,ColB);
  -- NOTE: On SQL 2014+ SELECT INTO is often faster than the standard create+insert
  ;
  -- 2. Setup @sortby
  SET @sortby = QUOTENAME(@sortby)+' '+CASE @sortorder WHEN 'DESC' THEN 'DESC' ELSE '' END;

  -- 3. Set up the Dynamic SQL
  DECLARE @createIndex  NVARCHAR(4000) = 'CREATE CLUSTERED INDEX x ON ##tmp ('+@sortby+') ';
  DECLARE @executeQuery NVARCHAR(4000) = 'SELECT t.* FROM ##tmp AS t ORDER BY '+@sortby;

  -- 4. Execute the queries
  EXEC sys.sp_executesql @createIndex;
  EXEC sys.sp_executesql @executeQuery;
END
GO

You can test like this:

EXEC dbo.DynamicSortProc 'ColA', 'ASC';
EXEC dbo.DynamicSortProc 'ColA', 'DESC';
EXEC dbo.DynamicSortProc 'ColB', 'ASC';
EXEC dbo.DynamicSortProc 'ColB', 'DESC';

In each case they sort as expected and there is no sort in the output query.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM