4

我有一个用于记录实验室访问数据的表。表结构如下:

create table accesslog
(
    userid int not null,
    direction int not null,
    accesstime datetime not null
);

这个实验室只有一个处于访问控制之下的大门。所以用户必须先“进入”实验室,才能“离开”。在我的原始设计中,我将“方向”字段设置为 1(用于进入实验室)或 -1(用于离开实验室)的标志。这样我就可以使用如下查询:

SELECT SUM(direction) FROM accesslog;

获得实验室内的总用户数。从理论上讲,它有效;因为对于任何给定的用户 ID,“方向”将始终采用 1 => -1 => 1 => -1 的模式。

但是很快我发现日志消息会在从实验室门到服务器的传输路径中丢失,要么是由于网络繁忙,要么是硬件故障。当然我可以用序列号、ACK、重传、硬件冗余等来强制传输路径,但最后我可能还是会得到这样的东西:

userid   direction   accesstime
-------------------------------------
1         1          2013/01/03 08:30
1        -1          2013/01/03 09:20
1         1          2013/01/03 10:10
1        -1          2013/01/03 10:50
1        -1          2013/01/03 13:40
1         1          2013/01/03 18:00

这是用户“1”的最新日志。很明显,我丢失了该用户在 10:50 到 13:40 之间进入实验室的一条日志消息。我查询这个数据的时候,他还在实验室,所以2013/01/03 18:00之后还没有退出日志;这是肯定的。

我的问题是:有没有办法“发现”这些数据与 SQL 命令不一致?我的系统内总共有 5000 名用户,实验室 24 小时运行,没有这样的“神奇时间”可以清除实验室。如果我必须逐行、逐个地编写代码来检查“方向”字段的连续性,那我会很糟糕。

我知道不可能用正确的数据“修复”日志。我只想知道“哦,我有一个 userid=1 的数据不一致问题”,这样我就可以添加一个标记的修改数据来纠正最终的统计数据。

任何建议都将不胜感激,即使更改表结构也可以。

谢谢。

编辑:对不起,我没有提到细节。

目前我正在使用混合 SQL 解决方案。上表是 MySQL,它只包含 24 小时内的日志,作为快速浏览的“实时”状态。

每天凌晨 03:00 将启动在 POSIX 上用 C++ 编写的预先安排的进程。此过程将计算统计数据,并通过专有协议 TCP 套接字将每日统计数据添加到 Oracle DB,然后从 MySQL 中删除旧数据。

Oracle 部分不是我处理的,我对此无能为力。我只是想确保每一天的最终统计数据是正确的。

数据量约为每天 200,000 条记录——我知道这听起来很疯狂,但这是真的。

4

3 回答 3

2

您没有说明您的 DBMS,所以这是 ANSI SQL(适用于大多数现代 DBMS)。

select userid,
       direction,
       accesstime,
       case 
         when lag(direction) over (partition by userid order by accesstime) = direction then 'wrong'
         else 'correct'
       end as status
from accesslog
where userid = 1

对于 accesslog 中的每一行,您将获得一列“状态”,指示该行是否“违反”规则。

您可以使用以下方法过滤掉那些无效的:

select *
from (
  select userid,
         direction,
         accesstime,
         case 
           when lag(direction) over (partition by userid order by accesstime) = direction then 'wrong'
           else 'correct'
         end as status
  from accesslog
  where userid = 1
) t
where status = 'wrong'

我不认为有一种方法可以使用数据库中的约束来强制执行这种规则(尽管我觉得 PostgreSQL 的排除约束在这里有所帮助)

于 2013-01-03T10:41:29.433 回答
1

为什么不使用带有 WHERE 字段的 SUM() 来按 USER 进行过滤。

如果你得到的不是 0 或 1,那么你肯定有问题。

于 2013-01-03T10:44:02.657 回答
0

好的,我想通了。感谢 a_horse_with_no_name 提供的想法。

我的最终解决方案是这个查询:

SELECT userid, COUNT(*), SUM(direction * rule) FROM (
    SELECT userid, direction, @inout := @inout * -1 AS rule
    FROM accesslog l, (SELECT @inout := -1) r
    ORDER by userid, accesstime
) g GROUP by userid;

首先,我使用@inout 创建了一个模式,它将为“规则”列中的每一行产生 1 => -1 => 1 => -1。比我通过计算乘积来比较方向字段和规则列。

某些用户有奇数记录也没关系;因为每个用户都应该遵循相同或相反的模式作为“规则”。所以乘积的总和应该等于 COUNT() 或 -1 * COUNT()。

通过检查 SUM() 和 COUNT(),我可以准确地知道哪个用户 ID 出错了。

于 2013-01-04T02:43:28.177 回答