【发布时间】:2015-04-21 06:04:06
【问题描述】:
架构
数据库架构已简化
事件表
此表存储事件。
CREATE TABLE `Events` (
`event_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`isPublic` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
地方表
存储位置的简单表。一个事件可以在多个地方进行。
CREATE TABLE `Places` (
`place_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
PRIMARY KEY (`place_id`),
KEY `latind` (`latitude`,`longitude`)
) ENGINE=InnoDB CHARSET=latin1;
规则表
存储事件时间表的表。一个事件可以有多个时间表。所有日期均采用 unixtimestamp 格式。 Regular 表示此规则有一些重复的时间表,存储在 RegularRules 表中。
CREATE TABLE `Rules` (
`rule_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`start_date` int(11) NOT NULL,
`end_date` int(11) NOT NULL,
`regular` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`rule_id`),
KEY `endindx` (`end_date`)
) ENGINE=InnoDB CHARSET=latin1;
常规规则
以下列格式存储可重复计划的表。 day_start/end 表示从一天的开始 (00:00) 到事件开始的秒数。例如,事件发生在每周一的 10:00 到 18:00。我们会将start_date 和end_date 存储在规则表中,这些值代表事件的时间限制。在 RegularRules 表中,mon_start 中有 36000 个,mon_end 中有 64800 个。
CREATE TABLE `RegularRules` (
`repetition_id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`rule_id` bigint(20) unsigned NOT NULL,
`mon_start` int(11) DEFAULT NULL,
`tue_start` int(11) DEFAULT NULL,
`wed_start` int(11) DEFAULT NULL,
`th_start` int(11) DEFAULT NULL,
`fr_start` int(11) DEFAULT NULL,
`sat_start` int(11) DEFAULT NULL,
`sun_start` int(11) DEFAULT NULL,
`mon_end` int(11) DEFAULT NULL,
`tue_end` int(11) DEFAULT NULL,
`wed_end` int(11) DEFAULT NULL,
`th_end` int(11) DEFAULT NULL,
`fr_end` int(11) DEFAULT NULL,
`sat_end` int(11) DEFAULT NULL,
`sun_end` int(11) DEFAULT NULL,
PRIMARY KEY (`repetition_id`),
KEY `fk_rule_id_regularrules_idx` (`rule_id`),
CONSTRAINT `fk_rule_id_regularrules` FOREIGN KEY (`rule_id`)
REFERENCES `Rules` (`rule_id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB CHARSET=latin1;
活动-地点-规则
连接上述所有表的表。
CREATE TABLE EPR (
`holding_id` bigint(30) NOT NULL AUTO_INCREMENT,
`event_id` bigint(20) unsigned NOT NULL,
`place_id` bigint(20) unsigned NOT NULL,
`rule_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`holding_id`),
UNIQUE KEY `compound` (`place_id`,`event_id`,`rule_id`),
KEY `FK_Places-Company Events-Rules_Events_event_id` (`event_id`),
KEY `FK_Places-Company Events-Rules_Places_place_id` (`place_id`),
KEY `FK_Places-Company Events-Rules_Rules_rule_id` (`rule_id`),
CONSTRAINT `FK_Places-Company Events-Rules_Events_event_id`
FOREIGN KEY (`event_id`) REFERENCES `Events` (`event_id`) ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `FK_Places-Company Events-Rules_Rules_rule_id`
FOREIGN KEY (`rule_id`) REFERENCES `Rules` (`rule_id`) ON DELETE CASCADE ON
UPDATE CASCADE,
CONSTRAINT `fk_place_id_pcerc` FOREIGN KEY (`place_id`)
REFERENCES `Places` (`place_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB CHARSET=latin1;
存储函数
有两个存储函数。 GETBEGINS 和 GETENDS。参数:rule_id、timestamp、curtimestamp。Timestamp为当天的unixtimestamp,curtimestamp为当天开始的unixtimestamp。
这些功能的工作方式如下。对于每条规则,它们都返回规则的开始(开始)和结束(结束)。如果规则不可重复,它们会返回存储在规则表中的start_date 和end_date。如果规则是可重复的,则它们构造 RegularRules 表中最接近的非空 day_start/day_end 的 begins 和 ends。例如,有一个事件有 2 个规则。第一个不可重复,以 start_timestamp 开头并以 end_timestamp 结尾。第二个是可重复的,只有两个非空字段:mon_start = 36000 和 mon_end = 64800。 GETBEGINS 将根据当天开始的当前 unixtimetamp 和当前 unixtimestamp 将 mon_start 转换为 unixtimestamp。 GETBEGINS 工作类似。如有必要,将提供这些功能的代码。
有问题的查询
这个查询应该返回ongoing在地理上和时间上最接近的事件。地方应该是不同的。所以查询应该为每个地方返回按时间顺序最近的事件,最后根据时间和距离对结果值进行排序,并使用一些系数(我认为排序部分将移至 PHP 等服务器端语言。如果您有关于这种排序我对任何解决方案持开放态度)。例如,附近的 10 家电影院有 5 部电影。每个电影院有 100 个时间表。查询应该为每个电影院返回按时间顺序最近的电影,然后根据时间和距离两个值对电影和电影院进行排序。
预期查询
latpoin,longpoint,r - 是传递给脚本的坐标和半径,
curstamp - 一天开始的unixtimestamp,
时间戳 - 当前的 unixtimestamp
SELECT
epr.event_id,
epr.place_id,
epr.rule_id,
(6371 * ACOS(COS(RADIANS(latpoint)) * COS(RADIANS(latitude)) *
COS(RADIANS(longitude) - RADIANS(longpoint)) + SIN(RADIANS(latpoint)) *
SIN(RADIANS(latitude)))) AS distance,
p.latitude,
p.longitude,
GETBEGINS(r.rule_id, curstamp, timestamp) AS begins,
GETENDS(r.rule_id, curstamp, timestamp) AS ends,
MIN(ABS(GETBEGINS(r.rule_id, curstamp, timestamp) - timestamp)) AS
time_min
FROM
Events e
INNER JOIN
EPR epr ON e.event_id = epr.event_id
INNER JOIN
Places p ON epr.place_id = p.place_id
INNER JOIN
Rules r ON epr.rule_id = r.rule_id
WHERE
r.end_date >= timestamp
AND latitude BETWEEN latpoint - (r / 111.045) AND latpoint + (r /
111.045)
AND longitude BETWEEN longpoint - (r / (111.045 *
COS(RADIANS(latpoint)))) AND longpoint + (r / (111.045 *
COS(RADIANS(latpoint))))
AND e.isPublic = 1
GROUP BY epr.place_id
如主题中所述,此查询混合了返回值。更具体地说,它匹配错误的 rule_id,开始,结束到 place_id 组。
此外,此查询的性能很差。表格大小:事件 - 3000 行,地点 - 8000 行,规则 18000 行,EPR-15000 行。使用索引提示 (use index compound) 和 1.2 时,这些查询大约工作 1.8 秒。不使用索引提示查询会进行全表扫描。
我已经阅读了关于这个主题的official mysql docs。然而,由于用户计算的值(GETBEGINS 和 GETENDS),他们的解决方案并不适用。
问题
预期查询部分中提供的查询由于mysql handles group by 的方式而存在分组最小问题。所以可能的解决方案是以这种方式使函数 GETBEGINS 和 GETENDS 用户定义的聚合函数 mysql 可能会返回适当的结果?这个解决方案合乎逻辑吗? 将函数GETBEGINS 和GETENDS 聚合起来有帮助吗?在这种情况下mysql会返回适当的数据吗?
结论
欢迎对提供的解决方案、新解决方案、关于索引和数据库架构的 cmets 发表评论。
【问题讨论】:
标签: mysql indexing group-by user-defined-functions stored-functions