MySQL optimizes the query to count scheduled items over time periods

In a planning application, I am working on a fairly complex database schema to describe a series of children assigned to groups on time slots on specific dates . Now in this scheme I want to query the database, how many planned children are in a certain group for a certain time interval in a certain date range.

DB schema

  • Time interval. The time interval has a specific start and end time (for example, 13:00 - 18:00). Time can vary in 15-minute steps. In our application, we want to plan a baby in a group throughout this entire time interval.
  • Time slice: for every 15 minutes within a 24-hour period there is a record of a temporary fragment (96). 15 minutes is the smallest planning block. A time interval is assigned to each slice covered between its beginning and end time (for example, the time interval 13: 00-18: 00 will have a record indicating the time slice [13:00, 13:15, 13:30 ... 17: 45]). This allows you to calculate how many children "took" the same slice of time for any date, time and date.
  • Kid: a child is just a planned organization
  • Group: a group is a physical location with a specific capacity
  • GroupAssignment: group assignment is time related. Between date 1 and 2 it can be group A, between date 2 and 3 it can be group B.
  • Employment: Planning master record. It has timeslot_id, kid_id, start and end date. Note: the baby is appointed on the day of the start and every subsequent 7 days until the end date.

SQL Database Schema

The number of records can be approximately obtained from the value of auto_increment. If not, I mentioned them manually.

CREATE TABLE `group_assignment_caches` (
  `group_id` int(11) DEFAULT NULL,
  `occupancy_id` int(11) DEFAULT NULL,
  `start` date DEFAULT NULL,
  `end` date DEFAULT NULL,
  KEY `index_group_assignment_caches_on_occupancy_id` (`occupancy_id`),
  KEY `index_group_assignment_caches_on_group_id` (`group_id`),
  KEY `index_group_assignment_caches_on_start_and_end` (`start`,`end`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/* (~1500 records) */

CREATE TABLE `kids` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `archived` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=592 DEFAULT CHARSET=utf8;

CREATE TABLE `occupancies` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `kid_id` int(11) DEFAULT NULL,
  `timeslot_id` int(11) DEFAULT NULL,
  `start` date DEFAULT NULL,
  `end` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_occupancies_on_kid_id` (`kid_id`),
  KEY `index_occupancies_on_timeslot_id` (`timeslot_id`),
  KEY `index_occupancies_on_start_and_end` (`start`,`end`)
) ENGINE=InnoDB AUTO_INCREMENT=2675 DEFAULT CHARSET=utf8;

CREATE TABLE `time_slices` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `start` time DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_time_slices_on_start` (`start`)
) ENGINE=InnoDB AUTO_INCREMENT=97 DEFAULT CHARSET=latin1;

CREATE TABLE `timeslot_slices` (
  `timeslot_id` int(11) DEFAULT NULL,
  `time_slice_id` int(11) DEFAULT NULL,
  KEY `index_timeslot_slices_on_timeslot_id` (`timeslot_id`),
  KEY `index_timeslot_slices_on_time_slice_id` (`time_slice_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/* (~1500 records) */

CREATE TABLE `timeslots` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `start` time DEFAULT NULL,
  `end` time DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8;

Current solution

, . , . 1 , 1 1 50 . 100 1000 , , . Ive , . , , !

SELECT subq.date, subq.group_id, subq.timeslot_id, MAX(subq.spots) AS max_spots
FROM (
    SELECT  di.date, 
            ts.start, 
            gac.group_id AS group_id, 
            tss2.timeslot_id AS timeslot_id, 
            COUNT(*) AS spots
    FROM date_intervals di, 
    timeslot_slices tss2,
    occupancies o
        JOIN timeslots t ON o.timeslot_id = t.id
        JOIN group_assignment_caches gac ON o.id = gac.occupancy_id
        JOIN timeslot_slices tss1 ON t.id = tss1.timeslot_id
        JOIN time_slices ts ON tss1.time_slice_id = ts.id
        JOIN kids k ON o.kid_id = k.id
    WHERE di.date BETWEEN gac.start AND gac.end
    AND di.date BETWEEN o.start AND o.end
    AND MOD(DATEDIFF(di.date, o.start),7)=0
    AND k.archived = 0
    AND tss1.time_slice_id = tss2.time_slice_id
    AND gac.group_id IN (3) AND tss2.timeslot_id IN (5)
    GROUP BY ts.start, di.date, group_id, timeslot_id
) subq
GROUP BY subq.date, subq.group_id, subq.timeslot_id

, . 1 (15 ) . . , .

Date_intervals . , , REPEAT . - "", 10-300 . .

, . , . , . , , date_intervals di, 122 .

+----+-------------+------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+---------+----------------------------+------+------------------------------------------------+
| id | select_type | table      | type   | possible_keys                                                                                                                          | key                                           | key_len | ref                        | rows | Extra                                          |
+----+-------------+------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+---------+----------------------------+------+------------------------------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                                                                                                                                   | NULL                                          | NULL    | NULL                       | 5124 | Using temporary; Using filesort                |
|  2 | DERIVED     | tss2       | ref    | index_timeslot_slices_on_timeslot_id,index_timeslot_slices_on_time_slice_id                                                            | index_timeslot_slices_on_timeslot_id          | 5       |                            |   42 | Using where; Using temporary; Using filesort   |
|  2 | DERIVED     | ts         | eq_ref | PRIMARY                                                                                                                                | PRIMARY                                       | 4       | ookidoo.tss2.time_slice_id |    1 |                                                |
|  2 | DERIVED     | tss1       | ref    | index_timeslot_slices_on_timeslot_id,index_timeslot_slices_on_time_slice_id                                                            | index_timeslot_slices_on_time_slice_id        | 5       | ookidoo.tss2.time_slice_id |    6 | Using where                                    |
|  2 | DERIVED     | o          | ref    | PRIMARY,index_occupancies_on_timeslot_id,index_occupancies_on_kid_id,index_occupancies_on_start_and_end                                | index_occupancies_on_timeslot_id              | 5       | ookidoo.tss1.timeslot_id   |    6 | Using where                                    |
|  2 | DERIVED     | k          | eq_ref | PRIMARY                                                                                                                                | PRIMARY                                       | 4       | ookidoo.o.kid_id           |    1 | Using where                                    |
|  2 | DERIVED     | gac        | ref    | index_group_assignment_caches_on_occupancy_id,index_group_assignment_caches_on_start_and_end,index_group_assignment_caches_on_group_id | index_group_assignment_caches_on_occupancy_id | 5       | ookidoo.o.id               |    1 | Using where                                    |
|  2 | DERIVED     | di         | range  | PRIMARY                                                                                                                                | PRIMARY                                       | 3       | NULL                       |    1 | Range checked for each record (index map: 0x1) |
|  2 | DERIVED     | t          | eq_ref | PRIMARY                                                                                                                                | PRIMARY                                       | 4       | ookidoo.o.timeslot_id      |    1 | Using where; Using index                       |
+----+-------------+------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+---------+----------------------------+------+------------------------------------------------+

(122 , )

date       group_id   timeslot_id max_spots            
+------------+----------+-------------+-----------+
| date       | group_id | timeslot_id | max_spots |
+------------+----------+-------------+-----------+
| 2012-08-20 |        3 |           5 |        12 |
| 2012-08-27 |        3 |           5 |        12 |
| 2012-09-03 |        3 |           5 |        12 |
| 2012-09-10 |        3 |           5 |        12 |
+------------+----------+-------------+-----------+
| 2014-11-24 |        3 |           5 |        15 |
| 2014-12-01 |        3 |           5 |        15 |
| 2014-12-08 |        3 |           5 |        15 |
| 2014-12-15 |        3 |           5 |        15 |
+------------+----------+-------------+-----------+

, . , , , (10-1000 ).

+4
2

, .

- :

CREATE TABLE `occupancy_caches` (
  `occupancy_id` int(11) DEFAULT NULL,
  `kid_id` int(11) DEFAULT NULL,
  `group_id` int(11) DEFAULT NULL,
  `client_id` int(11) DEFAULT NULL,
  `date` date DEFAULT NULL,
  `timeslot_id` int(11) DEFAULT NULL,
  `start` int(11) DEFAULT NULL,
  `end` int(11) DEFAULT NULL,
  KEY `index_occupancy_caches_on_date_and_client_id` (`date`,`client_id`),
  KEY `index_occupancy_caches_on_date_and_group_id` (`date`,`group_id`),
  KEY `index_occupancy_caches_on_occupancy_id` (`occupancy_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

group_assignment_caches (MOD (DATEDIFF...)). , 2.

, Occupancy_caches , . . , 400 (!) ... , - , .

, ...

0

. , .

. . ? , , , ? ?

, , . , , , , . , . , ?

SQLite, . (!) . :

$SQLiteDB = new PDO("sqlite::memory:");
$SQLiteDB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$SQL = "<any valid sqlite query>";
$SQLiteDB->query($SQL);

, SQL- sqlite. :

http://www.sqlite.org

, . , .

. , , . , . , .

, . , SQL- , .

+1

All Articles