Làm thế nào để tôi có được giá trị lớn hơn hiện tại và tiếp theo trong một lựa chọn?


18

Tôi có bảng 'idtimes' của bảng InnoDB (Nhật ký MySQL 5.0.22) với các cột

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

với một khóa duy nhất ghép

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

do đó, có thể có nhiều dấu thời gian cho mỗi id và nhiều id cho mỗi dấu thời gian.

Tôi đang cố gắng thiết lập một truy vấn trong đó tôi nhận được tất cả các mục cộng với thời gian lớn hơn tiếp theo cho mỗi mục, nếu nó tồn tại, vì vậy nó sẽ trả về, ví dụ:

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

Ngay bây giờ tôi cho đến nay:

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;

nhưng tất nhiên điều này trả về tất cả các hàng có r.time> l.time và không chỉ hàng đầu tiên ...

Tôi đoán tôi sẽ cần một mục phụ như

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;

nhưng tôi không biết làm thế nào để tham khảo thời điểm hiện tại (tôi biết ở trên không phải là SQL hợp lệ).

Làm cách nào để thực hiện việc này với một truy vấn duy nhất (và tôi không muốn sử dụng các biến @ phụ thuộc vào bước mặc dù mỗi lần một bảng và ghi nhớ giá trị cuối cùng)?

Câu trả lời:


20

Làm THAM GIA là một điều bạn có thể cần.

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

Tôi cho rằng sự tham gia bên ngoài là có chủ ý và bạn muốn nhận được null. Thêm về điều đó sau.

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

Bạn chỉ muốn r. hàng có thời gian (MIN) thấp nhất cao hơn thời gian. Đó là nơi mà bạn cần truy vấn.

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

Bây giờ đến nulls. Nếu "không có thời gian tiếp theo cao hơn", thì CHỌN MIN () sẽ ước tính thành null (hoặc tệ hơn) và bản thân nó không bao giờ so sánh với bất cứ điều gì, vì vậy mệnh đề WHERE của bạn sẽ không bao giờ được thỏa mãn và "thời gian cao nhất" đối với mỗi ID, không bao giờ có thể xuất hiện trong tập kết quả.

Bạn giải quyết nó bằng cách loại bỏ THAM GIA của bạn và di chuyển truy vấn con vô hướng vào danh sách CHỌN:

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

Tôi luôn tránh sử dụng các truy vấn con trong SELECTkhối hoặc trong FROMkhối, vì nó làm cho mã "bẩn hơn" và đôi khi kém hiệu quả hơn.

Tôi nghĩ rằng một cách thanh lịch hơn để làm điều đó là:

1. Tìm thời gian lớn hơn thời gian của hàng

Bạn có thể làm điều này với một JOINgiữa idtimes bảng với chính nó, kìm hãm sự tham gia với cùng idlần lớn hơn thời gian của hàng hiện tại.

Bạn nên sử dụng LEFT JOINđể tránh loại trừ các hàng trong đó không có lần nào lớn hơn một trong các hàng hiện tại.

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

Vấn đề, như bạn đã đề cập, là bạn có nhiều hàng trong đó next_time lớn hơn thời gian .

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

2. Tìm các hàng trong đó Greater_time không chỉ lớn hơn mà next_time

Cách tốt nhất để lọc tất cả các dòng vô dụng là để tìm hiểu xem có lần giữa thời gian (lớn hơn) và greater_time (ít hơn) cho việc này 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

ops, chúng tôi vẫn có một next_time sai !

+-----+------------+--------------+---------------+
| 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  |
+-----+------------+--------------+---------------+

Chỉ cần lọc các hàng nơi sự kiện này xảy ra, thêm các WHEREràng buộc bên dưới

WHERE
    i3.time IS NULL

Voilà, chúng ta có những gì chúng ta cần!

+-----+------------+--------------+---------------+
| 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  |
+-----+------------+--------------+---------------+

Tôi hy vọng rằng bạn vẫn cần một câu trả lời sau 4 năm!


Thật khéo léo. Tôi không chắc nó dễ hiểu hơn mặc dù. Tôi nghĩ rằng nếu chúng ta thay thế is nullvà nối với i3 where not exists (select 1 from itimes i3 where [same clause]), thì mã sẽ phản ánh chặt chẽ hơn những gì chúng ta muốn thể hiện.
Andrew Spencer

Thx anh bạn đã lưu (ngày) của tôi!
Jakob

2

Trước khi trình bày giải pháp, tôi cần lưu ý nó không đẹp. Sẽ dễ dàng hơn nhiều nếu bạn có một số AUTO_INCREMENTcột trên bàn của mình (phải không?)

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

Giải trình:

  • Cùng tham gia với bạn: tham gia hai bảng, bảng bên phải chỉ nhận được số lần cao hơn
  • NHÓM THEO cả hai cột từ bảng bên trái: điều này đảm bảo chúng tôi nhận được tất cả các (id, time)kết hợp (còn được gọi là duy nhất).
  • Đối với mỗi (l.id, l.time), có được cái đầu tiên r.time lớn hơn l.time. Điều này xảy ra với lần đầu tiên đặt hàng r.timequa GROUP_CONCAT(r.time ORDER BY r.time), bằng cách cắt mã thông báo đầu tiên qua SUBSTRING_INDEX.

Chúc may mắn, và, đừng mong đợi hiệu suất tốt nếu bảng này lớn.


2

Bạn cũng có thể nhận được những gì bạn muốn từ một min()GROUP BYkhông có lựa chọn bên trong:

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;

Tôi gần như sẽ đặt cược một khoản tiền lớn mà trình tối ưu hóa biến điều này thành điều tương tự như câu trả lời của Erwin Smout, và điều gây tranh cãi là liệu nó có rõ ràng hơn không, nhưng đó là sự hoàn chỉnh ...


1
Về giá trị của nó, SSMS & SQLServer 2016 thích truy vấn của bạn hơn rất nhiều so với Erwin (thời gian chạy 2 giây so với thời gian chạy 24 giây trên tập kết quả ~ 24k)
Nathan Lafferty

Andrew có vẻ như bạn đã thua cược :-)
Erwin Smout

Thật thú vị, bởi vì đây là trường hợp chung mà một truy vấn con tham gia trở lại bảng truy vấn bên ngoài bởi một trong các cột PK giống như một nhóm theo. Tôi tự hỏi nếu bất kỳ cơ sở dữ liệu khác sẽ tối ưu hóa nó tốt hơn. (Tôi biết rất ít về trình tối ưu hóa cơ sở dữ liệu BTW; chỉ tò mò thôi.)
Andrew Spencer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.