我有一份报告,我正在为使用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来完成大部分工作?我一直想找到的是一种编写MySQL查询的方法,该查询可以解析select语句,只选择符合上述条件的访问,然后最终按位置对其进行分组,并为我提供COUNT(*)每个小组。
我真的不知道是否可能,并希望其中一位数据库大师可能能够阐明如何做到这一点。
答案 0 :(得分:2)
假设你有一个稍微不同结构的表(可能是临时的):
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 )
其余的只是对行列表的过程迭代的一种hacky表示(按索引的顺序读取,由location asc, visisted asc
排序):我初始化一些变量,如果位置与前一行不同 - 我算一下,如果没有 - 我检查一下间隔,如果错误则返回NULL。
答案 1 :(得分:0)
您可以使用INSERT / SELECT语句填充临时表。
参见手册。 http://dev.mysql.com/doc/refman/5.0/en/insert-select.html
我在SELECT语句中使用GROUP BY来缩小地点范围。
对于可以作为COUNT操作填充的访问列,我认为也可以将其作为INSERT / SELECT的一部分执行。
参见手册。 http://dev.mysql.com/doc/refman/5.1/en/counting-rows.html
所以你的SQL可能看起来像这样。
INSERT INTO temp
SELECT * FROM (
SELECT *,COUNT('visits')
FROM source AS table1
GROUP BY location
WHERE date_visited > xxxx AND date_visited < xxxx
)
AS table2
说真的,这不是我的头脑,但它应该给你一些关于如何构建SQL的想法。但是你可以只使用一个好的查询来完成报告。