对不起,我想给出一个完整的描述!我需要显示一个报告,其中显示有关来自另一个表格的ID的一些信息,以及有人在一个国家/地区内更改国家/地区的时间。请注意我如何在表中多次为同一个国家/地区输入一个id(因为信息会定期多次查询,但在此期间可能没有移动),并且还可以有不同的国家/地区条目(因为它们改变国家)。
快速解释数据: 我有下表:
CREATE TABLE IF NOT EXISTS `country` (
`id` mediumint(8) unsigned NOT NULL,
`timestamp` datetime NOT NULL,
`country` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`,`timestamp`),
KEY `country` (`country`),
KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
并且这些声明是这样的:
41352 2012-03-26 15:46:01 Jamaica
41352 2012-03-05 22:49:41 Jamaican Applicant
41352 2012-02-26 15:46:01 Jamaica
41352 2012-02-16 12:11:19 Jamaica
41352 2012-02-05 23:00:30 Jamaican Applicant
此表目前总共约有214,590行,但一旦测试数据被实际数据替换,就会有数百万。
我想要的是有关从y时间起离开x国家的所有人的一些信息。假设它是在上面的数据上运行的,我希望它输出的方式如下:
id name last country TIMESTAMP o_timestamp
41352 Sweet Mercy Jamaica 2012-03-26 15:46:01 2012-03-05 22:49:41
41352 Sweet Mercy Jamaica 2012-02-16 12:11:19 2012-02-05 23:00:30
如果o_timestamp比某个日期(比如说100)更新,那么国家就是他们搬到的地方,他们来自的旧国家(未显示)是我传入查询的任何内容(牙买加申请人基于以上数据)
我开发了以下查询以满足要求并使用某个id进行测试:
SELECT a.id,
c.name,
c.last,
a.country,
a.timestamp,
b.timestamp AS o_timestamp
FROM country a
INNER JOIN user_info c
ON ( a.id = c.id )
LEFT JOIN country AS b
ON ( a.id = b.id
AND a.timestamp != b.timestamp
AND a.country != b.country )
WHERE b.timestamp = (SELECT c.timestamp
FROM country c
WHERE a.id = c.id
AND a.timestamp > c.timestamp
ORDER BY c.timestamp DESC
LIMIT 1)
AND a.id = 965
我完成了这个(总共7个,查询耗时0.0050秒)
并且扩展的解释揭示了以下内容:
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY c const PRIMARY PRIMARY 3 const 1 100.00
1 PRIMARY a ref PRIMARY PRIMARY 3 const 16 100.00
1 PRIMARY b eq_ref PRIMARY,timestamp PRIMARY 11 const,func 1 100.00 Using where
2 DEPENDENT SUBQUERY c index PRIMARY,timestamp timestamp 8 NULL 1 700.00 Using where; Using index
所以我认为我非常好并且突然出现:
SELECT a.id,
c.name,
c.last,
a.country,
a.timestamp,
b.timestamp AS o_timestamp
FROM country a
INNER JOIN user_info c
ON ( a.id = c.id )
LEFT JOIN country AS b
ON ( a.id = b.id
AND a.timestamp != b.timestamp
AND a.country != b.country )
WHERE b.timestamp = (SELECT c.timestamp
FROM country c
WHERE a.id = c.id
AND a.timestamp > c.timestamp
ORDER BY c.timestamp DESC
LIMIT 1)
AND b.country = "whatever" AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
这个查询在一个有200条记录并且从未完成的国家(在下午和晚上外出后
完成了惊人的6分54秒完成 对于一个在数据库中有9000条记录的国家来说,回家一共大约8小时。在实际数据中,一个国家可以容易10000次。 100k不会是不合理的。所以我解释扩展,得到这个:
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY <derived2> ALL NULL NULL NULL NULL 3003 100.00
1 PRIMARY c eq_ref PRIMARY PRIMARY 3 b.id 1 100.00
1 PRIMARY a ref PRIMARY PRIMARY 3 b.id 7 100.00 Using where
3 DEPENDENT SUBQUERY c index PRIMARY,timestamp timestamp 8 NULL 1 700.00 Using where; Using index
2 DERIVED country range country,timestamp country 195 NULL 474 100.00 Using where; Using index
所以它看起来更大,但并非不合理。
[删除了空间的配置变量,如果需要,请告诉我,以及自问题查询以来的性能信息。]
如果我错过了什么,请告诉我。
答案 0 :(得分:2)
问题不是添加标准;它正在放弃一个正在造成伤害的人。在原始查询中,您有:
AND a.id = 965
这意味着查询执行不需要读取整个a
(country
)表。在您的第二个遇到性能的查询中,您将该标准更改为:
AND b.country = "whatever"
AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
您在a
上不再有严格限制标准,因此工作起来要慢得多。
当意识到b
是country
的另一个引用时,事情变得更加复杂。然而,从a
上的条件到b
(其中b
位于外连接的外侧)的变化并非微不足道;处理查询条件需要更长的时间。
这是否意味着因为我不是在寻找具体的身份证,我运气不好?
使用给定的查询结构,答案似乎是“是”,但是,我们说,给定的查询结构可能是次优的。
“处理一个ID”查询的“足够快”是:
SELECT a.id,
c.name,
c.last,
a.country,
a.timestamp,
b.timestamp AS o_timestamp
FROM country a
INNER JOIN user_info c
ON ( a.id = c.id )
LEFT JOIN country AS b
ON ( a.id = b.id
AND a.timestamp != b.timestamp
AND a.country != b.country )
WHERE b.timestamp = (SELECT c.timestamp
FROM country c
WHERE a.id = c.id
AND a.timestamp > c.timestamp
ORDER BY c.timestamp DESC
LIMIT 1)
AND a.id = 965
我不完全理解这个查询以及它正在尝试做什么。您需要知道外连接比内连接更昂贵,并且外连接表上的条件如
b.timestamp = (...correlated sub-query...)
非常昂贵。一个问题是b
列中可能存在NULL,包括timestamp
,但子查询被浪费在那,因为除非值非空,否则不满足条件,所以我们最终想知道'为什么要加入'?
当您添加修订后的条件时,您应该收到“模糊列名称”错误,因为该时间戳可能来自a
或c
。此外,b.country = "whatever"
条件是仅在b
值不为空时才有意义的情况,因此OUTER连接也是可疑的。
据我了解,country
表包含有关谁进入哪个国家/地区以及何时进入的记录。另外,FWIW,我可以肯定地确定与user_info
表的连接是一个可以忽略的性能问题;问题全部归结为country
表的三个引用。
从一些澄清中判断,你可以逐步建立查询,也许就是这样。
查找记录在时间顺序上相同的id
的每对国家/地区记录,其中较旧的一对是针对给定国家/地区(“牙买加申请人”),较新的是对于另一个国家。
这很简单:
SELECT a.id, a.country, a.timestamp, b.country, b.timestamp
FROM country AS a
JOIN country AS b
ON a.id = b.id
AND b.timestamp > a.timestamp
AND a.country = 'Jamaica Applicant'
AND b.country != a.country
这可以完成大部分工作,但不能确保条目的相邻性。要做到这一点,我们必须坚持country
表中没有记录两个时间戳id
和a.timestamp
之间(但不包括)b.timestamp
的相同SELECT a.id,
a.country AS o_country,
a.timestamp AS o_timestamp,
b.country AS n_country,
b.timestamp AS n_timestamp
FROM country AS a
JOIN country AS b
ON a.id = b.id
AND b.timestamp > a.timestamp
AND a.country = 'Jamaica Applicant'
AND b.country != a.country
WHERE NOT EXISTS
(SELECT *
FROM country AS c
WHERE c.timestamp > a.timestamp
AND c.timestamp < b.timestamp
AND c.id = a.id
)
。这是一个额外的NOT EXISTS条件:
user_info
请注意,BETWEEN和符号不合适。它包括范围内的终点,但我们明确需要排除终点。
鉴于上面的国家/地区条目列表,我们现在需要选择那些......嗯,那么,标准是什么?我想您可以选择,但结果可以轻松地与SELECT e.id, u.name, u.last, e.o_country, e.o_timestamp, e.n_country, e_n_timestamp
FROM (SELECT a.id,
a.country AS o_country,
a.timestamp AS o_timestamp,
b.country AS n_country,
b.timestamp AS n_timestamp
FROM country AS a
JOIN country AS b
ON a.id = b.id
AND b.timestamp > a.timestamp
AND a.country = 'Jamaica Applicant'
AND b.country != a.country
WHERE NOT EXISTS
(SELECT *
FROM country AS c
WHERE c.timestamp > a.timestamp
AND c.timestamp < b.timestamp
AND c.id = a.id
)
) AS e
JOIN user_info AS u ON e.id = u.id
WHERE e.o_timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY);
表结合使用:
{{1}}
我不打算保证性能会更好(甚至它在语法上是正确的;它还没有超过SQL DBMS)。但我认为获取相邻日期的复杂查询结构更整洁,可能比原始代码更好。特别要注意,它不使用任何外连接,(显式)排序或限制子句。这应该会有所帮助。
答案 1 :(得分:0)
您应该查看此参考:http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_now
和http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-add
它说的是,NOW()函数可以返回一个字符串(取决于上下文),而date_add可以返回一个字符串(取决于参数)。我的猜测是,你正在获取字符串,然后只在比较中投射到日期(在每条记录上都会发生)。你能试试AND时间戳&gt;施放(DATE_SUB(NOW(),INTERVAL 7 DAY)作为日期时间),这可能会提高性能。
答案 2 :(得分:0)
我并不是说这是一个完成的解决方案,但这是一个我将回归的开始。请告诉我这对您的测试数据集的效果如何 -
SELECT ui.*, c1.*, MAX(c2.timestamp)
FROM country c1
INNER JOIN user_info ui
ON c1.id = ui.id
INNER JOIN country c2
ON c1.id = c2.id
AND c1.timestamp > c2.timestamp
AND c1.country <> c2.country
WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
AND c2.country = 'somewhere'
GROUP BY c1.id
下一步是添加LEFT JOIN以确保其间没有其他记录 -
SELECT ui.*, c1.*, c2.timestamp
FROM country c1
INNER JOIN user_info ui
ON c1.id = ui.id
INNER JOIN country c2
ON c1.id = c2.id
AND c1.timestamp > c2.timestamp
AND c1.country <> c2.country
LEFT JOIN country c3
ON c1.id = c3.id
AND c1.timetsamp > c3.timestamp
AND c2.timestamp < c2.timetsamp
WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
AND c2.country = 'somewhere'
AND c3.id IS NULL