sevenWonders
sevenWonders

Reputation: 185

How to optimise my complex MySQL query?

Table

Each row represents a video that was on air at particular time on particular date. There are about 1600 videos per day.

CREATE TABLE `air_video` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `date` DATE NOT NULL,
    `time` TIME NOT NULL,
    `duration` TIME NOT NULL,
    `asset_id` INT(10) UNSIGNED NOT NULL,
    `name` VARCHAR(100) NOT NULL,
    `status` VARCHAR(100) NULL DEFAULT NULL,
    `updated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE INDEX `date_2` (`date`, `time`),
    INDEX `date` (`date`),
    INDEX `status` (`status`),
    INDEX `asset_id` (`asset_id`)
)
ENGINE=InnoDB

Task

There are two conditions.

  1. Each video must be shown not more than 24 times per day.
  2. Each video must be in rotation no longer than 72 hours.

In rotation means time span between the fist and the last time the video was on air.

So I need to select all videos that violate those conditions, given user-specified date range.

The result must be grouped by day and by asset_id (video id). For example:

      date      asset_id          name           dailyCount      rotationSpan
   2012-04-27      123      whatever_the_name         35              76
   2012-04-27      134      whatever_the_name2        39              20
   2012-04-28      125      whatever_the_name3        26              43

Query

By now I have written this query:

SELECT
    t1.date, t1.asset_id, t1.name,
    (SELECT 
        COUNT(t3.asset_id) 
        FROM air_video AS t3 
        WHERE t2.asset_id = t3.asset_id AND t3.date = t1.date
    ) AS 'dailyCount',
    MIN(CONCAT(t2.date, ' ', t2.time)) AS 'firstAir',
    MAX(CONCAT(t2.date, ' ', t2.time)) AS 'lastAir',
    ROUND(TIMESTAMPDIFF(
        MINUTE,
        MIN(CONCAT(t2.date, ' ', t2.time)),
        MAX(CONCAT(t2.date, ' ', t2.time)) 
    ) / 60) as 'rotationSpan'
FROM
    air_video AS t1
INNER JOIN
    air_video AS t2 ON
        t1.asset_id = t2.asset_id 
WHERE
    t1.status NOT IN ('bumpers', 'clock', 'weather')
    AND t1.date BETWEEN '2012-04-01' AND '2012-04-30'
GROUP BY
    t1.asset_id, t1.date
HAVING
    `rotationSpan` > 72
    OR `dailyCount` > 24
ORDER BY
    `date` ASC,
    `rotationSpan` DESC,
    `dailyCount` DESC    

Problems

  1. The bigger the range between user specified days - the longer it takes to complete the query (for a month range it takes about 9 sec)
  2. The lastAir timestamp is not the latest time the video was aired on particular date but the latest time it was on air altogether.

Upvotes: 4

Views: 149

Answers (1)

Nightwolf
Nightwolf

Reputation: 951

If you need to speed up your query you need to remove the select sub query on line 3. To still have that count you can inner join it again in the from clause with the exact parameters you used initially. This is how it should look:

SELECT
    t1.date, t1.asset_id, t1.name,
    COUNT(t3.asset_id) AS 'dailyCount',
    MIN(CONCAT(t2.date, ' ', t2.time)) AS 'firstAir',
    MAX(CONCAT(t2.date, ' ', t2.time)) AS 'lastAir',
    ROUND(TIMESTAMPDIFF(
        MINUTE,
        MIN(CONCAT(t2.date, ' ', t2.time)),
        MAX(CONCAT(t2.date, ' ', t2.time)) 
    ) / 60) as 'rotationSpan'
FROM
    air_video AS t1
INNER JOIN
    air_video AS t2 ON
        (t1.asset_id = t2.asset_id)
INNER JOIN
    air_video AS t3 
        ON (t2.asset_id = t3.asset_id AND t3.date = t1.date)
WHERE
    t1.status NOT IN ('bumpers', 'clock', 'weather')
    AND t1.date BETWEEN '2012-04-01' AND '2012-04-30'
GROUP BY
    t1.asset_id, t1.date
HAVING
    `rotationSpan` > 72
    OR `dailyCount` > 24
ORDER BY
    `date` ASC,
    `rotationSpan` DESC,
    `dailyCount` DESC

Since t2 is not bound by date, you are obviously looking at the whole table, instead of the date range.

Edit: Due to a lot of date bindings the query still ran too slowly. I then took a different approach. I created 3 views (which you obviously can combine into a normal query without the views, but I like the end result query better)

--T1--

CREATE VIEW t1 AS select date,asset_id,name from air_video where (status not in ('bumpers','clock','weather')) group by asset_id,date order by date;

--T2--

CREATE VIEW t2 AS select t1.date,t1.asset_id,t1.name,min(concat(t2.date,' ',t2.time)) AS 'firstAir',max(concat(t2.date,' ',t2.time)) AS 'lastAir',round((timestampdiff(MINUTE,min(concat(t2.date,' ',t2.time)),max(concat(t2.date,' ',t2.time))) / 60),0) AS 'rotationSpan' from (t1 join air_video t2 on((t1.asset_id = t2.asset_id))) group by t1.asset_id,t1.date;

--T3--

CREATE VIEW t3 AS select t2.date,t2.asset_id,t2.name,count(t3.asset_id) AS 'dailyCount',t2.firstAir,t2.lastAir,t2.rotationSpan AS rotationSpan from (t2 join air_video t3 on(((t2.asset_id = t3.asset_id) and (t3.date = t2.date)))) group by t2.asset_id,t2.date;

From there you can then just run the following query:

SELECT
    date,
    asset_id, 
    name,
    dailyCount,
    firstAir,
    lastAir,
    rotationSpan
FROM
    t3
WHERE
    date BETWEEN '2012-04-01' AND '2012-04-30'
    AND (
        rotationSpan > 72
        OR 
        dailyCount > 24
    )
ORDER BY
    date ASC,
    rotationSpan DESC,
    dailyCount DESC

Upvotes: 3

Related Questions