簡體   English   中英

SQL 查找表中第一個出現的數據集

[英]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,但現在這並不重要。

1月15日修訂

我確定在某個地方有一個簡單的方法!

就在這里。 但首先是兩個問題。

  1. 該表不是關系數據庫表。 它沒有唯一的密鑰,這是RM和規范化所要求的(具體地說,每一行必須具有唯一的標識符;不一定是PK)。 因此,SQL(一種用於在關系數據庫表上操作的標准語言)無法對其執行基本操作。

    • 它是一個堆(數據結構,按時間順序插入和刪除),記錄不是行。
    • 使用SQL的任何和所有操作都會非常慢,並且不正確
    • 將ROWCOUNT設置為1,執行行處理,SQL就可以在Heap上運行了
    • 你最好的選擇是使用任何unix utiliy來操作它(awk,cut,chop)。 他們的速度非常快。 回答你的要求所需的awk腳本需要3分鍾才能寫入,並且它會在幾秒鍾內運行數百萬條記錄(我上周寫了一些)。

    所以問題實際上是SQL在非關系堆中找到第一組數據集

    現在,如果你的問題是SQL來查找Relational表中第一次出現的數據集,當然暗示一些唯一的行標識符,這將是(a)在SQL中很容易,以及(b)快速的任何SQL的風格。 。

    • 除了Oracle之外,已知它會嚴重處理子查詢 (特別是Tony Andrews的評論,他是Oracle的知名權威)。 在這種情況下,請使用物化視圖。
  2. 這個問題非常通用(沒有投訴)。 但是,這些特定需求中的許多通常在更大的上下文中應用,並且上下文具有本說明書中不存在的要求。 通常需要一個簡單的子查詢(但在Oracle中使用物化視圖來避免子查詢)。 子查詢也取決於外部上下文,外部查詢。 因此,小通用問題的答案將不包含實際特定需求的答案。


無論如何,我不想回避這個問題。 為什么我們不使用現實世界的例子,而不是簡單的通用例子; 在Relational表中查找另一組數據中的一組數據的第一個或最后一個或最小值或最大值

主要查詢

讓我們使用上一個問題中的▶數據模型◀

報告自特定日期以來的所有Alerts ,其持續時間的峰值為未Acknowledged

由於您將使用完全相同的技術(具有不同的表和列名稱)來滿足您的所有時間和歷史要求,因此您需要完全理解子查詢的基本構造及其不同的應用程序。

介紹

請注意,您不僅擁有純5NF數據庫和關系標識符(復合鍵),而且您具有完整的Temporal功能,並且在不破壞5NF(無更新異常)的情況下呈現時間要求,這意味着ValidToDateTime和持續時間的ValidToDateTime是派生的,而不是在數據中重復。 點是,這使事情變得復雜,因此這 不是子查詢教程的最佳示例

  • 請記住,SQL引擎是一個集處理器,因此我們采用面向集合的思維方式解決問題
    • 不要把發動機笨到行處理; 很慢
    • 更重要的是, 不必要的
  • 子查詢是普通的SQL。 我使用的語法是直接的ISO / IEC / ANSI SQL。
    • 如果你不能在SQL中編寫子查詢,那么你將非常有限; 然后需要引入數據復制或使用較大的結果集物化視圖或臨時表或其他數據和附加處理的所有方式,這將是緩慢速度很慢 ,更不用說完全不必要
    • 如果在真正的關系數據庫(以及我的數據模型總是如此)中無法做任何事情而沒有切換到行處理或內聯視圖或臨時表,請求幫助,這就是你在這里所做的。
  • 在嘗試理解第二個子查詢之前,您需要完全理解第一個子查詢(更簡單); 等等

方法

首先根據您需要的結果集的結構 ,使用最小連接等構建外部查詢,僅此而已。 首先解析外部查詢的結構是非常重要的; 否則你會來回試圖使子查詢適合外部查詢,反之亦然。

  • 這恰好需要子查詢。 所以現在就把這個部分留下來,然后再把它拿出來。 現在,外部查詢在特定日期之后獲得所有(不是未確認的) Alerts

需要▶SQL代碼◀是第1頁(抱歉,SO編輯功能很糟糕,它會破壞格式化,代碼已經格式化)。

然后構建子查詢以填充每個單元格。

子查詢(1)導出 Alert.Value

這是一個簡單的派生數據,從生成AlertReading中選擇Value 這些表是相關的,基數是1 :: 1,所以它是PK上的直接連接。

  • 這里需要的子查詢類型是相關子查詢 ,我們需要將外部查詢中的表與(內部)子查詢中的表相關聯。
    • 為了做到這一點,我們需要外部查詢中的表的別名,以將其與子查詢中的表相關聯。
    • 為了區分,我只使用別名來進行這種必需的關聯,並使用普通連接的完全限定名
  • 任何引擎中的子查詢都非常快(Oracle除外)
  • SQL是一種繁瑣的語言。 但這就是我們所擁有的一切。 所以習慣它。

需要▶SQL代碼◀是第2頁。

我有意在外部查詢中添加了多個連接,並通過子查詢獲取數據,這樣您就可以學習(您可以通過連接交替獲取Alert.Value ,但這會更加麻煩 )。

我們需要的下一個子查詢派生Alert.PeakValue 為此,我們需要確定Alert的時間持續時間。 我們有Alert持續時間的開始; 我們需要確定持續時間的結束,這是范圍內下一個 (暫時) Reading.Value 這也需要一個Subquery,我們最好先處理它。

  • 從內到外工作邏輯。 好老BODMAS。

子查詢(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.ReadingDtmAlert.EndDtm之間的Readings選擇MAX(Value) ,即Alert的持續時間。

需要▶SQL代碼◀是第4頁。

標量子查詢

除了是相關子查詢之外,以上都是標量子查詢 ,因為它們返回單個值; 網格中的每個單元格只能填充一個值。 (返回多個值的非標量子查詢非常合法,但不適用於上述情況。)

子查詢(4)已確認的警報

好了,現在你已經掌握了上面的相關標量子查詢,那些填充集合中單元格的子集,一個由外部查詢定義的集合,讓我們看一下可以用來約束外部查詢的子查詢。 我們並不真正想要所有 Alerts (上圖),我們需要Un-Acknowledged AlertsAlert中存在的標識符,在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的不同組合進行了測試; 以及文件中確定的ReadingsAlerts 所有執行的零毫秒執行時間(0.003秒分辨率)。

如果需要,可以使用文本格式▶SQL代碼◀

對評論的回應

(6) ▶從閱讀◀注冊提醒
此代碼在循環(提供)中執行,選擇超出范圍的新Readings ,並創建Alerts ,除非適用的Alerts已存在。

(7) ▶從閱讀◀加載警報
鑒於您有一整套用於Reading的測試數據,此代碼使用修改后的(6)形式加載適用的Alerts

常見問題

當你知道如何時,它是“簡單的”。 我再說一遍,編寫沒有編寫子查詢能力的SQL是非常有限的; 它對於處理關系數據庫至關重要,這是SQL的設計目標。

  • 開發人員實現非標准化數據堆(海量數據重復)的一半原因是因為它們無法編寫規范化結構所需的子查詢
    • 並不是說他們“為表現而非正常化”; 它們無法編碼為Normalized。 我已經看過它一百次了。
    • 舉個例子:你有一個完全標准化的關系數據庫,困難就是為它編碼,你正在考慮復制表以便進行處理。
  • 這並不包括時間數據庫增加的復雜性; 或5NF時態數據庫。
  • 規范化意味着永遠不要復制任何東西 ,最近被稱為“ 不要重復自己”
  • Master Suqueries,你將處於第98個百分點:規范化,真實的關系數據庫; 零數據重復; 非常高的性能。

我想你可以找出你剩下的查詢。

關系標識符

注意,這個例子也恰好證明了使用關系標識符的能力 ,因為我們想要的幾個表之間不必連接(是的!事實是關系標識符意味着更少,而不是更多,連接,而不是Id鍵)。 只需按照實線。

  • 您的時間要求需要包含DateTime鍵。 想象一下嘗試用Id PKs編寫上面的代碼,會有兩個級別的處理:一個用於連接(並且會有更多的連接),另一個用於數據處理。

標簽

我試圖遠離口語標簽(“嵌套”,“內部”等)因為它們不具體,並堅持特定的技術術語。 為了完整和理解:

  • FROM子句之后的子查詢,是一個物化視圖 ,一個查詢中派生的結果集,然后作為“表”輸入另一個查詢的FROM子句。
    • Oracle類型調用此內聯視圖。
    • 在大多數情況下,您可以將相關子查詢編寫為物化視圖,但這大大增加了I / O和處理(因為Oracles處理子查詢非常簡單,僅對Oracle而言,物化視圖“更快”)。
  • WHERE子句中的子查詢謂詞子查詢 ,因為它更改了結果集的內容(它所基於的內容)。 它可以返回標量(一個值)或非標量(多個值)。

    • 對於Scalars,請使用WHERE column =或任何標量運算符

    • 對於非Scalars,使用WHERE [NOT] EXISTSWHERE 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM