พยายามค้นหาครั้งล่าสุดที่มีการเปลี่ยนแปลงค่า


26

ฉันมีตารางที่มี ID ค่าและวันที่ มี ID, ค่าและวันที่จำนวนมากในตารางนี้

บันทึกจะถูกแทรกลงในตารางนี้เป็นระยะ ID จะยังคงเหมือนเดิม แต่บางครั้งค่าจะเปลี่ยน

ฉันจะเขียนคิวรีที่จะให้ ID พร้อมกับเวลาที่มีการเปลี่ยนแปลงค่าล่าสุดได้อย่างไร หมายเหตุ: ค่าจะเพิ่มขึ้นเสมอ

จากข้อมูลตัวอย่างนี้:

  Create Table Taco
 (  Taco_ID int,
    Taco_value int,
    Taco_date datetime)

Insert INTO Taco 
Values (1, 1, '2012-07-01 00:00:01'),
        (1, 1, '2012-07-01 00:00:02'),
        (1, 1, '2012-07-01 00:00:03'),
        (1, 1, '2012-07-01 00:00:04'),
        (1, 2, '2012-07-01 00:00:05'),
        (1, 2, '2012-07-01 00:00:06'),
        (1, 2, '2012-07-01 00:00:07'),
        (1, 2, '2012-07-01 00:00:08')

ผลลัพธ์ควรเป็น:

Taco_ID      Taco_date
1            2012-07-01 00:00:05

(เนื่องจาก 00:05 เป็นครั้งสุดท้ายที่Taco_Valueมีการเปลี่ยนแปลง)


2
ฉันคิดว่าtacoไม่มีอะไรเกี่ยวข้องกับอาหารเหรอ?
Kermit

5
ฉันหิวและอยากทานทาโก้ ต้องการชื่อสำหรับตารางตัวอย่าง
SqlSandwiches

8
คุณเลือกชื่อผู้ใช้ของคุณในแบบเดียวกันหรือไม่?
Martin Smith

1
เป็นไปได้ค่อนข้างมาก
SqlSandwiches

คำตอบ:


13

แบบสอบถามทั้งสองนี้ขึ้นอยู่กับสมมติฐานที่Taco_valueเพิ่มขึ้นตลอดเวลา

;WITH x AS
(
  SELECT Taco_ID, Taco_date,
    dr = ROW_NUMBER() OVER (PARTITION BY Taco_ID, Taco_Value ORDER BY Taco_date),
    qr = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date)
  FROM dbo.Taco
), y AS
(
  SELECT Taco_ID, Taco_date,
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID, dr ORDER BY qr DESC)
  FROM x WHERE dr = 1
)
SELECT Taco_ID, Taco_date
FROM y 
WHERE rn = 1;

ทางเลือกที่มีความบ้าคลั่งของฟังก์ชั่นหน้าต่างน้อยลง:

;WITH x AS
(
  SELECT Taco_ID, Taco_value, Taco_date = MIN(Taco_date)
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
), y AS
(
  SELECT Taco_ID, Taco_date, 
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM x
)
SELECT Taco_ID, Taco_date FROM y WHERE rn = 1;

ตัวอย่างที่SQLfiddle


ปรับปรุง

สำหรับการติดตามนั้นมีการโต้เถียงกันว่าจะเกิดอะไรขึ้นหากTaco_valueสามารถทำซ้ำได้ หากสามารถไปได้ตั้งแต่ 1 ถึง 2 จากนั้นกลับเป็น 1 สำหรับTaco_IDการสอบถามใด ๆแบบสอบถามจะไม่ทำงาน นี่เป็นวิธีแก้ปัญหาสำหรับกรณีนี้แม้ว่าจะไม่ใช่เทคนิคช่องว่างและเกาะที่คนอย่าง Itzik Ben-Gan อาจฝันถึงและแม้ว่ามันจะไม่เกี่ยวข้องกับสถานการณ์ของ OP ก็ตาม เกี่ยวข้องกับผู้อ่านในอนาคต มันซับซ้อนกว่านี้เล็กน้อยและฉันยังเพิ่มตัวแปรเพิ่มเติม - Taco_IDซึ่งมีเพียงหนึ่งตัวTaco_valueเท่านั้น

หากคุณต้องการรวมแถวแรกสำหรับ ID ใด ๆ ที่ค่าไม่เปลี่ยนแปลงในชุดทั้งหมด:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT  
  main.Taco_ID, 
  Taco_date = MIN(CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main LEFT OUTER JOIN rest
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
) 
GROUP BY main.Taco_ID;

หากคุณต้องการยกเว้นแถวเหล่านั้นมันซับซ้อนกว่าเล็กน้อย แต่ยังคงมีการเปลี่ยนแปลงเล็กน้อย:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT 
  main.Taco_ID, 
  Taco_date = MIN(
  CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main INNER JOIN rest -- ***** change this to INNER JOIN *****
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
)
AND EXISTS -- ***** add this EXISTS clause ***** 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND Taco_value <> rest.Taco_value
)
GROUP BY main.Taco_ID;

อัปเดตตัวอย่าง SQLfiddle


ฉันสังเกตเห็นปัญหาด้านประสิทธิภาพที่สำคัญบางอย่างของ OVER แต่ฉันใช้มันเพียงไม่กี่ครั้งและอาจเขียนได้ไม่ดี คุณสังเกตเห็นอะไรบ้าง
Kenneth Fisher

1
@KennethFisher ไม่เฉพาะเจาะจงกับ OVER เช่นเดียวกับสิ่งอื่นการสร้างคิวรีขึ้นอยู่กับสคีมา / ดัชนีพื้นฐานเพื่อให้ทำงานอย่างถูกต้อง ส่วนคำสั่งที่พาร์ทิชันจะประสบปัญหาเช่นเดียวกับกลุ่มตาม
Aaron Bertrand

@ เคนเน็ ธ ฟิชเชอร์โปรดระวังอย่าสรุปข้อสรุปที่กว้างขวางจากการสังเกตการณ์เดี่ยว ๆ ฉันเห็นข้อโต้แย้งแบบเดียวกันกับ CTE - "เอาล่ะฉันมี CTE แบบเรียกซ้ำอีกครั้งและประสิทธิภาพก็แย่ลงดังนั้นฉันจะไม่ใช้ CTE อีกต่อไป"
Aaron Bertrand

นั่นเป็นเหตุผลที่ฉันถาม ฉันไม่ได้ใช้มันมากพอที่จะพูดไม่ทางใดก็ทางหนึ่ง แต่ไม่กี่ครั้งที่ฉันใช้มันฉันก็สามารถเพิ่มประสิทธิภาพด้วย CTE ฉันจะเล่นกับมันต่อไป
Kenneth Fisher

@AaronBertrand ฉันไม่คิดว่าสิ่งเหล่านี้จะทำงานได้ถ้าvalueปรากฏขึ้นอีกครั้ง: ซอ
ypercubeᵀᴹ

13

โดยทั่วไปนี่เป็นคำแนะนำของ @ Taryn "ย่อ" เป็น SELECT เดี่ยวโดยไม่มีตารางที่ได้รับ:

SELECT DISTINCT
  Taco_ID,
  Taco_date = MAX(MIN(Taco_date)) OVER (PARTITION BY Taco_ID)
FROM Taco
GROUP BY
  Taco_ID,
  Taco_value
;

หมายเหตุ: โซลูชันนี้จะพิจารณาถึงข้อกำหนดที่Taco_valueสามารถเพิ่มได้เท่านั้น (ยิ่งไปกว่านั้นคือสมมติว่าTaco_valueไม่สามารถเปลี่ยนกลับไปเป็นค่าก่อนหน้า - เช่นเดียวกับคำตอบที่เชื่อมโยงในความเป็นจริง)

ตัวอย่าง SQL Fiddle สำหรับการสืบค้น: http://sqlfiddle.com/#!3/91368/2


7
อ๊ะซ้อนกัน MAX / MIN MIND BLOWN +1
Aaron Bertrand

7

คุณควรจะสามารถใช้ทั้งฟังก์ชั่นmin()และmax()ฟังก์ชั่นรวมได้รับผล:

select t1.Taco_ID, MAX(t1.taco_date) Taco_Date
from taco t1
inner join
(
    select MIN(taco_date) taco_date,
        Taco_ID, Taco_value
    from Taco
    group by Taco_ID, Taco_value
) t2
    on t1.Taco_ID = t2.Taco_ID
    and t1.Taco_date = t2.taco_date
group by t1.Taco_Id

ดูSQL Fiddle พร้อมเดโม


5

อีกหนึ่งคำตอบที่ตั้งอยู่บนสมมติฐานที่ว่าค่าจะไม่ปรากฏขึ้นอีกครั้ง (นี่คือพื้นฐาน @ แอรอนของคิวรี 2, ย่อในหนึ่งรังน้อย):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MIN(Taco_date) DESC),
    Taco_date = MIN(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT Taco_ID, Taco_value, Taco_date
FROM x 
WHERE Rn = 1 ;

ทดสอบที่: SQL-Fiddle


และคำตอบสำหรับปัญหาทั่วไปที่ค่าสามารถปรากฏขึ้นอีกครั้ง:

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.Taco_ID, Taco_date = MIN(t.Taco_date)
FROM x
  JOIN dbo.Taco t
    ON  t.Taco_ID = x.Taco_ID
    AND t.Taco_date > x.Taco_date
WHERE x.Rn = 2 
GROUP BY t.Taco_ID ;

(หรือใช้CROSS APPLYดังนั้นแถวที่เกี่ยวข้องทั้งหมดรวมถึงvalueจะแสดง):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.*
FROM x
  CROSS APPLY 
  ( SELECT TOP (1) *
    FROM dbo.Taco t
    WHERE t.Taco_ID = x.Taco_ID
      AND t.Taco_date > x.Taco_date
    ORDER BY t.Taco_date
  ) t
WHERE x.Rn = 2 ;

ทดสอบที่: SQL-Fiddle-2


คำแนะนำสำหรับปัญหาทั่วไปไม่ทำงานกับ ID ที่ไม่มีการเปลี่ยนแปลง จะได้รับการแก้ไขด้วยการเพิ่มรายการหุ่นชุดเดิม (สิ่งที่ต้องการdbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date)
Andriy M

@AndriyM ฉันรู้ ฉันสันนิษฐานว่า "การเปลี่ยนแปลง" หมายถึงพวกเขาต้องการผลลัพธ์เมื่อมีค่าอย่างน้อย 2 ค่า OP ไม่ได้ชี้แจงว่า (และเนื่องจากง่ายต่อการเขียน :)
ypercubeᵀᴹ

2

FYI +1 สำหรับการให้โครงสร้างตัวอย่างและข้อมูล สิ่งเดียวที่ฉันจะขอได้คือผลลัพธ์ที่คาดหวังสำหรับข้อมูลนั้น

แก้ไข: อันนี้จะผลักฉันถั่ว ฉันเพิ่งใหม่มีวิธี "ง่าย" ในการทำเช่นนี้ ฉันกำจัดวิธีแก้ไขปัญหาที่ไม่ถูกต้องและวางสิ่งที่ฉันเชื่อว่าถูกต้อง นี่เป็นวิธีการแก้ปัญหาคล้ายกับ @bluefeets แต่ครอบคลุมการทดสอบที่ @AaronBertrand มอบให้

;WITH TacoMin AS (SELECT Taco_ID, Taco_value, MIN(Taco_date) InitialValueDate
                FROM Taco
                GROUP BY Taco_ID, Taco_value)
SELECT Taco_ID, MAX(InitialValueDate)
FROM TacoMin
GROUP BY Taco_ID

2
OP ไม่ขอวันที่มากกว่านี้เขาถามเมื่อมีvalueการเปลี่ยนแปลง
ypercubeᵀᴹ

Ahhh ฉันเห็นความผิดพลาดของฉัน ฉันตอบคำถามออกไปแล้ว แต่มันก็เหมือนกับ @ Aaron ที่ไม่มีประเด็นในการโพสต์เลย
Kenneth Fisher

1

ทำไมไม่เพียงแค่ได้รับความแตกต่างของค่าความล่าช้าและค่านำ? ถ้าความแตกต่างเป็นศูนย์มันไม่เปลี่ยนมันไม่ใช่ศูนย์แล้วมันเปลี่ยน สิ่งนี้สามารถทำได้ในแบบสอบถามง่ายๆ:

-- example gives the times the value changed in the last 24 hrs
SELECT
    LastUpdated, [DiffValue]
FROM (
  SELECT
      LastUpdated,
      a.AboveBurdenProbe1TempC - coalesce(lag(a.AboveBurdenProbe1TempC) over (order by ProcessHistoryId), 0) as [DiffValue]
  FROM BFProcessHistory a
  WHERE LastUpdated > getdate() - 1
) b
WHERE [DiffValue] <> 0
ORDER BY LastUpdated ASC

lag...ฟังก์ชั่นการวิเคราะห์เป็นเพียง "เมื่อเร็ว ๆ นี้" แนะนำใน SQL Server 2012 คำถามเดิมจะขอเพื่อแก้ปัญหาใน SQL Server 2008 R2 โซลูชันของคุณจะไม่ทำงานสำหรับ SQL Server 2008 R2
John aka hot2use

-1

สิ่งนี้อาจเป็นเรื่องง่ายดังต่อไปนี้หรือไม่

       SELECT taco_id, MAX(
             CASE 
                 WHEN taco_value <> MAX(taco_value) 
                 THEN taco_date 
                 ELSE null 
             END) AS last_change_date

เนื่องจาก taco_value เพิ่มขึ้นเสมอหรือไม่

ป.ล. ฉันค่อนข้างจะเป็นผู้เริ่มต้น SQL ด้วยตัวเอง แต่เรียนรู้อย่างช้า ๆ


1
บน SQL Server สิ่งนี้จะทำให้เกิดข้อผิดพลาด Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith

2
การเพิ่มจุดให้กับความคิดเห็นของ Martin: คุณอยู่ในด้านที่ปลอดภัยถ้าคุณโพสต์รหัสทดสอบเท่านั้น วิธีง่าย ๆ สามารถไปที่sqlfiddle.comหากคุณอยู่ห่างจากสนามเด็กเล่นตามปกติ
dezso
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.