【问题标题】:Filter out orphan table entries过滤掉孤立表条目
【发布时间】:2011-11-16 09:56:10
【问题描述】:

假设有一个只有两列的表(示例如下所示)。每个 '1' 条目后(按照下面给出的排序顺序)应该跟一个 '0'。但是,如您所见,在表中,有一些“孤儿”,其中有两个连续的“1”。

如何创建一个返回所有行的查询,除了任何连续的“1”中的第一个之外? (这会将下面的示例从 16 行减少到 14 行)

1 E
0 A
1 T
0 S
1 R
0 E
1 F
0 T
1 G
1 T
0 R
1 X
1 R
0 R
1 E
0 T

我将尝试澄清我的问题,我认为上面我简化了太多。想象一张名为logs 的表,有四列:

  • user(包含用户名的字符串)
  • machine(唯一标识各种 PC 的字符串)
  • type(事件类型:1 表示 login,0 表示 logout)
  • time(记录事件的时间)

[机器/时间对提供唯一的密钥,因为没有机器可以在同一时刻登录或退出两次。如果需要,可以根据机器/时间排序人工创建一个“ID”列。]

这个想法是每个登录事件都应该伴随一个注销事件。用理想的话来说,将登录与注销相匹配是相当容易的,因此可以分析登录所花费的时间。

但是,在断电的情况下,不会记录注销。因此(仅考虑一台机器的数据,按时间排序)如果连续两次登录事件,我们希望忽略第一次登录,因为我们没有任何可靠的数据。这就是我要解决的问题。

【问题讨论】:

  • SQL 表没有顺序。想要一行后面跟着另一行是没有意义的。如果您想要自然顺序使用 id
  • 排序是如何实现的?我们可以为此假设一个额外的 id 列?
  • @Erwin,是的,我忽略了其他一些列(提供排序顺序)(因为我认为这会使事情变得更简单,并专注于问题)
  • @fluteflute,由于 ID 在确定序列中的“下一个”记录时必不可少,因此它是问题的重要部分。 Benoit 发布了一个解决方案,该解决方案依赖于 ID 是由 1 迭代的完整数字序列 - 这是否现实?
  • 另外,表名和列名也会有所帮助。

标签: mysql sql duplicate-removal gaps-and-islands


【解决方案1】:

前提是

  • 只有 1 是骗子,绝不是 0
  • 如果还有更多,您想删除所有第一个 1。

你的文字说“除了第一个连续的”,但我认为,这就是你想要的。或者只能有 2 个,那么它是一样的。

SELECT x.*
FROM   x
LEFT   JOIN x y on y.id = (x.id + 1)
WHERE  (x.nr = y.nr) IS NOT TRUE -- OR x.nr = 0
ORDER  BY x.id

如果要保留双 0,请另外使用注释子句,但可能不需要。

问题编辑后编辑:

您可能希望在数据中添加一个自动增量列以简化操作: Generate (i.e. write) a row number index column in MySQL

其他 RDBMS(PostgreSQL、Oracle、SQL Server 等)具有诸如 row_number()lag()lead() 之类的窗口函数,使此类操作更加容易。

【讨论】:

  • 注释子句似乎是需要的,否则表中的最后一项('0'被删除)
  • @fluteflute:一定有某种错误。 y.nr 对于“最后”行(最大的 x.id)是 NULL,因此 (x.nr = y.nr) IS NOT TRUE 的计算结果为 TRUE。
【解决方案2】:

假设你得到一个 id(添加列,设置列 id = 数据库中的记录号)使用:

select a.*
  from the_table a
  left join the_table b on b.id = a.id + 1
                       and b.col1 = 0
 where a.col1 = 1
   and b.id is null

【讨论】:

  • where 条件应该是 where (a.col1 = 1 and b.id is null) or a.col1 = 0 - fluteflute 想要包含 0。
【解决方案3】:

试试:

select l.*
from logs l
where l.type = 0 or
      not (select type
           from (select * from logs order by `time` desc) n
           where n.machine = l.machine and
                 n.user = l.user and
                 n.time > l.time)
           group by () )

【讨论】:

    【解决方案4】:

    使用 CTE 将滞后逻辑与选择标准分开。

    DROP TABLE tmp.bits;
    CREATE TABLE tmp.bits
        ( id SERIAL NOT NULL
        , bit INTEGER NOT NULL
        , code CHAR(1)
        );
    INSERT INTO tmp.bits(bit, code) VALUES
    (1, 'T' )
    , (0, 'S' )
    , (1, 'R' )
    , (0, 'E' )
    , (1, 'F' )
    , (0, 'T' )
    , (1, 'G' )
    , (1, 'T' )
    , (0, 'R' )
    , (1, 'X' )
    , (1, 'R' )
    , (0, 'R' )
    , (1, 'E' )
    , (0, 'T' )
        ;
    
    SET search_path='tmp';
    SELECT * FROM bits;
    
    -- EXPLAIN ANALYZE
    WITH prevnext AS (
    SELECT
        bt.id AS thisid
        , bt.bit  AS thisbit
        , bt.code AS thiscode
        , bp.bit AS prevbit
        , bp.code AS prevcode
        FROM bits bt
        LEFT JOIN bits bp ON (bt.id > bp.id)
        AND NOT EXISTS ( SELECT * FROM bits nx
            WHERE nx.id > bp.id
            AND nx.id < bt.id
            )   
        )
    SELECT thisid, thisbit, thiscode
    FROM prevnext
    WHERE thisbit=0
    OR prevbit IS NULL OR thisbit <> prevbit
        ;
    

    编辑:

    对于那些不能使用 CTE 的可怜的 soals,很容易创建一个视图来代替:

    CREATE VIEW prevnext AS (
    SELECT
        bt.id AS thisid
        , bt.bit  AS thisbit
        ,bt.code AS thiscode
        , bp.bit AS prevbit
        , bp.code AS prevcode
        FROM bits bt
        LEFT JOIN bits bp ON (bt.id > bp.id)
        AND NOT EXISTS ( SELECT * FROM bits nx
            WHERE nx.id > bp.id
            AND nx.id < bt.id
            )
        )
        ;
    SELECT thisid, thisbit, thiscode
    FROM prevnext
    WHERE thisbit=0
    OR prevbit IS NULL OR thisbit <> prevbit
        ;
    

    【讨论】:

    • 除了我们在这里谈论MySQL。没有 CTE,没有窗口函数。
    • 我知道,但是这个问题非常普遍(并且标记为 sql),此外:CTE 总是可以被包裹到视图中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-27
    相关资源
    最近更新 更多