【问题标题】:Find records that have related records in the past查找过去有相关记录的记录
【发布时间】:2013-01-04 23:02:33
【问题描述】:

基本上,我尝试的结果是:“获取过去一定时间内有 0 个不成功 记录的 成功 记录的数量”。 “成功”和“不成功”只是指列的值。

虽然有点复杂,但这里是我正在处理的表的描述:

`log`
  id                int PRIMARY KEY AUTO_INCREMENT
  fingerprint_id    int (foreign key)
  status            boolean
  date              timestamp

我们的小系统的工作流程是,当用户刷指纹时,会在这个表中添加一条记录,并根据是否匹配设置status(再次,还有更多,我只是试图简化)。我们会根据用户执行此操作得到fingerprint_id,因此这是用于将记录与人员相关联的标识符。

目前,我们要求他们最多尝试 3 次。因此,他们可以在 3 之 1、3 之 2、3 之 3 或根本不匹配。这意味着他们的“组”中可以有 1、2 或 3 条记录。虽然这不是真的,但我们可以假设用户将继续尝试,直到匹配或达到 3 次失败尝试(我们发现有时人们可能会在失败一两次后不再继续)。

以下是一些数据的示例:

id  fp_id status   date
----------------------------------------
20    2     0      '2013-01-21 12:30:01'
21    2     0      '2013-01-21 12:30:05'
22    2     0      '2013-01-21 12:30:10'
23    9     1      '2013-01-21 12:31:30'
24    1     0      '2013-01-21 12:35:00'
25    1     1      '2013-01-21 12:35:05'

在数据中,用户 (fingerprint_id) 2 尝试了 3 次,从未匹配。用户 9 第一次尝试匹配。用户 1 尝试一次失败,然后再次尝试匹配。

关键是找出35秒前有多少成功(status=1)的日志记录有0个不成功(status=0)的记录。当然,“连接”它们的唯一方法是通过fingerprint_id

再一次,我们假设了很多事情,但这没关系。

这是我的尝试:

SELECT  COUNT(*)
FROM    log AS log_main
WHERE   log_main.status=1 AND
        (SELECT COUNT(*)
         FROM   log AS log_inner
         WHERE  log_inner.fingerprint_id=log_main.fingerprint_id AND
                log_inner.status=0 AND
                log_inner.date<log_main.date AND log_inner.date>=(log_main.date - INTERVAL 35 SECOND))=0

^ 我希望这个选择所有成功的记录,这些记录在 35 秒前(对于该用户)内发生的不成功记录计数为 0。但我不知道,因为查询需要 600 多秒。我刚刚发现了如何延长 MySQL Workbench 的最大超时时间,但无论哪种方式,都需要很长时间。该表总共有大约 120,000 条记录,所以我不确定这是否足以让这个查询变得如此缓慢。

无论如何,这是另一个尝试:

SELECT  COUNT(*)
FROM    (SELECT log.fingerprint_id, log.date
         FROM log
         WHERE log.status=1) successful,
        (SELECT log.fingerprint_id, log.date
         FROM log
         WHERE log.status=0) unsuccessful
WHERE   successful.fingerprint_id=unsuccessful.fingerprint_id AND
        unsuccessful.date<successful.date AND unsuccessful.date>=(successful.date - INTERVAL 35 SECOND)

^ 我觉得这个更接近,但当然,没有比较过去匹配多少记录的“计数”。这就是我对如何解决感到困惑的部分。我感觉它与GROUP BY 或使用IN 有关,但我所做的似乎不起作用(从某种意义上说,它超过了 600 秒或类似的时间)。这是我用GROUP BY尝试过的一个例子

SELECT  successful.id, COUNT(*) cnt
FROM    (SELECT log.fingerprint_id, log.date, log.id
         FROM log
         WHERE log.status=1) successful,
        (SELECT log.fingerprint_id, log.date, log.id
         FROM log
         WHERE log.status=0) unsuccessful
WHERE   successful.fingerprint_id=unsuccessful.fingerprint_id AND
        unsuccessful.date<successful.date AND unsuccessful.date>=(successful.date - INTERVAL 35 SECOND)
GROUP BY successful.id

^ 但结果仅包含计数不为 0 的行。我猜这是因为WHERE 子句。但我只需要 0 个计数。

我尝试了这么多组合,我觉得我的大脑只是炸了。

【问题讨论】:

  • 一点:关于运行了这么长时间的查询,你有没有索引你需要这些查询有机会执行ok?
  • @DWright 我感觉可能是个问题。不幸的是,我们没有。巧合的是,我是不久前加入团队时决定开始清理数据库的人,并且已经准备好添加索引的查询。问题是我正在生产服务器(数据真正重要的地方)上测试这些查询,并且宁愿等到今晚的一些停机时间来修改表。对我来说,索引在fingerprint_iddate 列上很有意义。那可能是对的吗?

标签: mysql sql


【解决方案1】:

尝试使用NOT EXISTS 而不是COUNT = 0。这应该表现得更好。

SELECT  COUNT(*)
FROM    log AS log_main
WHERE   log_main.status=1 
AND     NOT EXISTS
        (   SELECT 1
            FROM   log AS log_inner
            WHERE   log_inner.fingerprint_id=log_main.fingerprint_id
            AND     log_inner.status = 0
            AND     log_inner.date < log_main.date 
            AND     log_inner.date >= (log_main.date - INTERVAL 35 SECOND)
        );

您还应该确保该表已正确编入索引。

编辑

我相信在 MySQL 中使用 LEFT JOIN/IS NULL 比使用 NOT EXISTS 更有效,所以这会比上面的表现更好(尽管可能不是很明显):

SELECT  COUNT(*)
FROM    log AS log_main
        LEFT JOIN log AS log_inner
            ON log_inner.fingerprint_id=log_main.fingerprint_id
            AND log_inner.status = 0
            AND log_inner.date < log_main.date 
            AND log_inner.date >= (log_main.date - INTERVAL 35 SECOND)
WHERE   log_main.status = 1 
AND     Log_inner.fingerprint_id IS NULL;

编辑 2

要获得 1 或 2 次尝试的记录等,我仍然会使用 JOIN,但就像这样:

SELECT  COUNT(*)
FROM    (   SELECT  log_Main.id
            FROM    log AS log_main
                    INNER JOIN log AS log_inner
                        ON log_inner.fingerprint_id=log_main.fingerprint_id
                        AND log_inner.status = 0
                        AND log_inner.date < log_main.date 
                        AND log_inner.date >= (log_main.date - INTERVAL 35 SECOND)
            WHERE   log_main.status = 1 
            AND     Log_inner.fingerprint_id IS NULL
            GROUP BY log_Main.id
            HAVING COUNT(log_Inner.id) = 1
        ) d

【讨论】:

  • 谁知道我过去有没有试过。我很乐意试一试,让你知道。感谢您的建议!
  • 是的,脑子肯定炸了。这些是有道理的,所以我希望它们能工作(更好)!
  • 所以它花了 2000 多秒,但它似乎工作。我可能不得不调整它,因为有一些奇怪的场景......无论如何,这似乎是正确的!我不想问,但你能帮我做一件事吗?我知道在我的问题中,我问了如何获取具有 0 条相关记录的记录...您给了我答案...但是有没有办法做类似于您的解决方案的方法来匹配具有 1 的记录,或者2、相关记录?而不是0?我知道使用 0 更容易,因为您可以检查不存在,但我也在寻找特定数量的记录。具体来说,1,还有2
猜你喜欢
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 2020-02-04
  • 2014-06-10
相关资源
最近更新 更多