在运行时过滤掉 SQL 数据库中的重复值 - 基于集合

Posted

技术标签:

【中文标题】在运行时过滤掉 SQL 数据库中的重复值 - 基于集合【英文标题】:Filtering out duplicate values at runtime in a sql database - set based 【发布时间】:2010-09-12 03:51:50 【问题描述】:

我有一个数据库问题,我目前无法用简单的解决方案解决这个问题。在我的数据库中,我有一个存储事件值的表。0 和 1 带有时间戳。问题是同一事件可能作为业务规则发生两次。如下所示

'2008-09-22 16:28:14.133', 0 '2008-09-22 16:28:35.233', 1 '2008-09-22 16:29:16.353', 1 '2008-09-22 16:31:37.273', 0 '2008-09-22 16:35:43.134', 0 '2008-09-22 16:36:39.633', 1 '2008-09-22 16:41:40.733', 0

在现实生活中,这些事件是循环的,我正在尝试查询以获取这些事件的循环,但我需要忽略重复值 ( 1,1 ) 当前的解决方案是使用 SQL 游标循环每个并抛出如果前一个相同,则取出该值。我考虑过在插入时使用触发器来清理后处理的表,但我想不出一个简单的解决方案来基于这个集合。

有什么想法或建议吗?

谢谢

【问题讨论】:

你怎么知道哪些是重复值?你的桌子上有某种主键吗?现在我不知道你会如何分辨哪两条记录是相互关联的。 【参考方案1】:

(前言.......我只在oracle中做过,但我很确定db是否支持触发器这一切都是可能的)

有一个插入前触发器,它选择具有最大时间戳值的行。如果该行的值与您要插入的值相同,请忽略它。

这应该使它们都处于正确的状态。

现在,如果您需要存储两组状态,则触发器始终可以在全包表中插入,但仅在值更改时才在“过滤”表中进行查找和插入。

【讨论】:

【参考方案2】:

只是为了让我理解问题。

如果您根据时间戳对行集进行排序,有时 重复 值会彼此相邻出现,例如上述第 2 项和第 3 项中的一对 1?然后你在第 4 和第 5 位有双 0,是这样吗?

并且您想要对应对中的最后一个(或序列,如果有超过 2 个)?

为什么需要删除它们?我之所以这样问,是因为除非它们在此表的大小中占据很大份额,否则当您需要处理或显示它们时,可能更容易像按顺序一样将它们过滤掉。

一种解决方案,虽然不是一个很好的解决方案,但将检索最小时间戳高于您正在检查的当前行的时间戳,然后从中检索值,如果它是同理,不返回当前行。

这是获取所有内容的 SQL:

SELECT timestamp, value
FROM yourtable

以下是如何加入以获得高于当前时间戳的最小时间戳:

SELECT T1.timestamp, MIN(T2.timestamp) AS next_timestamp, T1.value
FROM yourtable T1, yourtable T2
WHERE T2.timestamp > T1.timestamp
GROUP BY T1.timestamp, T1.value

(我担心上面的查询会非常慢)

然后检索对应于那个最小时间戳的值

SELECT T3.timestamp, T3.value
FROM (
    SELECT T1.timestamp, MIN(T2.timestamp) AS next_timestamp, T1.value
    FROM yourtable T1, yourtable T2
    WHERE T2.timestamp > T1.timestamp
    GROUP BY T1.timestamp, T1.value
) T3, yourtable AS T4
WHERE T3.next_timestamp = T4.timestamp
  AND T3.value <> T4.value

不幸的是,这不会产生最后一个值,因为它需要一个后续值来进行比较。一个简单的虚拟哨兵值(如果需要,可以将其合并)将处理该问题。

这是我测试上述查询的 sqlite 数据库转储:

BEGIN TRANSACTION;
CREATE TABLE yourtable (timestamp datetime, value int);
INSERT INTO "yourtable" VALUES('2008-09-22 16:28:14.133',0);
INSERT INTO "yourtable" VALUES('2008-09-22 16:28:35.233',1);
INSERT INTO "yourtable" VALUES('2008-09-22 16:29:16.353',1);
INSERT INTO "yourtable" VALUES('2008-09-22 16:31:37.273',0);
INSERT INTO "yourtable" VALUES('2008-09-22 16:35:43.134',0);
INSERT INTO "yourtable" VALUES('2008-09-22 16:36:39.633',1);
INSERT INTO "yourtable" VALUES('2008-09-22 16:41:40.733',0);
INSERT INTO "yourtable" VALUES('2099-12-31 23:59:59.999',2);
COMMIT;

这是(格式化的)输出:

timestamp                 value
2008-09-22 16:28:14.133   0
2008-09-22 16:29:16.353   1
2008-09-22 16:35:43.134   0
2008-09-22 16:36:39.633   1
2008-09-22 16:41:40.733   0

【讨论】:

【参考方案3】:

这个问题实际上是一个数据捕获问题。典型的数据库引擎不是解决它的好选择。一个简单的预处理器应该检测输入数据集的变化并只存储相关数据(时间戳等)。

一个简单的解决方案是在数据库环境中(例如在 Oracle 中)创建一个包,该包可以具有用于存储最后输入数据集的本地内存变量并消除不必要的数据库访问。

当然,您可以使用数据库环境的所有功能来定义“输入数据集的变化”并存储过滤后的数据。因此,它可以是简单的,也可以是复杂的。

【讨论】:

【参考方案4】:

这使用 SQL Server 公用表表达式,但它可以内联,表 t 具有列 dt 和循环状态:

;WITH Firsts AS (
    SELECT t1.dt
        ,MIN(t2.dt) AS Prevdt
    FROM t AS t1
    INNER JOIN t AS t2
        ON t1.dt < t2.dt
        AND t2.cyclestate <> t1.cyclestate
    GROUP BY t1.dt
)
SELECT MIN(t1.dt) AS dt_start
    ,t2.dt AS dt_end
FROM t AS t1
INNER JOIN Firsts
    ON t1.dt = Firsts.dt
INNER JOIN t AS t2
    ON t2.dt = Firsts.Prevdt
    AND t1.cyclestate <> t2.cyclestate
GROUP BY t2.dt
    ,t2.cyclestate
HAVING MIN(t1.cyclestate) = 0

【讨论】:

以上是关于在运行时过滤掉 SQL 数据库中的重复值 - 基于集合的主要内容,如果未能解决你的问题,请参考以下文章

php数组的重复值如何过滤掉

过滤掉流分析中的重复项

Oracle SQL - 过滤掉包含具有特定值的行的分区或行组

将所有数据保存在数组中,过滤掉重复的数据,比较数组之间的数据并删除匹配的数据

在 Oracle 11g 中过滤掉报告中的重复条目

如何过滤掉 UIWebView 中的重复请求