Mysql – Optimizing a stored procedure

MySQL

I have done some optimizations on the below mysql stored procedure, however it still runs pretty slow:

CREATE DEFINER=`xxx`@`%` PROCEDURE `testing`(IN _MONTH INT, IN _YEAR INT, IN _PRODUCTS TEXT)
BEGIN

set @_START = date(_YEAR * 10000 + _MONTH * 100 + 1); 
set @_END = date_add(date(_YEAR * 10000 + _MONTH * 100 + 1), interval 1 month);

DROP TABLE IF EXISTS ReportThreeTotal;
DROP TABLE IF EXISTS ReportThreeAbandondCalls;
DROP TABLE IF EXISTS ReportThreeSpillGain;
DROP TABLE IF EXISTS ReportThreeStamp;
DROP TABLE IF EXISTS ReportThreeResults;

CREATE TEMPORARY TABLE IF NOT EXISTS ReportThreeTotal AS (
    SELECT count(*) as 'count', DAYOFWEEK(`end`) as 'DOW' 
    FROM callstats.cdrdata_archive 
    where end >= @_START and end < @_END
    AND calling IN (
        SELECT Number FROM callstats.products WHERE products.id IN (_PRODUCTS) )
    GROUP BY DAYOFWEEK(`end`)
);

CREATE TEMPORARY TABLE IF NOT EXISTS ReportThreeAbandondCalls AS (
    SELECT count(*) as 'count', DAYOFWEEK(`end`) as 'DOW'
    FROM callstats.cdrdata_archive 
    WHERE calling IN (
        SELECT Number 
        FROM callstats.products 
        WHERE products.id IN (_PRODUCTS) ) 
    AND end >= @_START and end < @_END
    AND CAST(`duration` AS UNSIGNED) < 30 
    GROUP BY DAYOFWEEK(`end`)
);

CREATE TEMPORARY TABLE IF NOT EXISTS ReportThreeSpillGain AS (
SELECT (case when (SUM(count) is null) then 0 else SUM(count) end) as 'SPILLGAIN', DAYOFWEEK(`end`) as 'DOW'
FROM (SELECT count(*) as 'count', called, end
    FROM (
        SELECT called, calling, count(*) as 'count', end
        FROM cdrdata_archive 
        JOIN products ON Number = calling 
        WHERE end >= @_START AND end < @_END
        AND products.id IN (_PRODUCTS) 
        AND called != 'Anonymous' 
        GROUP BY called, calling, DAYOFWEEK(`end`) 
        ORDER BY `end` ASC ) 
    AS tbl1 
     GROUP BY called having count(*) > 1) 
 AS tbl2
 GROUP BY DAYOFWEEK(`end`));

CREATE TEMPORARY TABLE ReportThreeStamp(stamp text, DOW INT);
    SET @Counter = 1;
    while @Counter <= 7 do
        INSERT INTO ReportThreeStamp VALUES(TIMESTAMP_FROM_DATE(_YEAR * 10000 + _MONTH * 100 + @Counter), @Counter);
        SET @Counter=@Counter+1;
    end while;

CREATE TEMPORARY TABLE IF NOT EXISTS ReportThreeResults AS (
SELECT CONCAT('[',S.`stamp`,',', (
    case when (A.`count` = 0) 
         then 0 
         else (A.`count` / T.`count`) * 100 end),']') AS 'JSON_PERCENT',
    CONCAT('[',S.`stamp`,',',T.`count`,']') AS 'JSON_TOTAL',
    A.`DOW` as 'DOTW',
 A.`COUNT` as 'ATOTAL', T.`COUNT` as 'TOTAL',
SG.SPILLGAIN as 'GAINPER', SG.SPILLGAIN as 'SPILLPER'
FROM ReportThreeAbandondCalls A LEFT JOIN ReportThreeTotal T ON A.`DOW` = T.`DOW` LEFT JOIN ReportThreeSpillGain SG ON SG.`DOW` = A.`DOW` 
LEFT JOIN ReportThreeStamp S ON A.`DOW` = S.`DOW`);

SELECT * FROM ReportThreeResults;

DROP TABLE IF EXISTS ReportThreeTotal;
DROP TABLE IF EXISTS ReportThreeAbandondCalls;
DROP TABLE IF EXISTS ReportThreeSpillGain;
DROP TABLE IF EXISTS ReportThreeStamp;
DROP TABLE IF EXISTS ReportThreeResults;

END

There is an index set for the end field which should increase the performance.
Any ideas how to optimize this stored procedure?

Best Answer

  • "IN ( SELECT ... )" optimizes very poorly; switch to a JOIN.

  • CREATE/DROP TABLE is slow, especially on Windows.

  • ReportThreeStamp seems static; why not build it as a non-TEMPORARY TABLE, and do so only once.

  • (case when (SUM(count) is null) then 0 else SUM(count) end) --> IFNULL(SUM(count), 0))

  • Your temp tables seem to have no indexes; this leads to slow table scans. Suggest this syntax to create an index while creating and populating:

    CREATE TABLE x ( PRIMARY KEY (y) ) SELECT ...

  • How many rows in the tables? If millions, we should talk about "summary tables" and how they could probably speed up the processing 10-fold.