我正在为使用 MySQL 作为数据库的应用程序重写一份报告。目前,该报告正在使用来自 php 的大量繁重工作,它创建数组,将它们重新存储到临时数据库中,然后从该临时数据库生成结果。
重写大部分代码的主要目标之一是简化和清理我的大量旧代码,我想知道是否可以简化以下过程,或者甚至更好地仅在 MySQL 上完成,让 php 处理将数据分发到客户端。
我将使用一个虚构的场景来描述我正在尝试做的事情:
让我们假设下表(请注意,在实际应用中,该表的信息实际上是从多个表中提取的,但这应该可以清楚地表达要点):
+----+-----------+--------------+--------------+
| id | location | date_visited | time_visited |
+----+-----------+--------------+--------------+
| 1 | place 1 | 2012-04-20 | 11:00:00 |
+----+-----------+--------------+--------------+
| 2 | place 2 | 2012-04-20 | 11:06:00 |
+----+-----------+--------------+--------------+
| 3 | place 1 | 2012-04-20 | 11:06:00 |
+----+-----------+--------------+--------------+
| 4 | place 3 | 2012-04-20 | 11:20:00 |
+----+-----------+--------------+--------------+
| 5 | place 2 | 2012-04-20 | 11:21:00 |
+----+-----------+--------------+--------------+
| 6 | place 1 | 2012-04-20 | 11:22:00 |
+----+-----------+--------------+--------------+
| 7 | place 3 | 2012-04-20 | 11:23:00 |
+----+-----------+--------------+--------------+
我需要的报告要求我首先列出每个位置,然后列出对该位置的访问次数。然而,需要注意的是,需要满足一个时间间隔才能将访问计入此报告中,这使得查询对我来说很困难。
例如:假设访问任何给定地点之间的间隔是 10 分钟。
第一个条目会自动锁定,因为没有之前的条目,第二个条目也会自动锁定,因为“位置 2”还没有其他条目。然而,在第三个条目中,检查地点 1 的最后一次访问时间,该时间间隔小于定义的时间间隔(10 分钟),因此报告将忽略此条目并移至下一个条目。
本质上,我们正在逐案检查时间间隔不是从最后一个条目开始,而是从同一位置的最后一个条目开始。
报告的结果最终应如下所示:
+----+-----------+--------+
| id | location | visits |
+----+-----------+--------+
| 1 | place 1 | 2 |
+----+-----------+--------+
| 2 | place 2 | 2 |
+----+-----------+--------+
| 3 | place 3 | 1 |
+----+-----------+--------+
我当前的基本实现是通过以下步骤来获取上述结果集:
- MySQL 查询创建一个临时表,其中包含所有所需位置及其 ID 的列表。
- MySQL查询选择指定时间范围内的所有访问数据并将其传递给PHP。
- PHP 和 MySQL 用访问数据填充临时表,PHP 在这里完成繁重的工作。
- MySQL从临时表中选择数据返回给客户端显示。
我的问题是。有没有办法单独使用 MySQL 来完成大部分工作?我一直在试图找到一种编写 MySQL 查询的方法,该查询可以解析 select 语句并仅选择满足上述条件的访问,然后最终按位置对其进行分组,并为我提供 COUNT(*)每个组。
我真的不知道这是否可能,并且希望其中一位数据库专家能够阐明如何做到这一点。
最佳答案
假设您有一个结构略有不同的表(可能是临时表):
CREATE TABLE `visits` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`location` varchar(45) NOT NULL,
`visited` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `loc_vis` (`location`,`visited`)
) ENGINE=InnoDB;
INSERT INTO visits (location, visited) VALUES
('place 1', '2012-04-20 11:00:00'),
('place 2', '2012-04-20 11:06:00'),
('place 1', '2012-04-20 11:06:00'),
('place 3', '2012-04-20 11:20:00'),
('place 2', '2012-04-20 11:21:00'),
('place 1', '2012-04-20 11:22:00'),
('place 1', '2012-04-20 11:23:00');
如您所见,其索引位于( location
, visited
)。那么下面的查询就会使用索引,即按照索引的顺序读取数据,并返回你期望的结果:
SELECT
location,
COUNT(IF(@loc <> @loc:=location,
@vis:=visited,
IF(@vis + INTERVAL 10 MINUTE < @vis:=visited,
visited,
NULL))) as visit_count
FROM visits,
(SELECT @loc:='', @vis:=FROM_UNIXTIME(0)) as init
GROUP BY location;
结果:
+----------+-------------+
| location | visit_count |
+----------+-------------+
| place 1 | 2 |
| place 2 | 2 |
| place 3 | 1 |
+----------+-------------+
3 rows in set (0.00 sec)
一些解释:
该解决方案的关键在于它淡出了 SQL 的功能本质,并使用 MySQL 实现细节(他们说这很糟糕,再也不会这样做了!!!)。
如果表有索引(列值的有序表示)并且在查询中使用该索引,则意味着表中的数据按照索引的顺序读取。
GROUP BY 操作将受益于索引(因为数据已经在那里分组),并且如果适用的话将选择它。
SQL 中的所有聚合函数(除了具有特殊含义的
COUNT(*)
)都会检查每一行,并且仅当该值不为 NULL 时才使用该值(上面的 COUNT 中的表达式如果条件错误则返回 NULL)其余部分只是行列表上的程序迭代的一种黑客表示(按索引顺序读取,按
location asc, visisted asc
排序):如果位置不同于前一行 - 我计算它,如果没有 - 我检查间隔,如果错误则返回 NULL。
关于php - MySQL:可以根据每条记录参数进行分组的 SELECT 条件语句吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10239290/