数据库:选择最后一个非空条目

Posted

技术标签:

【中文标题】数据库:选择最后一个非空条目【英文标题】:Database: Select last non-null entries 【发布时间】:2012-02-17 03:25:38 【问题描述】:

这是我一直在思考的一个问题。假设我有一个表,其中包含一系列时间戳和一个零件号作为主键。该表存储增量更改,这意味着对于每个时间戳,如果字段更改,则记录该更改。如果该字段没有改变,那么对于新的时间戳,它是 NULL。 这是基本思想。

 part | timestamp | x-pos | y-pos | status
------+-----------+-------+-------+--------
 a5   |       151 |     5 |    15 |      g
 a5   |       153 |  NULL |    17 |   NULL

(part, timestamp) 是主键。第二条记录中的NULLs 表示自第一条记录以来未更改的值。

我希望能够为按部件分组的每个字段选择最新值。例如,给定上述条目,a5 部分的结果将为 153,5,17,g。

到目前为止,我已经完成了这个组合查询。

    ((SELECT x-pos FROM part_changes WHERE x-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)

    UNION

    (SELECT y-pos FROM part_changesWHERE y-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)

    UNION

    (SELECT status FROM part_changes WHERE status IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1))

但这会返回一个单列,这意味着我可以使用 group-by 进行组织。

必须有一种更优雅的做事方式,例如以创造性的方式使用 COALESCE 或 IS NULL。但我被卡住了,无法弄清楚。有人有想法吗?

不,我无法更改数据库结构。

编辑:ruakh 的想法是正确的。现在唯一的问题是按部分分组。我似乎无法绕过LIMIT 1 按多个部分进行分组。有任何想法吗?

mdahlman,我对 postgresql 中的分析函数不太熟悉。因此,如果该解决方案比复杂查询更容易,那么请务必发表您的想法。

编辑 2:谢谢大家的帮助。我想我已经足够了解我需要做什么了。

【问题讨论】:

您应该指定是否允许分析函数。有了他们,答案应该很简单。没有他们......这会很艰难。 有多少空值可以出现的限制吗?如果有限制,这将是一个带有一些左连接的解决方案.. 不好但可以做到;) 我认为没有限制。事实上,其中一个字段的绝大多数(大约 99%)记录为 NULL。 【参考方案1】:

相关时间戳列表:

select max timestamp from part_changes where x_POS is not null group by part

你可以把它变成一个视图:让我们称之为 view1

SELECT part_changes.part, part_changes.x-pos 
FROM part_changes left join view1 on part_changes.part = view1.part
WHERE x-pos IS NOT NULL 
 AND part_changes.timestamp = view1.timestamp 
GROUP BY part_changes.part

看到我要去哪里了吗?这应该会为您提供 x-pos 的完整列表。

【讨论】:

有趣的想法。我考虑过使用某些连接的消除空值的特性来发挥我的优势,但我不知道从哪里开始。我将对此进行一些实验。我唯一担心的是,就我而言,实际上还有更多的列。帖子中的示例是简化版本。这么多的连接会不会有内存问题? 如果你有几百个它可能...我也刚刚编辑了我的帖子以包含分组依据。现在我想起来了,我认为这会为您提供您想要的 x_pos。未经测试! 我相信只有大约 15 个字段,而且每天有数千条记录。我会尝试一下。这可能正是我所需要的。【参考方案2】:

而不是使用UNION,听起来你真的想要字段列表中的子查询。也就是说,您需要SELECT (SELECT ...), (SELECT ...), (SELECT ...),而不是(SELECT ...) UNION (SELECT ...) UNION (SELECT ...)


例如:

SELECT part,
       ( SELECT x_pos
           FROM part_changes
          WHERE part = pc.part
            AND x_pos IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS x_pos,
       ( SELECT y_pos
           FROM part_changes
          WHERE part = pc.part
            AND y_pos IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS y_pos,
       ( SELECT status
           FROM part_changes
          WHERE part = pc.part
            AND status IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS status
  FROM ( SELECT DISTINCT
                part
           FROM part_changes
       ) AS pc
;

但在这一点上,我真的会考虑编写一个存储过程。


或者:

SELECT DISTINCT
       part,
       FIRST_VALUE(x_pos) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN x_pos IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS x_pos,
       FIRST_VALUE(y_pos) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN y_pos IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS y_pos,
       FIRST_VALUE(status) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN status IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS status
  FROM part_changes
;

【讨论】:

这似乎解决了一个问题。谢谢。但是,我不确定“分组依据”会去哪里以便按零件号分组。有什么想法吗? @BatMasterson:那种细节取决于 SQL 的方言。从您对ORDER BY ... LIMIT 1 的使用来看,我猜您的目标是 mysql;那是对的吗? (如果是——或者如果不是——你应该编辑你的问题的标签。) 除非他避免他被卡住的限制1,所以我认为这种方法是一种混淆。 @BatMasterson:我已经扩展了我的答案。 啊,不同。我什至没有想到如何使用它。因此,您的替代答案非常有效。 ORDER BY CASE... 非常聪明。我永远不会想到这一点。谢谢,这是我需要的答案。【参考方案3】:

对于一个 part 这应该会给你一个答案.. 感谢 ruakh

但我不喜欢这个版本..

SELECT 
    (SELECT timestamp  FROM part_changes WHERE part = $part 
    ORDER BY timestamp DESC
    LIMIT 1) as timestamp,

    (SELECT x-pos FROM part_changes WHERE part = $part and x-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1) as xpos,

    (SELECT y-pos FROM part_changes WHERE part = $part and  y-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1) as ypos,

    (SELECT status FROM part_changes WHERE part = $part and status IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)) as status

【讨论】:

我同意,对每个部分进行递归处理有点粗糙。 我想我可以简单地编写一个函数,将零件号作为参数,然后使用它。谢谢。【参考方案4】:

ruak 是对的。备选方案:使用 SQL-CLR 编写用户定义的聚合。此聚合可以在您的行上自下而上运行,并记住每列的第一个非空值。

您也可以在应用程序中执行此操作。使您的程序请求行按批量排序,例如 10 行。如上所述聚合这些批次。如果在当前批次完成后有一个空列,则获取下一批。

【讨论】:

SQL-CLR 不会帮助 OP 处理 PostgreSQL。但是,它可以使用内置的东西来完成,并且有一个您所描述的示例 (FIRST) on the PostgreSQL wiki。

以上是关于数据库:选择最后一个非空条目的主要内容,如果未能解决你的问题,请参考以下文章

我想从数据库表中选择最后 5 个条目并将其显示在页面上。如何使用 JSP 实现它?

从ViewModel设置条目的焦点

Doctrine 只选择集合的最后一个条目

在几天的持续时间内,选择表格中记录的每一天的最后一个条目

excel如何快速选择第一行到最后一行

R子集嵌套列表,选择多个条目