[英]SQL to find the first occurrence of sets of data in a table
假设我有一张桌子:
CREATE TABLE T
(
TableDTM TIMESTAMP NOT NULL,
Code INT NOT NULL
);
我插入一些行:
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);
所以我最终得到了一个类似于以下的表:
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
我怎样才能 select 每组相同数字的第一个日期,所以我最终得到这个:
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
在一天的大部分时间里,我一直在处理子查询之类的问题,但出于某种原因,我似乎无法破解它。 我敢肯定在某个地方有一个简单的方法!
我可能想从结果中排除 0,但现在这并不重要。
我确定在某个地方有一个简单的方法!
就在这里。 但首先是两个问题。
该表不是关系数据库表。 它没有唯一的密钥,这是RM和规范化所要求的(具体地说,每一行必须具有唯一的标识符;不一定是PK)。 因此,SQL(一种用于在关系数据库表上操作的标准语言)无法对其执行基本操作。
所以问题实际上是SQL在非关系堆中找到第一组数据集 。
现在,如果你的问题是SQL来查找Relational表中第一次出现的数据集,当然暗示一些唯一的行标识符,这将是(a)在SQL中很容易,以及(b)快速的任何SQL的风格。 。
这个问题非常通用(没有投诉)。 但是,这些特定需求中的许多通常在更大的上下文中应用,并且上下文具有本说明书中不存在的要求。 通常需要一个简单的子查询(但在Oracle中使用物化视图来避免子查询)。 子查询也取决于外部上下文,外部查询。 因此,小通用问题的答案将不包含实际特定需求的答案。
无论如何,我不想回避这个问题。 为什么我们不使用现实世界的例子,而不是简单的通用例子; 并在Relational表中查找另一组数据中的一组数据的第一个或最后一个或最小值或最大值 ?
主要查询
让我们使用上一个问题中的▶数据模型◀ 。
报告自特定日期以来的所有Alerts
,其持续时间的峰值为未Acknowledged
由于您将使用完全相同的技术(具有不同的表和列名称)来满足您的所有时间和历史要求,因此您需要完全理解子查询的基本构造及其不同的应用程序。
请注意,您不仅拥有纯5NF数据库和关系标识符(复合键),而且您具有完整的Temporal功能,并且在不破坏5NF(无更新异常)的情况下呈现时间要求,这意味着
ValidToDateTime
和持续时间的ValidToDateTime
是派生的,而不是在数据中重复。 点是,这使事情变得复杂,因此这 不是子查询教程的最佳示例 。
首先根据您需要的结果集的结构 ,使用最小连接等构建外部查询,仅此而已。 首先解析外部查询的结构是非常重要的; 否则你会来回试图使子查询适合外部查询,反之亦然。
Alerts
需要▶SQL代码◀是第1页(抱歉,SO编辑功能很糟糕,它会破坏格式化,代码已经格式化)。
然后构建子查询以填充每个单元格。
子查询(1)导出 Alert.Value
这是一个简单的派生数据,从生成Alert
的Reading
中选择Value
。 这些表是相关的,基数是1 :: 1,所以它是PK上的直接连接。
需要▶SQL代码◀是第2页。
我有意在外部查询中添加了多个连接,并通过子查询获取数据,这样您就可以学习(您可以通过连接交替获取Alert.Value
,但这会更加麻烦 )。
我们需要的下一个子查询派生Alert.PeakValue
。 为此,我们需要确定Alert
的时间持续时间。 我们有Alert
持续时间的开始; 我们需要确定持续时间的结束,这是范围内的下一个 (暂时) Reading.Value
。 这也需要一个Subquery,我们最好先处理它。
子查询(2)导出 Alert.EndDtm
稍微复杂Suquery以选择第一Reading.ReadingDtm
,即大于或等于Alert.ReadingDtm
,具有Reading.Value
小于或等于其Sensor.UpperLimit
。
处理5NF时态数据
为了处理5NF数据库中的时间要求(其中未存储EndDateTime
,如同重复数据),我们仅处理StartDateTime
,并导出 EndDateTime
:它是下一个 StartDateTime
。 这是持续时间的时间概念。
EndDateTime
并将其报告为Next.StartDateTime
,并忽略一毫秒的问题。 This.StartDateTime
和< Next.StartDateTime
。
Sensor.UpperLimit
(即监视它,因为它们通常都位于一个WHERE
子句中,很容易将它们混淆或混淆)。 所需的▶SQL代码◀以及使用的测试数据在第3页。
子查询(3)导出 Alert.PeakValue
现在很容易。 从Alert.ReadingDtm
和Alert.EndDtm
之间的Readings
选择MAX(Value)
,即Alert
的持续时间。
需要▶SQL代码◀是第4页。
标量子查询
除了是相关子查询之外,以上都是标量子查询 ,因为它们返回单个值; 网格中的每个单元格只能填充一个值。 (返回多个值的非标量子查询非常合法,但不适用于上述情况。)
子查询(4)已确认的警报
好了,现在你已经掌握了上面的相关标量子查询,那些填充集合中单元格的子集,一个由外部查询定义的集合,让我们看一下可以用来约束外部查询的子查询。 我们并不真正想要所有 Alerts
(上图),我们需要Un-Acknowledged Alerts
: Alert
中存在的标识符,在Acknowledgement
不存在。 那不是填充单元格,即改变外部集合的内容 。 当然,这意味着更改WHERE
子句。
FROM
和现有 WHERE
子句没有变化。 只需添加WHERE
条件即可排除已Acknowledged Alerts
。 1 :: 1基数,直相关联接。
需要▶SQL代码◀是第5页。
不同的是,这是一个非标量子查询 ,产生一组行(一列)。 我们有一整套Alerts
(外部集)与一整套Acknowledgements
相匹配。
1
,因为我们正在执行存在检查。 将其可视化为添加到外部查询定义的Alert
集上的列。 WHERE NOT IN ()
,但同样,构造定义的列集,然后比较两个集。 慢得多。 子查询(5) Actioned Alerts
作为外部查询的替代约束,对于未执行的Alerts
,而不是(4),排除一组Actioned Alerts
。 直接相关联接。
需要▶SQL代码◀是第5页。
此代码已在Sybase ASE 15.0.3上使用1000个Alerts
和200个已Acknowledgements
的不同组合进行了测试; 以及文件中确定的Readings
和Alerts
。 所有执行的零毫秒执行时间(0.003秒分辨率)。
(6) ▶从阅读◀注册提醒
此代码在循环(提供)中执行,选择超出范围的新Readings
,并创建Alerts
,除非适用的Alerts
已存在。
(7) ▶从阅读◀加载警报
鉴于您有一整套用于Reading
的测试数据,此代码使用修改后的(6)形式加载适用的Alerts
。
当你知道如何时,它是“简单的”。 我再说一遍,编写没有编写子查询能力的SQL是非常有限的; 它对于处理关系数据库至关重要,这是SQL的设计目标。
我想你可以找出你剩下的查询。
注意,这个例子也恰好证明了使用关系标识符的能力 ,因为我们想要的几个表之间不必连接(是的!事实是关系标识符意味着更少,而不是更多,连接,而不是Id
键)。 只需按照实线。
DateTime
键。 想象一下尝试用Id
PKs编写上面的代码,会有两个级别的处理:一个用于连接(并且会有更多的连接),另一个用于数据处理。 我试图远离口语标签(“嵌套”,“内部”等)因为它们不具体,并坚持特定的技术术语。 为了完整和理解:
FROM
子句之后的子查询,是一个物化视图 ,一个查询中派生的结果集,然后作为“表”输入另一个查询的FROM
子句。
WHERE
子句中的子查询是谓词子查询 ,因为它更改了结果集的内容(它所基于的内容)。 它可以返回标量(一个值)或非标量(多个值)。
对于Scalars,请使用WHERE column =
或任何标量运算符
对于非Scalars,使用WHERE [NOT] EXISTS
或WHERE column [NOT] IN
WHERE
子句中的Suquery 不需要相关; 以下工作正常。 识别所有多余的附属物:
SELECT [Never] = FirstName, [Acted] = LastName FROM User WHERE UserId NOT IN ( SELECT DISTINCT UserId FROM Action )
PostgreSQL支持窗口函数,看看这个
[编辑]尝试以下方法:
SELECT TableDTM, Code FROM
(
SELECT TableDTM,
Code,
LAG(Code, 1, NULL) OVER (ORDER BY TableDTM) AS PrevCode
FROM T
)
WHERE PrevCode<>Code OR PrevCode IS NULL;
试试这个:
SELECT MIN(TableDTM) TableDTM, Code
FROM
(
SELECT T1.TableDTM, T1.Code, MIN(T2.TableDTM) XTableDTM
FROM T T1
LEFT JOIN T T2
ON T1.TableDTM <= T2.TableDTM
AND T1.Code <> T2.Code
GROUP BY T1.TableDTM, T1.Code
) X
GROUP BY XTableDTM, Code
ORDER BY 1;
你可以尝试一下吗?
"SELECT DISTINCT Code, (SELECT MIN(TableDTM) FROM T AS Q WHERE Q.Code = T.Code) As TableDTM FROM T;"
如果您需要排除0,请将其更改为:
SELECT DISTINCT Code, (SELECT MIN(TableDTM) FROM T AS Q WHERE Q.Code = T.Code) As TableDTM FROM T WHERE Code <> 0;
也许我不明白这个问题。 但我没有看到任何关于公用表表达式或分析函数的提及。 这些是我解决大多数问题的首选武器,当它们无法处理时,我开始求助于临时表。
我想,我最近解决了一个类似的问题,在处理一个日常的接口文件时,想获取第一次出错的数据。 接口上出现问题的记录被移除到一组保持表中,以便处理rest条记录。
-- EE with errors removed from most recent batch
with current_batch as (
select employee_number, PVL.ADDITIONAL_INFORMATION
from PERSONNEL_VALIDATION_LOG_X PVL
where PVL.PERSONNEL_BATCH_ID = EMPSRV.CURRENTPERSONNELBATCH(6,900)
)
, hist as (
select
row_number() over (
partition by X.EMPLOYEE_NUMBER, X.ADDITIONAL_INFORMATION
order by B.BATCH_STATUS_DATE
) as RN,
B.PERSONNEL_BATCH_ID BatchId,
B.SUBMITTAL_DATE,
X.EMPLOYEE_NUMBER EMPNUM,
MX.LAST_NAME,
MX.FIRST_NAME,
X.ADDITIONAL_INFORMATION
from PERSONNEL_VALIDATION_LOG_X X
join current_batch C on
X.Employee_number = C.EMPLOYEE_NUMBER
and X.additional_information = C.ADDITIONAL_INFORMATION
join empsrv.personnel_batch B
on B.PERSONNEL_BATCH_ID = X.PERSONNEL_BATCH_ID
join EMPSRV.PERSONNEL_MEMBER_DATA_X MX
on X.PERSONNEL_BATCH_ID = MX.PERSONNEL_BATCH_ID
and X.EMPLOYEE_NUMBER = MX.EMPLOYEE_NUMBER
)
select
batchId,
to_char(submittal_date, 'mm/dd/yyyy') First_Reported,
EmpNum,
Last_name,
first_name,
additional_information
from hist where rn = 1
order by submittal_date desc;
第一个 CTE 只是将总体限制为当前错误。 hist CTE 遍历日志并找出该错误的第一次出现(即 ame EE 和 messge)这并不完美,因为错误可能消失并返回,我会得到最旧的出现而不是开始最近的序列。 但这已经足够好了,而且不太可能是由于错误消息本身的形状。 finally 查询只是选择每个组的第一行,这将是第一次出现。
查询需要几秒钟才能运行,但我的日志不是特别大,所以性能对我来说几乎从来都不是问题。 我也不太注意问题的日期。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.