SQL查找表中第一次出现的数据集

Mar*_*ark 6 sql database

假设我有一张桌子:

CREATE TABLE T
(
    TableDTM  TIMESTAMP  NOT NULL,
    Code      INT        NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

我插入一些行:

INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:00:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:10:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:20:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:30:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:40:00', 0);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 10:50:00', 1);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:00:00', 1);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:10:00', 1);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:20:00', 0);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:30:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:40:00', 5);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 11:50:00', 3);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 12:00:00', 3);
INSERT INTO T (TableDTM, Code) VALUES ('2011-01-13 12:10:00', 3);
Run Code Online (Sandbox Code Playgroud)

所以我最终得到了一张类似于的表:

2011-01-13 10:00:00, 5
2011-01-13 10:10:00, 5
2011-01-13 10:20:00, 5
2011-01-13 10:30:00, 5
2011-01-13 10:40:00, 0
2011-01-13 10:50:00, 1
2011-01-13 11:00:00, 1
2011-01-13 11:10:00, 1
2011-01-13 11:20:00, 0
2011-01-13 11:30:00, 5
2011-01-13 11:40:00, 5
2011-01-13 11:50:00, 3
2011-01-13 12:00:00, 3
2011-01-13 12:10:00, 3
Run Code Online (Sandbox Code Playgroud)

如何选择每组相同数字的第一个日期,所以我最终得到:

2011-01-13 10:00:00, 5
2011-01-13 10:40:00, 0
2011-01-13 10:50:00, 1
2011-01-13 11:20:00, 0
2011-01-13 11:30:00, 5
2011-01-13 11:50:00, 3
Run Code Online (Sandbox Code Playgroud)

在一天的大部分时间里,我一直在搞乱子查询等等,出于某种原因我似乎无法破解它.我确定在某个地方有一个简单的方法!

我可能想要从结果中排除0,但这对于现在来说并不重要.

Per*_*DBA 4

2011 年 1 月 15 日修订

\n\n

我确信某处有一个简单的方法!

\n\n

就在这里。但首先,有两个问题。

\n\n
    \n
  1. 该表不是关系数据库表。它没有唯一的键,这是 RM 和规范化所要求的(具体来说,每一行必须有一个唯一的标识符;不一定是 PK)。因此,SQL作为操作关系型数据库表的标准语言,无法对其进行基本操作。

    \n\n
      \n
    • 它是一个堆(数据结构,按时间顺序插入和删除),其中包含记录而不是行。
    • \n
    • 任何使用 SQL 的操作都会非常慢,而且不正确
    • \n
    • 将 ROWCOUNT 设置为 1,执行行处理,SQL 将在堆上正常工作
    • \n
    • 最好的选择是使用任何 UNIX 实用程序对其进行操作(awk、cut、chop)。他们的速度快得令人眼花缭乱。满足您的要求所需的 awk 脚本需要 3 分钟才能编写,并且它将在几秒钟内运行数百万条记录(我上周写了一些)。
      \n。
    • \n
    \n\n

    所以问题实际上是SQL 查找非关系堆中第一次出现的数据集

    \n\n

    现在,如果您的问题是用SQL 来查找关系表中数据集的第一次出现(这当然意味着一些唯一的行标识符),那么(a)在 SQL 中很容易,(b)在任何 SQL 风格中都很快。 。

    \n\n
      \n
    • 除了 Oracle,众所周知,它在处理子查询方面很糟糕(特别是 Tony Andrews 的评论,他是 Oracle 的知名权威)。在这种情况下,请使用物化视图。
      \n。
    • \n
  2. \n
  3. 这个问题很笼统(没有抱怨)。但这些特定需求中的许多通常应用在更大的上下文中,并且该上下文具有此处的规范中没有的要求。通常需要一个简单的子查询(但在 Oracle 中使用物化视图来避免子查询)。子查询也取决于外部上下文、外部查询。因此,对小通用问题的答案将不包含对实际特定需求的答案。

  4. \n
\n\n
\n\n

无论如何,我不想回避这个问题。为什么我们不使用一个现实世界的例子,而不是一个简单的通用例子?并在关系表中查找一组数据在另一组数据中的第一次或最后一次出现,或者最小值或最大值

\n\n

主要查询

\n\n

让我们使用上一个问题中的\xe2\x96\xb6Data Model\xe2\x97\x80 。

\n\n

全部举报Alerts自特定日期以来的所有情况(包括持续时间的峰值)Acknowledged

\n\n

由于您将使用完全相同的技术(具有不同的表和列名称)来满足所有时间和历史要求,因此您需要完全理解子查询的基本构造及其不同的应用程序。

\n\n

介绍

\n\n
\n

请注意,您不仅拥有带有关系标识符(复合键)的纯 5NF 数据库,而且自始至终都具有完整的时间功能,并且在不破坏 5NF(无更新异常)的情况下呈现时间要求,这意味着周期和持续时间ValidToDateTime是派生的,并且在数据中不重复。重点是,这使事情变得复杂,因此这不是 子查询教程的最佳示例

\n
\n\n
    \n
  • 请记住,SQL 引擎是一个集合处理器,因此我们以面向集合的思维方式来处理问题\n\n
      \n
    • 不要将引擎简化为行处理;那是很慢
    • \n
    • 更重要的是,不必要的
    • \n
  • \n
  • 子查询是普通的 SQL。我使用的语法是直接的 ISO/IEC/ANSI SQL。\n\n
      \n
    • 如果你不能用 SQL 编写子查询,那么你的能力将会非常有限;然后需要引入数据重复或使用大型结果集作为物化视图或临时表或各种附加数据和附加处理,这将很非常慢,更不用说完全不必要了
    • \n
    • 如果在不切换到行处理或内联视图或临时表的情况下,您无法在真正的关系数据库中(我的数据模型总是如此)做任何事情,请寻求帮助,这就是您在这里所做的。
    • \n
  • \n
  • 在尝试理解第二个子查询之前,您需要完全理解第一个子查询(更简单);ETC。
  • \n
\n\n

方法

\n\n

首先根据结构使用最小连接等构建外部查询仅此而已。首先解决外层查询的结构非常重要;否则,您将来回尝试使子查询适合外部查询,反之亦然。

\n\n
    \n
  • 这恰好也需要子查询。因此,暂时保留该部分,稍后再拾取。目前,外部查询获取全部(不是未确认的)Alerts特定日期之后的所有(不是未确认的)
  • \n
\n\n

\xe2\x96\xb6SQL代码\xe2\x97\x80位于第1页(抱歉,SO编辑功能太糟糕了,它破坏了格式,并且代码已经格式化)。

\n\n

然后构建子查询来填充每个单元格。

\n\n

子查询 (1) 派生 Alert.Value

\n\n

Value这是一个简单的派生数据,从Reading生成的数据中选择Alert。表是相关的,基数是1::1,所以它是PK上的直接连接。

\n\n
    \n
  • 这里需要的子查询类型是相关子查询,我们需要将外部查询中的表与(内部)子查询中的表相关联。\n\n
      \n
    • 为此,我们需要外部查询中的表的别名,以将其与子查询中的表关联起来。
    • \n
    • 为了进行区分,我仅使用别名来实现所需的关联,并使用完全限定名称来实现普通连接
    • \n
  • \n
  • 子查询在任何引擎中都非常快(Oracle 除外)
  • \n
  • SQL 是一种繁琐的语言。但这就是我们所拥有的一切。所以要习惯它。
  • \n
\n\n

\xe2\x96\xb6SQL代码\xe2\x97\x80位于第 2 页。

\n\n

我特意为您提供了外部查询中的连接与通过子查询获取数据的混合,以便您可以学习(您也可以Alert.Value通过连接获取,但这会更麻烦)。

\n\n

我们需要的下一个子查询 派生Alert.PeakValue。为此,我们需要确定 的时间持续时间Alert。我们有持续时间的开始Alert;我们需要确定持续时间的结束,这是范围内的下一个(暂时)Reading.Value。这还需要一个子查询,我们最好先处理它。

\n\n
    \n
  • 从内到外地处理逻辑。好老的BODMAS。
  • \n
\n\n

子查询 (2) 派生 Alert.EndDtm

\n\n

稍微复杂一点的 Suquery 选择第一个Reading.ReadingDtm,即大于或等于Alert.ReadingDtm,其中 aReading.Value小于或等于其Sensor.UpperLimit

\n\n

处理 5NF 时态数据

\n\n

为了处理 5NF 数据库中的时间要求(其中EndDateTime存储重复数据),我们只处理 a StartDateTime,并EndDateTime导出:它是一个 StartDateTime这是Duration的时间概念。

\n\n
    \n
  • 从技术上讲,它要少一毫秒(无论使用的数据类型的分辨率如何)。
  • \n
  • 然而,为了合理起见,我们可以EndDateTime简单地谈论和报告Next.StartDateTime,并忽略一毫秒的问题。
  • \n
  • 代码应始终使用>= This.StartDateTime< Next.StartDateTime。\n\n
      \n
    • 这消除了一系列可以避免的错误
    • \n
    • 请注意,这些比较运算符将时间持续时间括起来,并且应该按照上面的常规方式使用,它们完全独立于与业务逻辑相关的类似比较运算符,例如。Sensor.UpperLimit(即注意它,因为两者通常位于一个WHERE子句中,并且很容易将它们混淆或混淆)。
    • \n
  • \n
\n\n

所需的 \xe2\x96\xb6SQL代码\xe2\x97\x80以及使用的测试数据位于第 3 页。

\n\n

子查询 (3) 派生 Alert.PeakValue

\n\n

现在很容易了。MAX(Value)从和Readings之间选择,持续时间Alert.ReadingDtmAlert.EndDtmAlert

\n\n

\xe2\x96\xb6SQL代码\xe2\x97\x80位于第 4 页。

\n\n

标量子查询

\n\n

除了相关子查询之外,以上都是标量子查询,因为它们返回单个值;网格中的每个单元格只能填充一个值。(返回多个值的非标量子查询是相当合法的,但不适用于上述情况。)

\n\n

子查询 (4) 已确认的警报

\n\n

好的,既然您已经掌握了上述相关标量子查询,即填充集合中的单元格的子查询,集合是由外部查询定义的,让我们看一下可用于约束外部查询的子查询。我们并不真正想要所有 Alerts(上面),我们想要Un-Acknowledged Alerts:存在于 中的标识符Alert,不存在于 中的标识符Acknowledgement。这不是填充单元格,而是更改外部集的内容。当然,这意味着改变WHERE条款。

\n\n
    \n
  • 我们不会更改外部集的结构FROM,因此和现有 子句没有变化WHERE
  • \n
\n\n

只需添加一个WHERE条件即可排除 的集合Acknowledged Alerts。1::1 基数,直接相关连接。

\n\n

所需的 \xe2\x96\xb6SQL代码\xe2\x97\x80位于第 5 页。

\n\n

不同之处在于,这是一个非标量子查询,生成一组行(一列)。我们有一整套Alerts(外部集)与一整套Acknowledgements.

\n\n
    \n
  • 处理匹配是因为我们已经通过使用别名告诉引擎子查询是相关的(不需要识别繁琐的连接)
  • \n
  • 使用1,因为我们正在执行存在性检查。将其可视化为添加到Alert外部查询定义的集合上的列。
  • \n
  • 切勿使用 *,因为我们不需要整组列,而且速度会更慢
  • \n
  • 同样,未能使用相关性意味着WHERE NOT IN ()需要 a,但同样,它会构造定义的列集,然后比较两个集。慢得多。
  • \n
\n\n

子查询 (5) Actioned Alerts

\n\n

作为外部查询的替代约束,对于未执行的Alerts,而不是 (4),排除 的集合Actioned Alerts。直接关联连接。

\n\n

所需的 \xe2\x96\xb6SQL代码\xe2\x97\x80位于第 5 页。

\n\n

此代码已在 Sybase ASE 15.0.3 上使用不同组合的1000Alerts和 200进行了测试;Acknowledgements以及文件中确定的Readings和。Alerts所有执行的执行时间为零毫秒(分辨率为 0.003 秒)。

\n\n

如果您需要它,这里是文本格式的 \xe2\x96\xb6SQL 代码\xe2\x97\x80

\n\n

对评论的回应

\n\n

(6) \xe2\x96\xb6Register Alert from Reading\xe2\x97\x80
\n此代码在循环中执行(已提供),选择Readings超出范围的 new 并创建Alerts,除非Alerts已存在适用的情况。

\n\n

(7) \xe2\x96\xb6Load Alert From Reading\xe2\x97\x80
\n鉴于您有 的完整测试数据集Reading,此代码使用 (6) 的修改形式来加载适用的Alerts.

\n\n

常见问题

\n\n

当你知道如何做时,这就是“简单”。我再说一遍,如果没有编写子查询的能力,编写 SQL 的能力是非常有限的;它对于处理关系数据库至关重要,而这正是 SQL 的设计目的。

\n\n
    \n
  • 开发人员实现非规范化数据堆(大量数据重复)的一半原因是因为他们无法编写规范化结构所需的子查询\n\n
      \n
    • 这并不是说他们“为了性能而进行了非规范化”;问题是他们无法为规范化编码。我已经看过一百遍了。
    • \n
    • 这里的例子是:您有一个完全规范化的关系数据库,困难在于对其进行编码,并且您正在考虑复制表以进行处理。
    • \n
  • \n
  • 这还不包括时态数据库增加的复杂性。或 5NF 时态数据库。
  • \n
  • 规范化意味着从不重复任何内容,最近称为“不要重复自己”
  • \n
  • 掌握 Suqueries,您将处于第 98 个百分点:标准化、真正的关系数据库;零数据重复;非常高的性能。
  • \n
\n\n

我想你可以弄清楚剩下的疑问。

\n\n

关系标识符

\n\n

请注意,此示例也恰好演示了使用关系标识符的强大功能,因为我们想要的表之间的多个表不必连接(是的!事实是关系标识符意味着比Id键更少而不是更多的连接)。只需遵循实线即可。

\n\n
    \n
  • 您的临时要求需要包含DateTime. 想象一下尝试使用IdPK 来编写上述内容,将会有两个处理级别:一个用于连接(并且会有更多的连接),另一个用于数据处理。
  • \n
\n\n

标签

\n\n

我尽量避免使用口语标签(“嵌套”、“内部”等),因为它们不具体,并坚持使用特定的技术术语。为了完整性和理解:

\n\n
    \n
  • 子句后面的子查询FROM是一个物化视图FROM,是一个查询中派生的结果集,然后作为“表”输入到另一个查询的子句中。\n\n
      \n
    • Oracle 类型将此称为内联视图。
    • \n
    • 在大多数情况下,您可以将相关子查询编写为物化视图,但这需要大量的 I/O 和处理(因为 Oracle 对子查询的处理非常糟糕,仅对于 Oracle,物化视图“更快”)。
      \n。
    • \n
  • \n
  • 子句中的子查询WHERE谓词子查询,因为它更改结果集的内容(它所依据的内容)。它可以返回标量(一个值)或非标量(多个值)。

    \n\n
      \n
    • 对于标量,使用WHERE column =或任何标量运算符

    • \n
    • 对于非标量,使用WHERE [NOT] EXISTS, 或WHERE column [NOT] IN

    • \n
  • \n
  • 子句中的 SuqueryWHERE不需要关联;以下工作正常。识别所有多余的附属物:

    SELECT  [Never] = FirstName,\n        [Acted] = LastName \n    FROM User \n    WHERE UserId NOT IN ( SELECT DISTINCT UserId\n        FROM Action\n        )
    Run Code Online (Sandbox Code Playgroud)

  • \n
\n