ดึงข้อมูลแถวที่มีค่า Max สำหรับคอลัมน์


574

โต๊ะ:

UserId, Value, Date.

ฉันต้องการได้รับ UserId, ค่าสูงสุด (วันที่) สำหรับแต่ละ UserId นั่นคือค่าสำหรับ UserId แต่ละอันที่มีวันที่ล่าสุด มีวิธีการทำเช่นนี้เพียงแค่ใน SQL? (เฉพาะ Oracle)

อัปเดต:ขอโทษสำหรับความกำกวมใด ๆ : ฉันต้องได้รับ UserIds ทั้งหมด แต่สำหรับแต่ละ UserId เฉพาะแถวนั้นที่ผู้ใช้นั้นมีวันล่าสุด


21
จะเกิดอะไรขึ้นถ้ามีหลายแถวที่มีค่าวันที่สูงสุดสำหรับหมายเลขผู้ใช้เฉพาะ
David Aldridge

ฟิลด์สำคัญของตารางคืออะไร
vamosrafa

โซลูชันบางรายการที่เปรียบเทียบด้านล่าง: sqlfiddle.com/#!4/6d4e81/1
_By_Already

1
@DavidAldridge คอลัมน์นั้นมีแนวโน้มที่ไม่ซ้ำกัน
Pacerier

คำตอบ:


397

สิ่งนี้จะดึงแถวทั้งหมดที่ค่าคอลัมน์ my_date เท่ากับค่าสูงสุดของ my_date สำหรับหมายเลขผู้ใช้นั้น สิ่งนี้อาจดึงข้อมูลหลายแถวสำหรับหมายเลขผู้ใช้ที่วันที่สูงสุดอยู่ในหลายแถว

select userid,
       my_date,
       ...
from
(
select userid,
       my_date,
       ...
       max(my_date) over (partition by userid) max_my_date
from   users
)
where my_date = max_my_date

"ฟังก์ชั่นการวิเคราะห์หิน"

แก้ไข: เกี่ยวกับความคิดเห็นแรก ...

"การใช้คิวรีวิเคราะห์และการรวมตัวเองเอาชนะวัตถุประสงค์ของคิวรีวิเคราะห์"

ไม่มีการเข้าร่วมด้วยตนเองในรหัสนี้ แทนที่จะมีคำกริยาที่วางอยู่บนผลลัพธ์ของมุมมองอินไลน์ที่มีฟังก์ชั่นการวิเคราะห์ - เป็นเรื่องที่แตกต่างกันมากและการปฏิบัติตามมาตรฐานอย่างสมบูรณ์

"หน้าต่างเริ่มต้นใน Oracle มาจากแถวแรกในพาร์ติชันจนถึงหน้าต่างปัจจุบัน"

อนุประโยคหน้าต่างจะใช้งานได้เฉพาะเมื่อมีการสั่งซื้อโดยข้อ โดยไม่มีคำสั่งตามคำสั่งไม่มีข้อประโยคที่ถูกนำไปใช้โดยค่าเริ่มต้นและไม่มีใครสามารถระบุได้อย่างชัดเจน

รหัสใช้งานได้


38
เมื่อนำไปใช้กับตารางที่มี 8.8 ล้านแถวแบบสอบถามนี้ใช้เวลาครึ่งหนึ่งของการสืบค้นในคำตอบอื่น ๆ ที่ได้รับการโหวตอย่างสูง
Derek Mahar

4
ใครสนใจโพสต์ลิงก์ไปยัง MySQL เทียบเท่านี้ถ้ามีหรือไม่
redolent

2
การส่งคืนนี้ไม่ซ้ำกันใช่หรือไม่ เช่น. หากสองแถวมี user_id เดียวกันและวันที่เดียวกัน (ซึ่งเกิดขึ้นเป็นสูงสุด)
jastr

2
@jastr ฉันคิดว่าเป็นที่ยอมรับในคำถาม
David Aldridge

3
แทนที่จะใช้MAX(...) OVER (...)คุณสามารถใช้ROW_NUMBER() OVER (...)(สำหรับ top-n-per-group) หรือRANK() OVER (...)(สำหรับ great-n-per-group)
MT0

441

ฉันเห็นคนจำนวนมากใช้คิวรีย่อยหรือคุณสมบัติเฉพาะของผู้จำหน่ายเพื่อทำสิ่งนี้ แต่ฉันมักจะทำคิวรีประเภทนี้โดยไม่มีคิวรีย่อยในวิธีต่อไปนี้ มันใช้ธรรมดา SQL มาตรฐานดังนั้นมันควรจะทำงานในทุกยี่ห้อของ RDBMS

SELECT t1.*
FROM mytable t1
  LEFT OUTER JOIN mytable t2
    ON (t1.UserId = t2.UserId AND t1."Date" < t2."Date")
WHERE t2.UserId IS NULL;

กล่าวอีกนัยหนึ่ง: ดึงแถวจากt1ที่ไม่มีแถวอื่นที่มีวันเดียวกันUserIdและวันที่มากกว่า

(ฉันใส่ตัวระบุ "วันที่" ในตัวคั่นเพราะเป็นคำที่สงวนไว้ของ SQL)

ในกรณีที่t1."Date" = t2."Date"ปรากฏเป็นสองเท่า มักจะมีตารางที่สำคัญเช่นauto_inc(seq) idเพื่อหลีกเลี่ยงการเสแสร้งสามารถใช้ได้ดังนี้:

SELECT t1.*
FROM mytable t1
  LEFT OUTER JOIN mytable t2
    ON t1.UserId = t2.UserId AND ((t1."Date" < t2."Date") 
         OR (t1."Date" = t2."Date" AND t1.id < t2.id))
WHERE t2.UserId IS NULL;

ความคิดเห็นอีกครั้งจาก @Farhan:

นี่คือคำอธิบายโดยละเอียดเพิ่มเติม:

การเข้าร่วม outer ความพยายามในการที่จะเข้าร่วมกับt1 t2ตามค่าเริ่มต้นผลลัพธ์ทั้งหมดt1จะถูกส่งคืนและหากมีการจับคู่ที่ตรงกันผลลัพธ์t2ก็จะถูกส่งคืนเช่นกัน หากไม่มีการจับคู่ในt2แถวที่ระบุt1แสดงว่าแบบสอบถามยังคงส่งคืนแถวt1และใช้NULLเป็นตัวยึดตำแหน่งสำหรับt2คอลัมน์ทั้งหมดของ นั่นเป็นเพียงวิธีที่การรวมภายนอกทำงานโดยทั่วไป

เคล็ดลับในแบบสอบถามนี้คือการออกแบบเข้าร่วมสภาพจับคู่ดังกล่าวว่าt2จะต้องตรงกับที่เหมือนกัน useridและมากขึ้น dateคิดถูกถ้าแถวมีอยู่ในt2ที่มีมากขึ้นdateแล้วแถวในt1มันเทียบกับการไม่สามารถเป็นคนที่ยิ่งใหญ่ที่สุดสำหรับการที่date useridแต่ถ้าไม่มีการแข่งขัน - คือถ้าไม่มีแถวที่มีอยู่ในt2ที่มีมากขึ้นdateกว่าแถวในt1- เรารู้ว่าแถวในt1เป็นแถวที่มีที่ยิ่งใหญ่ที่สุดสำหรับการที่ได้รับdateuserid

ในกรณีเหล่านั้น (เมื่อไม่มีการจับคู่) คอลัมน์ของt2จะเป็นNULL- แม้แต่คอลัมน์ที่ระบุในเงื่อนไขการเข้าร่วม เพื่อที่ว่าทำไมเราใช้WHERE t2.UserId IS NULLเพราะเรากำลังค้นหาสำหรับกรณีที่ไม่มีแถวก็พบว่ามีมากขึ้นสำหรับการที่ได้รับdateuserid


7
ว้าวบิล นี่เป็นทางออกที่สร้างสรรค์ที่สุดสำหรับปัญหานี้ที่ฉันเคยเห็น มันสวยมากเกินไปกับชุดข้อมูลที่มีขนาดใหญ่พอสมควร แน่นอนว่านี่จะเป็นการแก้ปัญหาอื่น ๆ ที่ฉันเคยเห็นหรือความพยายามของฉันในการแก้ไขปัญหานี้
Justin Noel

36
เมื่อนำไปใช้กับตารางที่มี 8.8 ล้านแถวแบบสอบถามนี้ใช้เวลานานเกือบสองเท่าในคำตอบที่ยอมรับ
Derek Mahar

16
@Derek: การปรับให้เหมาะสมขึ้นอยู่กับยี่ห้อและรุ่นของ RDBMS รวมถึงการมีดัชนีที่เหมาะสมประเภทข้อมูล ฯลฯ
Bill Karwin

7
บน MySQL แบบสอบถามชนิดนี้ดูเหมือนจะทำให้เกิดการวนซ้ำกับผลของการรวมคาร์ทีเซียนระหว่างตารางทำให้เกิดเวลา O (n ^ 2) การใช้วิธีการสืบค้นย่อยจะลดเวลาการสืบค้นจาก 2.0s เป็น 0.003s แทน YMMV
เจสซี่

1
มีวิธีในการปรับเปลี่ยนนี้เพื่อจับคู่แถวที่วันที่เป็นวันที่ยิ่งใหญ่ที่สุดน้อยกว่าหรือเท่ากับวันที่ผู้ใช้กำหนดหรือไม่ ตัวอย่างเช่นหากผู้ใช้ให้วันที่ "23-OCT-2011" และตารางมีแถวสำหรับ "24-OCT-2011", "22-OCT-2011", "20-OCT-2011" ฉันต้องการ รับ "22-OCT-2011" รับเกาหัวของฉันและอ่านข้อมูลโค้ดนี้สำหรับขณะนี้ ...
คอรีเคนดัลล์

164
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid

3
ในการทดสอบของฉันโดยใช้ตารางที่มีจำนวนแถวมากวิธีนี้ใช้เวลาประมาณสองเท่าในคำตอบที่ยอมรับ
Derek Mahar

7
แสดงการทดสอบของคุณโปรด
Rob van Wijk

ฉันยืนยันว่ามันเร็วกว่าโซลูชันอื่น ๆ
Tamersalama

5
ปัญหาคือมันไม่ได้ส่งกลับระเบียนเต็ม
Used_By_Already

@ user2067753 ไม่มันไม่ส่งคืนระเบียนเต็ม คุณสามารถใช้ MAX () .. KEEP .. นิพจน์เดียวกันในหลายคอลัมน์เพื่อให้คุณสามารถเลือกคอลัมน์ทั้งหมดที่คุณต้องการ แต่ไม่สะดวกถ้าคุณต้องการคอลัมน์จำนวนมากและต้องการใช้ SELECT *
เดฟคอสตา

51

ฉันไม่ทราบชื่อคอลัมน์ที่แน่นอนของคุณ แต่มันจะเป็นดังนี้:

    เลือกหมายเลขผู้ใช้ค่า
      จากผู้ใช้ u1
     โดยวันที่ = (เลือกสูงสุด (วันที่)
                     จากผู้ใช้ u2
                    โดยที่ u1.userid = u2.userid)

3
อาจจะไม่ได้ผลมากนักสตีฟ
David Aldridge

7
คุณอาจประเมินเครื่องมือเพิ่มประสิทธิภาพการสืบค้นของ Oracle ต่ำเกินไป
Rafał Dowgird

3
ไม่ใช่เลย. การดำเนินการนี้จะเป็นการสแกนแบบเต็มโดยมีการวนซ้ำแบบซ้อนเพื่อรับวันที่ คุณกำลังพูดถึงตรรกะ io ตามลำดับเป็น 4 เท่าของจำนวนแถวในตารางและน่ากลัวสำหรับข้อมูลจำนวนเล็กน้อย
David Aldridge

4
FYI "ไม่มีประสิทธิภาพ แต่ทำงาน" เหมือนกับ "ใช้งานได้ แต่ไม่มีประสิทธิภาพ" เมื่อไหร่ที่เราจะยอมแพ้กับเป้าหมายการออกแบบที่มีประสิทธิภาพ?
David Aldridge

6
+1 เพราะเมื่อ DataTables ของคุณไม่ใช่แถวยาวหลายล้านแถวต่อวันนี่เป็นวิธีที่เข้าใจได้ง่ายที่สุด เมื่อคุณมีนักพัฒนาซอฟต์แวร์หลายระดับทุกระดับที่ปรับเปลี่ยนรหัสความสามารถในการทำความเข้าใจนั้นสำคัญกว่าและเศษเสี้ยวของประสิทธิภาพในการทำงานที่ไม่สามารถสังเกตเห็นได้เพียงเสี้ยววินาที
n00b

35

ไม่ได้ทำงานฉันไม่มี Oracle มือ แต่ฉันจำได้ว่า Oracle อนุญาตให้มีหลายคอลัมน์ที่จะจับคู่ในมาตราซึ่งอย่างน้อยควรหลีกเลี่ยงตัวเลือกที่ใช้แบบสอบถามย่อยที่มีความสัมพันธ์ซึ่งไม่ค่อยดี ความคิด

บางสิ่งเช่นนี้บางที (จำไม่ได้ว่ารายการในคอลัมน์ควรเป็นวงเล็บหรือไม่):

SELECT * 
FROM MyTable
WHERE (User, Date) IN
  ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)

แก้ไข: เพิ่งลองใช้จริง:

SQL> create table MyTable (usr char(1), dt date);
SQL> insert into mytable values ('A','01-JAN-2009');
SQL> insert into mytable values ('B','01-JAN-2009');
SQL> insert into mytable values ('A', '31-DEC-2008');
SQL> insert into mytable values ('B', '31-DEC-2008');
SQL> select usr, dt from mytable
  2  where (usr, dt) in 
  3  ( select usr, max(dt) from mytable group by usr)
  4  /

U DT
- ---------
A 01-JAN-09
B 01-JAN-09

ดังนั้นจึงใช้งานได้แม้ว่าสิ่งใหม่ ๆ ที่กล่าวถึงในที่อื่นอาจมีประสิทธิภาพมากกว่า


4
สิ่งนี้ใช้ได้ดีกับ PostgreSQL เช่นกัน และฉันชอบความเรียบง่ายและทั่วไปของมัน - แบบสอบถามย่อยบอกว่า "นี่คือเกณฑ์ของฉัน" แบบสอบถามด้านนอกบอกว่า "และนี่คือรายละเอียดที่ฉันต้องการดู" +1
j_random_hacker

13

ฉันรู้ว่าคุณขอ Oracle แต่ใน SQL 2005 เราใช้สิ่งนี้:


-- Single Value
;WITH ByDate
AS (
SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE RowNum = 1

-- Multiple values where dates match
;WITH ByDate
AS (
SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE Rnk = 1

7

ฉันไม่มี Oracle ที่จะทำการทดสอบ แต่ทางออกที่มีประสิทธิภาพที่สุดคือการใช้เคียวรีการวิเคราะห์ ควรมีลักษณะดังนี้:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

ฉันสงสัยว่าคุณสามารถกำจัดข้อความค้นหาด้านนอกและทำให้ชัดเจนภายใน แต่ไม่แน่ใจ ในระหว่างนี้ฉันรู้ว่าสิ่งนี้ใช้ได้ผล

หากคุณต้องการที่จะเรียนรู้เกี่ยวกับคำสั่งการวิเคราะห์ผมขอแนะนำให้อ่านhttp://www.orafaq.com/node/55และhttp://www.akadia.com/services/ora_analytic_functions.html นี่คือบทสรุปสั้น ๆ

ภายใต้แบบสอบถามการวิเคราะห์การจัดเรียงชุดข้อมูลทั้งหมดจากนั้นประมวลผลตามลำดับ ในขณะที่คุณประมวลผลคุณจะแบ่งพาร์ติชันชุดข้อมูลตามเกณฑ์ที่แน่นอนจากนั้นสำหรับแต่ละแถวจะดูที่หน้าต่างบางหน้าต่าง (ค่าเริ่มต้นเป็นค่าแรกในพาร์ติชันไปยังแถวปัจจุบัน - ค่าเริ่มต้นนั้นมีประสิทธิภาพสูงสุดด้วย) และสามารถคำนวณค่าโดยใช้ จำนวนฟังก์ชั่นการวิเคราะห์ (รายการที่คล้ายกับฟังก์ชั่นรวม)

ในกรณีนี้นี่คือสิ่งที่แบบสอบถามภายในทำ ชุดข้อมูลทั้งหมดจะถูกจัดเรียงตาม UserId แล้วตามวันที่ DESC จากนั้นมันจะประมวลผลในครั้งเดียว สำหรับแต่ละแถวคุณจะส่งคืน UserId และวันที่แรกที่เห็นสำหรับ UserId นั้น (เนื่องจากวันที่จะถูกจัดเรียง DESC นั่นคือวันที่สูงสุด) สิ่งนี้จะให้คำตอบกับแถวที่ซ้ำกัน จากนั้นสควอช DISTINCT ด้านนอกจะซ้ำกัน

นี่ไม่ใช่ตัวอย่างการสืบค้นที่น่าประทับใจโดยเฉพาะ สำหรับการชนะที่ยิ่งใหญ่กว่านั้นลองพิจารณาตารางการรับเงินและการคำนวณสำหรับผู้ใช้แต่ละคนและใบเสร็จรับเงินจำนวนรวมของสิ่งที่พวกเขาจ่าย เคียวรีการวิเคราะห์แก้ปัญหาได้อย่างมีประสิทธิภาพ โซลูชันอื่น ๆ มีประสิทธิภาพน้อยกว่า นี่คือเหตุผลว่าทำไมจึงเป็นส่วนหนึ่งของมาตรฐาน SQL 2003 (น่าเสียดาย Postgres ยังไม่มี Grrr ... )


คุณต้องส่งคืนค่าวันที่เพื่อตอบคำถามโดยสมบูรณ์ ถ้านั่นหมายถึงประโยคแรก first_value อีกฉันขอแนะนำให้แก้ปัญหาที่ซับซ้อนกว่าที่ควรจะเป็นและวิธีการวิเคราะห์ตาม max (date) อ่านดีกว่า
David Aldridge

คำแถลงคำถามไม่ได้พูดอะไรเกี่ยวกับการคืนวันที่ คุณสามารถทำได้โดยเพิ่ม FIRST (Date) อื่นหรือเพียงแค่สอบถาม Date และเปลี่ยนการสืบค้นภายนอกเป็น GROUP BY ฉันจะใช้อันแรกและคาดว่าเครื่องมือเพิ่มประสิทธิภาพในการคำนวณทั้งสองในหนึ่งรอบ
user11318

"คำแถลงคำถามบอกว่าไม่มีอะไรเกี่ยวกับการคืนวันที่" ... ใช่คุณพูดถูก ขอโทษ แต่การเพิ่มคำสั่ง FIRST_VALUE เพิ่มเติมจะกลายเป็นความยุ่งเหยิงอย่างรวดเร็ว เป็นการเรียงลำดับหน้าต่างเดียว แต่ถ้าคุณมี 20 คอลัมน์ที่จะส่งคืนแถวนั้นคุณได้เขียนโค้ดจำนวนมากเพื่อลุย
David Aldridge

นอกจากนี้ยังเกิดขึ้นกับฉันว่าวิธีนี้ไม่ได้กำหนดไว้สำหรับข้อมูลที่หมายเลขผู้ใช้เดียวมีหลายแถวที่มีวันที่สูงสุดและค่าที่แตกต่างกัน มากกว่าความผิดในคำถามกว่าคำตอบว่า
David Aldridge

1
ฉันเห็นด้วยว่ามันเจ็บปวดอย่างมาก อย่างไรก็ตามไม่ใช่กรณีทั่วไปของ SQL ใช่ไหม และคุณพูดถูกว่าวิธีแก้ปัญหานั้นไม่ได้กำหนดขึ้น มีหลายวิธีในการจัดการกับความสัมพันธ์และบางครั้งก็เป็นสิ่งที่คุณต้องการ
user11318

6

ประโยคสั้น ๆ จะไม่ง่ายและดีที่สุดหรือไม่

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

สำหรับบริบทบน Teradata ที่นี่การทดสอบขนาดที่เหมาะสมของการทำงานนี้ใน 17s กับรุ่น QUALIFY นี้และใน 23 วินาทีด้วย 'inline view' / Aldridge solution # 1


1
นี่คือคำตอบที่ดีที่สุดในความคิดของฉัน อย่างไรก็ตามควรใช้ความระมัดระวังกับrank()ฟังก์ชั่นในสถานการณ์ที่มีความสัมพันธ์ rank=1คุณอาจจะจบลงด้วยการที่มีมากกว่าหนึ่ง ดีกว่าที่จะใช้row_number()ถ้าคุณต้องการกลับมาเพียงหนึ่งบันทึก
cartbeforehorse

1
นอกจากนี้ให้ระวังด้วยว่าQUALIFYประโยคนั้นมีความเฉพาะเจาะจงกับ Teradata ใน Oracle (อย่างน้อย) คุณต้องซ้อนคิวรีและตัวกรองโดยใช้WHEREประโยคบนคำสั่งเลือกการตัดคำ
cartbeforehorse

5

ในOracle 12c+คุณสามารถใช้การค้นหายอดนิยม nพร้อมกับฟังก์ชั่นการวิเคราะห์rankเพื่อให้ได้ผลลัพธ์ที่รัดกุมโดยไม่ต้องมีแบบสอบถามย่อย:

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

ด้านบนส่งคืนแถวทั้งหมดด้วยค่าสูงสุด my_date ต่อผู้ใช้

หากคุณต้องการเพียงหนึ่งแถวที่มีวันที่สูงสุดให้แทนที่rankด้วยrow_number:

select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties; 

5

ใช้ROW_NUMBER()เพื่อกำหนดการจัดอันดับที่ไม่ซ้ำกันจากมากไปน้อยDateสำหรับแต่ละรายการUserIdจากนั้นกรองไปยังแถวแรกสำหรับแต่ละรายการUserId(เช่นROW_NUMBER= 1)

SELECT UserId, Value, Date
FROM (SELECT UserId, Value, Date,
        ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
      FROM users) u
WHERE rn = 1;

5

ด้วย PostgreSQL 8.4 หรือใหม่กว่าคุณสามารถใช้สิ่งนี้:

select user_id, user_value_1, user_value_2
  from (select user_id, user_value_1, user_value_2, row_number()
          over (partition by user_id order by user_date desc) 
        from users) as r
  where r.row_number=1

3

สิ่งที่คุณ shuold ทำให้ตัวแปรนี้เพื่อการค้นหาก่อนหน้า:

SELECT UserId, Value FROM Users U1 WHERE 
Date = ( SELECT MAX(Date)    FROM Users where UserId = U1.UserId)

3
Select  
   UserID,  
   Value,  
   Date  
From  
   Table,  
   (  
      Select  
          UserID,  
          Max(Date) as MDate  
      From  
          Table  
      Group by  
          UserID  
    ) as subQuery  
Where  
   Table.UserID = subQuery.UserID and  
   Table.Date = subQuery.mDate  

3

เพียงแค่ต้องเขียนตัวอย่าง "สด" ในที่ทำงาน :)

อันนี้รองรับหลายค่าสำหรับ UserId ในวันเดียวกัน

คอลัมน์: UserId, Value, Date

SELECT
   DISTINCT UserId,
   MAX(Date) OVER (PARTITION BY UserId ORDER BY Date DESC),
   MAX(Values) OVER (PARTITION BY UserId ORDER BY Date DESC)
FROM
(
   SELECT UserId, Date, SUM(Value) As Values
   FROM <<table_name>>
   GROUP BY UserId, Date
)

คุณสามารถใช้ FIRST_VALUE แทน MAX และค้นหาในแผนอธิบาย ฉันไม่มีเวลาเล่นกับมัน

แน่นอนว่าถ้าค้นหาในตารางขนาดใหญ่มันคงจะดีกว่าถ้าคุณใช้คำแนะนำแบบเต็มในการค้นหาของคุณ



2

ฉันคิดว่าบางสิ่งเช่นนี้ (ยกโทษให้ฉันสำหรับข้อผิดพลาดทางไวยากรณ์ใด ๆ ฉันเคยใช้ HQL ณ จุดนี้!)

แก้ไข: ยังอ่านคำถามผิด! แก้ไขคำค้นหา ...

SELECT UserId, Value
FROM Users AS user
WHERE Date = (
    SELECT MAX(Date)
    FROM Users AS maxtest
    WHERE maxtest.UserId = user.UserId
)

ไม่ตรงตามเงื่อนไข "สำหรับผู้ใช้แต่ละคน"
David Aldridge

มันจะล้มเหลวที่ไหน? สำหรับทุก ID ผู้ใช้ในผู้ใช้จะรับประกันว่าอย่างน้อยหนึ่งแถวที่มี UserID นั้นจะถูกส่งคืน หรือฉันกำลังพลาดกรณีพิเศษอยู่ที่ไหนสักแห่ง?
jdmichal

2

(T-SQL) ขั้นแรกให้ผู้ใช้และ maxdate ของพวกเขาทั้งหมด เข้าร่วมกับตารางเพื่อค้นหาค่าที่เกี่ยวข้องสำหรับผู้ใช้บน maxdates

create table users (userid int , value int , date datetime)
insert into users values (1, 1, '20010101')
insert into users values (1, 2, '20020101')
insert into users values (2, 1, '20010101')
insert into users values (2, 3, '20030101')

select T1.userid, T1.value, T1.date 
    from users T1,
    (select max(date) as maxdate, userid from users group by userid) T2    
    where T1.userid= T2.userid and T1.date = T2.maxdate

ผล:

userid      value       date                                    
----------- ----------- -------------------------- 
2           3           2003-01-01 00:00:00.000
1           2           2002-01-01 00:00:00.000

2

คำตอบที่นี่คือ Oracle เท่านั้น นี่คือคำตอบที่ซับซ้อนมากขึ้นเล็กน้อยใน SQL ทั้งหมด:

ใครคือผลการบ้านโดยรวมที่ดีที่สุด (คะแนนรวมทำการบ้านสูงสุด)

SELECT FIRST, LAST, SUM(POINTS) AS TOTAL
FROM STUDENTS S, RESULTS R
WHERE S.SID = R.SID AND R.CAT = 'H'
GROUP BY S.SID, FIRST, LAST
HAVING SUM(POINTS) >= ALL (SELECT SUM (POINTS)
FROM RESULTS
WHERE CAT = 'H'
GROUP BY SID)

และเป็นตัวอย่างที่ยากขึ้นซึ่งต้องการคำอธิบายบางอย่างซึ่งฉันไม่มีเวลา:

ให้หนังสือ (ISBN และชื่อเรื่อง) ที่เป็นที่นิยมมากที่สุดในปี 2008 เช่นที่ยืมมาบ่อยที่สุดในปี 2008

SELECT X.ISBN, X.title, X.loans
FROM (SELECT Book.ISBN, Book.title, count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title) X
HAVING loans >= ALL (SELECT count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title);

หวังว่านี่จะช่วยได้ (ทุกคน) .. :)

ขอแสดงความนับถือ Guus


คำตอบที่ยอมรับไม่ใช่ "Oracle only" - เป็น SQL มาตรฐาน (รองรับโดย DBMS จำนวนมาก)
a_horse_with_no_name

2

สมมติว่าวันที่ไม่ซ้ำกันสำหรับ UserID ที่กำหนดนี่คือ TSQL

SELECT 
    UserTest.UserID, UserTest.Value
FROM UserTest
INNER JOIN
(
    SELECT UserID, MAX(Date) MaxDate
    FROM UserTest
    GROUP BY UserID
) Dates
ON UserTest.UserID = Dates.UserID
AND UserTest.Date = Dates.MaxDate 

2

ฉันค่อนข้างช้าไปงานปาร์ตี้ แต่แฮ็คต่อไปนี้จะมีประสิทธิภาพสูงกว่าทั้งเคียวรีย่อยที่มีความสัมพันธ์และฟังก์ชันการวิเคราะห์ใด ๆ แต่มีข้อ จำกัด เดียว: ค่าต้องแปลงเป็นสตริง ดังนั้นจึงใช้งานได้สำหรับวันที่ตัวเลขและสตริงอื่น ๆ รหัสดูไม่ดี แต่โปรไฟล์การดำเนินการดีมาก

select
    userid,
    to_number(substr(max(to_char(date,'yyyymmdd') || to_char(value)), 9)) as value,
    max(date) as date
from 
    users
group by
    userid

เหตุผลที่รหัสนี้ใช้งานได้ดีก็คือต้องการสแกนตารางเพียงครั้งเดียวเท่านั้น มันไม่จำเป็นต้องมีดัชนีใด ๆ และที่สำคัญที่สุดมันไม่จำเป็นต้องเรียงลำดับตารางซึ่งส่วนใหญ่ฟังก์ชั่นการวิเคราะห์ทำ ดัชนีจะช่วยได้หากคุณต้องการกรองผลลัพธ์สำหรับหมายเลขผู้ใช้เดียว


มันเป็นแผนการดำเนินการที่ดีเมื่อเทียบกับส่วนใหญ่ แต่การใช้กลเม็ดเหล่านั้นกับฟิลด์มากกว่านั้นจะน่าเบื่อและอาจใช้งานไม่ได้ แต่น่าสนใจมาก - ขอบคุณ ดูsqlfiddle.com/#!4/2749b5/23
Used_By_Already

คุณพูดถูกมันอาจกลายเป็นเรื่องน่าเบื่อซึ่งเป็นเหตุผลว่าทำไมจึงควรทำสิ่งนี้เฉพาะเมื่อประสิทธิภาพของแบบสอบถามต้องการ นี่เป็นกรณีที่เกิดขึ้นกับสคริปต์ ETL
aLevelOfIndirection

นี่เป็นสิ่งที่ดีมาก ทำสิ่งที่คล้ายกันโดยใช้ LISTAGG แต่ดูน่าเกลียด postgres มี altenative ที่ดีกว่าโดยใช้ array_agg ดูคำตอบของฉัน :)
บรูโน่ Calza

1
select userid, value, date
  from thetable t1 ,
       ( select t2.userid, max(t2.date) date2 
           from thetable t2 
          group by t2.userid ) t3
 where t3.userid t1.userid and
       t3.date2 = t1.date

IMHO งานนี้ HTH


1

ฉันคิดว่ามันน่าจะใช้ได้เหรอ

Select
T1.UserId,
(Select Top 1 T2.Value From Table T2 Where T2.UserId = T1.UserId Order By Date Desc) As 'Value'
From
Table T1
Group By
T1.UserId
Order By
T1.UserId

1

ก่อนอื่นให้ลองฉันอ่านคำถามผิด ๆ โดยทำตามคำตอบด้านบนนี่คือตัวอย่างที่สมบูรณ์พร้อมผลลัพธ์ที่ถูกต้อง:

CREATE TABLE table_name (id int, the_value varchar(2), the_date datetime);

INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'a','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'b','2/2/2002');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'c','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'d','3/3/2003');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'e','3/3/2003');

-

  select id, the_value
      from table_name u1
      where the_date = (select max(the_date)
                     from table_name u2
                     where u1.id = u2.id)

-

id          the_value
----------- ---------
2           d
2           e
1           b

(3 row(s) affected)

1

สิ่งนี้จะดูแลการซ้ำซ้อน (คืนหนึ่งแถวสำหรับแต่ละ user_id):

SELECT *
FROM (
  SELECT u.*, FIRST_VALUE(u.rowid) OVER(PARTITION BY u.user_id ORDER BY u.date DESC) AS last_rowid
  FROM users u
) u2
WHERE u2.rowid = u2.last_rowid

1

เพิ่งทดสอบสิ่งนี้และดูเหมือนว่าจะทำงานบนตารางการบันทึก

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc

1

นี่ควรจะง่ายเหมือน:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)

1

โซลูชันสำหรับ MySQL ซึ่งไม่มีแนวคิดของพาร์ติชัน KEEP, DENSE_RANK

select userid,
       my_date,
       ...
from
(
select @sno:= case when @pid<>userid then 0
                    else @sno+1
    end as serialnumber, 
    @pid:=userid,
       my_Date,
       ...
from   users order by userid, my_date
) a
where a.serialnumber=0

การอ้างอิง: http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html


สิ่งนี้ไม่ทำงาน " ในฐานข้อมูลอื่นด้วย " ใช้งานได้กับ MySQL และบน SQL Server เท่านั้นเนื่องจากมีแนวคิดของตัวแปรที่คล้ายคลึงกัน มันจะไม่ทำงานบน Oracle, Postgres, DB2, Derby, H2, HSQLDB, Vertica, Greenplum นอกจากนี้คำตอบที่ได้รับการยอมรับก็คือมาตรฐาน ANSI SQL (ซึ่งรู้เพียง MySQL เท่านั้นที่ไม่สนับสนุน)
a_horse_with_no_name

ม้าฉันว่าคุณพูดถูก ฉันไม่มีความรู้เกี่ยวกับฐานข้อมูลอื่นหรือ ANSI โซลูชันของฉันสามารถแก้ไขปัญหาใน MySQL ซึ่งไม่ได้รับการสนับสนุนที่เหมาะสมสำหรับ ANSI SQL เพื่อแก้ไขปัญหาด้วยวิธีมาตรฐาน
เบ็นหลิน

1

หากคุณใช้ Postgres คุณสามารถใช้array_aggเช่น

SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid

ฉันไม่คุ้นเคยกับ Oracle นี่คือสิ่งที่ฉันมาด้วย

SELECT 
  userid,
  MAX(adate),
  SUBSTR(
    (LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)),
    0,
    INSTR((LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)), ',')-1
  ) as value 
FROM YOURTABLE
GROUP BY userid 

ข้อความค้นหาทั้งสองรายการส่งคืนผลลัพธ์เดียวกับคำตอบที่ยอมรับ ดู SQLFiddles:

  1. คำตอบที่ได้รับการยอมรับ
  2. โซลูชันของฉันกับ Postgres
  3. โซลูชันของฉันกับ Oracle

0

ถ้า (ID ผู้ใช้, วันที่) ไม่ซ้ำกันนั่นคือไม่มีวันที่ปรากฏสำหรับผู้ใช้รายเดิมสองครั้ง:

select TheTable.UserID, TheTable.Value
from TheTable inner join (select UserID, max([Date]) MaxDate
                          from TheTable
                          group by UserID) UserMaxDate
     on TheTable.UserID = UserMaxDate.UserID
        TheTable.[Date] = UserMaxDate.MaxDate;

ผมเชื่อว่าคุณต้องการที่จะเข้าร่วมโดยหมายเลขผู้ใช้เป็นอย่างดี
ทอม H

0
select   UserId,max(Date) over (partition by UserId) value from users;

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