locked
Is there a better way? RRS feed

  • Question

  • Is there a better way to write this query? 

    DECLARE @startDate AS DATE, 
    @endDate AS DATE;
    
    SET @startDate = (GetDate() - 183);
    
    SET @endDate = (GetDate());
    
    WITH   [dates] ([Date])
    AS     (SELECT @startDate AS [Date]
            UNION ALL
            SELECT DATEADD(d, 1, [Date]) AS [Date]
            FROM   [dates]
            WHERE  [Date] < @endDate)
    SELECT [Date],
           (SELECT COUNT(*)
            FROM   cat_transaction
            WHERE  CONVERT (VARCHAR (10), trxdate, 20) = [dates].date AND trxsubtype ='prn' ) AS 'Print',
           
           (SELECT COUNT(*)
            FROM   cat_transaction
            WHERE  CONVERT (VARCHAR (10), trxdate, 20) = [dates].date AND trxsubtype ='cpy' ) AS 'Copy' 
            
    FROM   [dates]
    OPTION (MAXRECURSION 0);

    The reason I ask is it takes almost a minute to run on a table that has 121183 rows.  Here is an example of the table structure that I'm querying:

    trxdate trxsubtype
    2011-10-10 12:53:36.000 cpy
    2011-10-10 13:50:12.000 prn
    2011-10-10 13:53:15.000 prn
    2011-10-10 13:54:52.000 prn
    2011-10-10 13:55:53.000 prn
    2011-10-10 13:57:05.000 prn
    2011-10-10 13:59:09.000 prn
    2011-10-10 14:01:08.000 prn
    2011-10-10 14:16:32.000 prn
    2011-10-10 14:18:24.000 prn

    and we're trying to get it to display as:

    Date Print Copy
    7/1/2012 17 0
    7/2/2012 633 40
    7/3/2012 328 53
    7/4/2012 29 12
    7/5/2012 325 41
    7/6/2012 221 27
    7/7/2012 7 0
    7/8/2012 55 7
    7/9/2012 481 67
    7/10/2012 449 76

    Friday, January 11, 2013 7:52 PM

Answers

  • Your conversion trxdate to varchar is one obvous slowdown.  Casting a datetime to date will remove the time portion; there are other ways to achieve that as well. 

    left join your [table of dates] to cat_transaction on cast(trxdate as date) = dates.date and using summing (e.g., sum(case trxsubtype  when 'prn' then 1 else 0 end) as 'Print'. Your table of dates can be the cte you have now, a UDF that does the same thing, a calendar table, etc. Obviously you would want to limit the rows selected from cat_transaction to the 2 types of interest. 

    You might want to consider materializing trxdate as an actual date in some fashion - especially if you do this a lot.

    • Proposed as answer by Naomi N Sunday, January 13, 2013 5:21 PM
    • Marked as answer by Iric Wen Monday, January 21, 2013 9:19 AM
    Friday, January 11, 2013 9:06 PM

All replies

  • Don't know if it will be any faster, but this produces the desired output:

    Declare	@tvTable Table (
    	DateField	datetime
    	,TypeField	char(3)
    )
    
    Insert	@tvTable
    Values	('20130101', 'cpy')
    		,('20130101', 'cpy')
    		,('20130101', 'prn')
    		,('20130101', 'cpy')
    		,('20130101', 'prn')
    		,('20130102', 'cpy')
    		,('20130103', 'cpy')
    		,('20130103', 'prn')
    		,('20130103', 'cpy')
    		,('20130105', 'prn')
    		,('20130105', 'prn')
    		,('20130104', 'prn')
    		,('20130103', 'prn')
    
    ;with cteDates as
    (
    	Select	DATEADD([month], DATEDIFF([month], '20000101', CURRENT_TIMESTAMP), '19991101') dtDate
    	Union All
    	Select	DATEADD(dd, 1, dtDate)
    	From	cteDates
    	Where	DATEADD(dd, 1, dtDate) <= CURRENT_TIMESTAMP
    )
    ,cteCountCpy as
    (
    	Select	Count(DateField) CountCpy
    			,DateField
    	From	@tvTable
    	Where	TypeField = 'cpy'
    	Group By DateField
    )
    ,cteCountPrn as
    (
    	Select	Count(DateField) CountPrn
    			,DateField
    	From	@tvTable
    	Where	TypeField = 'prn'
    	Group By DateField
    )
    Select	d.dtDate
    		,Copy = IsNull(cpy.CountCpy, 0)
    		,Prnt = IsNull(prn.CountPrn, 0)
    From	@tvTable t
    join	cteDates d
    	on	t.DateField = d.dtDate
    left join cteCountCpy cpy
    	on	t.DateField = cpy.DateField
    left join cteCountPrn prn
    	on	t.DateField = prn.DateField
    Group By d.dtDate, cpy.CountCpy, prn.CountPrn


    And, side comment, anyone know why the forum is now partially in Hebrew?

    Edit: Can also do it this way, which is less verbose, and perhaps more straightforward:

    ;with cteDates as
    (
    	Select	DATEADD([month], DATEDIFF([month], '20000101', CURRENT_TIMESTAMP), '19991101') dtDate
    	Union All
    	Select	DATEADD(dd, 1, dtDate)
    	From	cteDates
    	Where	DATEADD(dd, 1, dtDate) <= CURRENT_TIMESTAMP
    )
    Select	d.dtDate
    		,Copy = Sum(Case
    						When t.TypeField = 'cpy' Then 1 Else 0 End)
    		,Prnt = Sum(Case
    						When t.TypeField = 'prn' Then 1 Else 0 End)
    From	@tvTable t
    join	cteDates d
    	on	t.DateField = d.dtDate
    Group By d.dtDate

    • Edited by dgjohnson Friday, January 11, 2013 8:25 PM
    Friday, January 11, 2013 8:19 PM
  • You should use a calendar table instead of generating one on the fly.

    Here is the query with that table:

    SELECT c.DateCol, ct.PRINT, ct.Copy FROM CalendarTable 
    LEFT JOIN (SELECT CONVERT (VARCHAR (10), trxdate, 20) dtCol,  
    SUM(CASE WHEN trxsubtype ='prn' THEN 1 ELSE 0 END)  AS 'Print',
    ,SUM(CASE WHEN trxsubtype ='cpy' THEN 1 ELSE 0 END) AS 'Copy' 
     FROM   cat_transaction
     GROUP BY  CONVERT (VARCHAR (10), trxdate, 20)
     ) ct ON c.DateCol=CreateTable.dtCol
     WHERE c.DateCol>=@startDate AND c.DateCo<=@endDate
    

    Friday, January 11, 2013 8:59 PM
  • Your conversion trxdate to varchar is one obvous slowdown.  Casting a datetime to date will remove the time portion; there are other ways to achieve that as well. 

    left join your [table of dates] to cat_transaction on cast(trxdate as date) = dates.date and using summing (e.g., sum(case trxsubtype  when 'prn' then 1 else 0 end) as 'Print'. Your table of dates can be the cte you have now, a UDF that does the same thing, a calendar table, etc. Obviously you would want to limit the rows selected from cat_transaction to the 2 types of interest. 

    You might want to consider materializing trxdate as an actual date in some fashion - especially if you do this a lot.

    • Proposed as answer by Naomi N Sunday, January 13, 2013 5:21 PM
    • Marked as answer by Iric Wen Monday, January 21, 2013 9:19 AM
    Friday, January 11, 2013 9:06 PM
  • To add to the answers here, you should rewrite where clause to avoid manipulation on the filtered columns. This will allow SQL Server to efficiently use an index. You currently have this one twice in your query.

    CONVERT (VARCHAR (10), trxdate, 20) = [dates].date

    Friday, January 11, 2013 9:08 PM