ฉันจะได้รับมูลค่าปัจจุบันและมูลค่าที่มากกว่าในการเลือกได้อย่างไร?


18

ฉันมีตาราง InnoDB 'idtimes' (MySQL 5.0.22-log) พร้อมคอลัมน์

`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]

ด้วยคีย์เฉพาะแบบผสม

UNIQUE KEY `id_time` (`id`,`time`)

ดังนั้นจึงอาจมีการประทับเวลาหลายรายการต่อ ID และหลาย ID ต่อการประทับเวลา

ฉันพยายามตั้งค่าแบบสอบถามที่ฉันได้รับรายการทั้งหมดรวมถึงเวลาที่มากขึ้นสำหรับแต่ละรายการถ้ามีอยู่ดังนั้นควรส่งคืนเช่น:

+-----+------------+------------+
| id  | time       | nexttime   |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 |       NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 |       NULL |
+-----+------------+------------+

ตอนนี้ฉันถึงตอนนี้:

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
    WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

แต่แน่นอนว่าสิ่งนี้จะส่งคืนแถวทั้งหมดด้วย r.time> l.time ไม่ใช่แค่แถวแรก ...

ฉันเดาว่าฉันต้องการตัวเลือกย่อยเช่น

SELECT outer.id, outer.time, 
    (SELECT time FROM idtimes WHERE id = outer.id AND time > outer.time 
        ORDER BY time ASC LIMIT 1)
    FROM idtimes AS outer ORDER BY outer.id ASC, outer.time ASC;

แต่ฉันไม่รู้วิธีการอ้างอิงถึงเวลาปัจจุบัน (ฉันรู้ว่าข้างต้นไม่ใช่ SQL ที่ถูกต้อง)

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

คำตอบ:


20

การเข้าร่วมเป็นสิ่งหนึ่งที่คุณอาจต้องการ

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id

ฉันคิดว่าการเข้าร่วมด้านนอกโดยเจตนาและคุณต้องการได้รับโมฆะ เพิ่มเติมว่าภายหลัง

WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

คุณต้องการเพียง แถวที่มีเวลาต่ำสุด (MIN) ที่สูงกว่า l.time นั่นคือสถานที่ที่คุณต้องการสอบถามข้อมูลย่อย

WHERE r.time = (SELECT MIN(time) FROM idtimes r2 where r2.id = l.id AND r2.time > l.time)

ตอนนี้เป็นโมฆะ หาก "ไม่มีเวลาถัดไปที่สูงกว่า" ดังนั้น SELECT MIN () จะประเมินเป็นโมฆะ (หรือแย่กว่านั้น) และตัวมันเองไม่เคยเปรียบเทียบเท่ากับอะไรเลยดังนั้นประโยค WHERE ของคุณจะไม่พอใจและ "เวลาสูงสุด" สำหรับแต่ละ ID ไม่สามารถปรากฏในชุดผลลัพธ์

คุณแก้ไขได้โดยกำจัด JOIN ของคุณและย้ายแบบสอบถามย่อยสเกลาร์ไปไว้ในรายการ SELECT:

SELECT id, time, 
    (SELECT MIN(time) FROM idtimes sub 
        WHERE sub.id = main.id AND sub.time > main.time) as nxttime
  FROM idtimes AS main 

4

ฉันมักจะหลีกเลี่ยงการใช้แบบสอบถามย่อยทั้งในSELECTบล็อกหรือในFROMบล็อกเพราะจะทำให้รหัส "สกปรก" และบางครั้งมีประสิทธิภาพน้อยกว่า

ฉันคิดว่าวิธีที่สง่างามกว่านี้คือ:

1. ค้นหาเวลาที่มากกว่าเวลาของแถว

คุณสามารถทำได้ด้วยตารางJOINระหว่างidtimesด้วยตัวเอง จำกัด การเข้าร่วมกับidเดียวกันและเวลาที่มากกว่าเวลาของแถวปัจจุบัน

คุณควรใช้LEFT JOINเพื่อหลีกเลี่ยงการยกเว้นแถวที่ไม่มีเวลามากกว่าแถวใดแถวหนึ่งในปัจจุบัน

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS greater_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time

ปัญหาที่เกิดขึ้นในขณะที่คุณกล่าวถึงคือการที่คุณมีหลายแถวที่ครั้งถัดไป NEXT_TIMEมีค่ามากกว่าเวลา

+-----+------------+--------------+
| id  | time       | greater_time |
+-----+------------+--------------+
| 155 | 1300000000 | 1311111111   |
| 155 | 1300000000 | 1322222222   |
| 155 | 1311111111 | 1322222222   |
| 155 | 1322222222 |       NULL   |
| 156 | 1312345678 | 1318765432   |
| 156 | 1318765432 |       NULL   |
+-----+------------+--------------+

2. ค้นหาแถวที่Greater_timeไม่เพียง แต่ใหญ่กว่า แต่ถัดไป _time

วิธีที่ดีที่สุดในการกรองแถวทั้งหมดไร้ประโยชน์เหล่านี้คือการหาถ้ามีเวลาระหว่างเวลา (มากกว่า) และgreater_time (น้อยกว่า) สำหรับเรื่องนี้ID

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS next_time,
    i3.time AS intrudor_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time
    LEFT JOIN idtimes AS i3 ON i2.id = i3.id AND i3.time > i1.time AND i3.time < i2.time

โอปเรายังคงมีnext_time เท็จ !

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1300000000 | 1322222222   |    1311111111 |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

เพียงกรองแถวที่เกิดเหตุการณ์นี้เพิ่มWHEREข้อ จำกัด ด้านล่าง

WHERE
    i3.time IS NULL

ใช่เรามีสิ่งที่เราต้องการ!

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

ฉันหวังว่าคุณจะยังต้องการคำตอบหลังจาก 4 ปี!


นั่นฉลาด ฉันไม่แน่ใจว่ามันง่ายกว่าที่จะเข้าใจ ฉันคิดว่าถ้าเราแทนที่is nullและการเข้าร่วม i3 ด้วยwhere not exists (select 1 from itimes i3 where [same clause])รหัสนั้นจะสะท้อนสิ่งที่เราต้องการแสดงออก
Andrew Spencer

ขอบคุณที่คุณช่วยฉัน (วันถัดไป) วัน!
จา

2

ก่อนนำเสนอทางออกฉันควรทราบว่ามันไม่สวย มันจะง่ายกว่านี้ถ้าคุณมีAUTO_INCREMENTคอลัมน์อยู่บนโต๊ะ (คุณ?)

SELECT 
  l.id, l.time, 
  SUBSTRING_INDEX(GROUP_CONCAT(r.time ORDER BY r.time), ',', 1)
FROM 
  idtimes AS l 
  LEFT JOIN idtimes AS r ON (l.id = r.id)
WHERE 
  l.time < r.time
GROUP BY
  l.id, l.time

คำอธิบาย:

  • เช่นเดียวกับคุณ: เข้าร่วมสองตารางหนึ่งอันที่ถูกต้องเท่านั้นจะได้เวลาที่สูงกว่า
  • จัดกลุ่มตามทั้งสองคอลัมน์จากตารางด้านซ้าย: สิ่งนี้ช่วยให้มั่นใจว่าเราจะได้รับ(id, time)ชุดค่าผสมทั้งหมด(ซึ่งรู้จักกันว่าไม่ซ้ำกัน)
  • สำหรับแต่ละ(l.id, l.time)ได้รับเป็นครั้งแรก r.timel.timeซึ่งมากกว่า เรื่องนี้เกิดขึ้นกับการสั่งซื้อครั้งแรกr.times ผ่านGROUP_CONCAT(r.time ORDER BY r.time), โดยการหั่นแรก token SUBSTRING_INDEXผ่าน

ขอให้โชคดีและอย่าคาดหวังว่าจะมีประสิทธิภาพที่ดีหากโต๊ะนี้มีขนาดใหญ่


2

คุณยังสามารถได้รับสิ่งที่คุณต้องการจากmin()และGROUP BYไม่มีตัวเลือกภายใน:

SELECT l.id, l.time, min(r.time) 
FROM idtimes l 
LEFT JOIN idtimes r on (r.id = l.id and r.time > l.time)
GROUP BY l.id, l.time;

ฉันเกือบจะวางเดิมพันเงินจำนวนมากที่เครื่องมือเพิ่มประสิทธิภาพเปลี่ยนสิ่งนี้ให้เป็นสิ่งเดียวกับคำตอบของ Erwin Smout และมันก็เป็นที่ถกเถียงกันอยู่ว่ามันจะชัดเจนขึ้น แต่มีความสมบูรณ์ ...


1
สำหรับสิ่งที่คุ้มค่า SSMS & SQLServer 2016 ชอบคำถามของคุณมากกว่า Erwin's (2s runtime กับ 24s runtime บนชุดผลลัพธ์ ~ 24k)
Nathan Lafferty

แอนดรูดูเหมือนว่าคุณจะสูญเสียการเดิมพัน :-)
เออร์วิน Smout

ที่น่าสนใจเพราะมันควรเป็นกรณีทั่วไปที่แบบสอบถามย่อยที่รวมกลับไปยังตารางแบบสอบถามภายนอกโดยหนึ่งในคอลัมน์ PK จะเหมือนกับกลุ่มโดย ฉันสงสัยว่าฐานข้อมูลอื่นใดจะปรับให้ดีขึ้นหรือไม่ (ฉันรู้น้อยมากเกี่ยวกับการเพิ่มประสิทธิภาพฐานข้อมูล BTW เพียงแค่อยากรู้อยากเห็น.)
แอนดรู Spencer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.