Chọn hàng có ngày gần đây nhất cho mỗi người dùng


125

Tôi có một bảng ("lms_attendance") về thời gian đăng ký và ra của người dùng giống như thế này:

id  user    time    io (enum)
1   9   1370931202  out
2   9   1370931664  out
3   6   1370932128  out
4   12  1370932128  out
5   12  1370933037  in

Tôi đang cố gắng tạo chế độ xem bảng này sẽ chỉ xuất ra bản ghi gần đây nhất cho mỗi id người dùng, trong khi đưa cho tôi giá trị "vào" hoặc "ra", vì vậy đại loại như:

id  user    time    io
2   9   1370931664  out
3   6   1370932128  out
5   12  1370933037  in

Cho đến nay tôi khá gần, nhưng tôi nhận ra rằng các lượt xem sẽ không chấp nhận các truy vấn con, điều này làm cho nó khó hơn rất nhiều. Truy vấn gần nhất tôi nhận được là:

select 
    `lms_attendance`.`id` AS `id`,
    `lms_attendance`.`user` AS `user`,
    max(`lms_attendance`.`time`) AS `time`,
    `lms_attendance`.`io` AS `io` 
from `lms_attendance` 
group by 
    `lms_attendance`.`user`, 
    `lms_attendance`.`io`

Nhưng những gì tôi nhận được là:

id  user    time    io
3   6   1370932128  out
1   9   1370931664  out
5   12  1370933037  in
4   12  1370932128  out

Mà gần, nhưng không hoàn hảo. Tôi biết rằng nhóm cuối cùng không nên ở đó, nhưng không có nó, nó sẽ trả về lần gần đây nhất, nhưng không phải với giá trị IO tương đối của nó.

Có ý kiến ​​gì không? Cảm ơn!



Quay trở lại hướng dẫn. Bạn sẽ thấy rằng nó cung cấp giải pháp cho vấn đề này cả có và không có các truy vấn con (tương quan và không liên kết).
Dâu

@Barmar, về mặt kỹ thuật, như tôi đã chỉ ra trong câu trả lời của mình, đây là một bản sao của tất cả 700 câu hỏi với thẻ n-mỗi nhóm lớn nhất .
TMS

@Prodikl, 'io (enum)' là gì?
Monica Heddneck

Tôi đã có một cột được gọi là "IO" là viết tắt của "vào hoặc ra", đó là một loại enum với các giá trị có thể "vào" hoặc "ra". Điều này đã được sử dụng để theo dõi khi mọi người đăng ký và ra khỏi lớp.
Keith

Câu trả lời:


199

Truy vấn:

SQLFIDDLEExample

SELECT t1.*
FROM lms_attendance t1
WHERE t1.time = (SELECT MAX(t2.time)
                 FROM lms_attendance t2
                 WHERE t2.user = t1.user)

Kết quả:

| ID | USER |       TIME |  IO |
--------------------------------
|  2 |    9 | 1370931664 | out |
|  3 |    6 | 1370932128 | out |
|  5 |   12 | 1370933037 |  in |

Giải pháp sẽ hoạt động mọi lúc:

SQLFIDDLEExample

SELECT t1.*
FROM lms_attendance t1
WHERE t1.id = (SELECT t2.id
                 FROM lms_attendance t2
                 WHERE t2.user = t1.user            
                 ORDER BY t2.id DESC
                 LIMIT 1)

2
ồ không chỉ làm việc này, tôi còn được phép tạo chế độ xem với truy vấn này mặc dù nó có chứa các truy vấn con. trước đây, khi tôi cố gắng tạo một khung nhìn chứa các truy vấn con, nó không cho phép tôi. Có những quy định tại sao điều này được cho phép nhưng một quy tắc khác thì không?
Keith

rất kì lạ. cảm ơn rất nhiều có thể đó là vì truy vấn con của tôi là một bảng giả mà tôi đã chọn TỪ, trong ví dụ này, nó được sử dụng trong mệnh đề WHERE.
Keith

4
Không cần truy vấn con! Hơn nữa, giải pháp này không hoạt động nếu có hai bản ghi với cùng một thời gian . Không cần phải thử phát minh lại bánh xe mỗi lần, vì đây là vấn đề phổ biến - thay vào đó, hãy tìm giải pháp đã được thử nghiệm và tối ưu hóa - @Prodikl xem câu trả lời của tôi.
TMS

ah, cảm ơn vì sự sáng suốt! Tôi sẽ thử mã mới khi tôi ở văn phòng vào ngày mai.
Keith

3
@TMS Giải pháp này hoạt động nếu các bản ghi có cùng thời gian chính xác, vì truy vấn đang định vị bản ghi với id lớn nhất. Điều này ngụ ý rằng thời gian trong bảng là thời gian chèn, có thể không phải là một giả định tốt. Thay vào đó, giải pháp của bạn so sánh dấu thời gian và khi hai dấu thời gian giống hệt nhau, bạn cũng trả về hàng có id lớn nhất. Do đó, giải pháp của bạn cũng giả định rằng dấu thời gian trong bảng này có liên quan đến thứ tự chèn, đây là lỗ hổng lớn nhất với cả hai truy vấn của bạn.
WebWanderer

73

Không cần phải cố gắng phát minh lại bánh xe, vì đây là vấn đề phổ biến nhất của mỗi nhóm . Giải pháp rất tốt đẹp được trình bày .

Tôi thích giải pháp đơn giản nhất ( xem SQLFiddle, Justin đã cập nhật ) mà không cần truy vấn con (do đó dễ sử dụng trong chế độ xem):

SELECT t1.*
FROM lms_attendance AS t1
LEFT OUTER JOIN lms_attendance AS t2
  ON t1.user = t2.user 
        AND (t1.time < t2.time 
         OR (t1.time = t2.time AND t1.Id < t2.Id))
WHERE t2.user IS NULL

Điều này cũng hoạt động trong trường hợp có hai bản ghi khác nhau có cùng giá trị lớn nhất trong cùng một nhóm - nhờ vào mẹo với (t1.time = t2.time AND t1.Id < t2.Id). Tất cả những gì tôi đang làm ở đây là để đảm bảo rằng trong trường hợp khi hai bản ghi của cùng một người dùng có cùng một thời điểm thì chỉ có một bản được chọn. Không thực sự quan trọng nếu các tiêu chí là Idhoặc một cái gì đó khác - về cơ bản bất kỳ tiêu chí nào được đảm bảo là duy nhất sẽ làm cho công việc ở đây.


1
Sử dụng tối đa t1.time < t2.timevà tối thiểu sẽ t1.time > t2.timetrái ngược với trực giác ban đầu của tôi.
Không có

1
@ J.Money vì có ẩn phủ định ẩn: bạn chọn tất cả các bản ghi từ t1 không có bản ghi tương ứng từ t2 trong đó t1.time < t2.timeđiều kiện áp dụng :-)
TMS

4
WHERE t2.user IS NULLlà một chút lạ. Dòng này đóng vai trò gì?
tumultous_rooster

1
Câu trả lời được chấp nhận, được đăng bởi Justin, có thể tối ưu hơn. Câu trả lời được chấp nhận sử dụng quét chỉ mục ngược trên khóa chính của bảng, theo sau là giới hạn, theo sau là quét chuỗi của bảng. Do đó, câu trả lời được chấp nhận có thể được tối ưu hóa rất nhiều với một chỉ số bổ sung. Truy vấn này cũng có thể được tối ưu hóa bởi một chỉ mục, vì nó thực hiện hai lần quét theo trình tự, nhưng cũng bao gồm hàm băm và "hàm băm chống tham gia" của kết quả quét trình tự và hàm băm của lần quét thứ tự khác. Tôi sẽ quan tâm đến một lời giải thích về cách tiếp cận nào thực sự tối ưu hơn.
WebWanderer

@TMS bạn có thể vui lòng làm rõ OR (t1.time = t2.time AND t1.Id < t2.Id))phần?
Oleg Kuts

6

Dựa trên câu trả lời @TMS, tôi thích nó vì không cần truy vấn con nhưng tôi nghĩ việc sử dụng 'OR'phần này sẽ đủ và đơn giản hơn nhiều để hiểu và đọc.

SELECT t1.*
FROM lms_attendance AS t1
LEFT JOIN lms_attendance AS t2
  ON t1.user = t2.user 
        AND t1.time < t2.time
WHERE t2.user IS NULL

nếu bạn không quan tâm đến các hàng có số lần null, bạn có thể lọc chúng trong WHEREmệnh đề:

SELECT t1.*
FROM lms_attendance AS t1
LEFT JOIN lms_attendance AS t2
  ON t1.user = t2.user 
        AND t1.time < t2.time
WHERE t2.user IS NULL and t1.time IS NOT NULL

Bỏ qua ORphần này là một ý tưởng thực sự tồi tệ nếu hai bản ghi có thể có cùng time.
TMS

Tôi sẽ tránh giải pháp này cho hiệu suất vì lợi ích. Như @OlegKuts đã đề cập, điều này trở nên rất chậm trên các tập dữ liệu từ trung bình đến lớn.
Peter Meadley

4

Đã được giải quyết, nhưng chỉ để ghi lại, một cách tiếp cận khác sẽ là tạo hai chế độ xem ...

CREATE TABLE lms_attendance
(id int, user int, time int, io varchar(3));

CREATE VIEW latest_all AS
SELECT la.user, max(la.time) time
FROM lms_attendance la 
GROUP BY la.user;

CREATE VIEW latest_io AS
SELECT la.* 
FROM lms_attendance la
JOIN latest_all lall 
    ON lall.user = la.user
    AND lall.time = la.time;

INSERT INTO lms_attendance 
VALUES
(1, 9, 1370931202, 'out'),
(2, 9, 1370931664, 'out'),
(3, 6, 1370932128, 'out'),
(4, 12, 1370932128, 'out'),
(5, 12, 1370933037, 'in');

SELECT * FROM latest_io;

Nhấn vào đây để xem nó hoạt động tại SQL Fiddle


1
Cảm ơn đã theo lên! vâng, tôi sẽ tạo ra nhiều lượt xem nếu không có cách nào dễ dàng hơn. cảm ơn một lần nữa
Keith

0
select b.* from 

    (select 
        `lms_attendance`.`user` AS `user`,
        max(`lms_attendance`.`time`) AS `time`
    from `lms_attendance` 
    group by 
        `lms_attendance`.`user`) a

join

    (select * 
    from `lms_attendance` ) b

on a.user = b.user
and a.time = b.time

cảm ơn. tôi biết tôi có thể làm điều đó bằng cách sử dụng truy vấn con, nhưng tôi đã hy vọng biến điều này thành chế độ xem và nó sẽ không cho phép truy vấn con trong chế độ xem AFAIK. tôi sẽ phải biến mỗi truy vấn phụ thành một khung nhìn, v.v.?
Keith

join (select * from lms_attendance ) b= join lms_attendance b
azerafati

0
 select result from (
     select vorsteuerid as result, count(*) as anzahl from kreditorenrechnung where kundeid = 7148
     group by vorsteuerid
 ) a order by anzahl desc limit 0,1

0

Nếu trên MySQL 8.0 trở lên, bạn có thể sử dụng các chức năng của Window :

Truy vấn:

DBFiddleExample

SELECT DISTINCT
FIRST_VALUE(ID) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS ID,
FIRST_VALUE(USER) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS USER,
FIRST_VALUE(TIME) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS TIME,
FIRST_VALUE(IO) OVER (PARTITION BY lms_attendance.USER ORDER BY lms_attendance.TIME DESC) AS IO
FROM lms_attendance;

Kết quả:

| ID | USER |       TIME |  IO |
--------------------------------
|  2 |    9 | 1370931664 | out |
|  3 |    6 | 1370932128 | out |
|  5 |   12 | 1370933037 |  in |

Ưu điểm tôi thấy khi sử dụng giải pháp do Justin đề xuất là nó cho phép bạn chọn hàng có dữ liệu gần đây nhất trên mỗi người dùng (hoặc mỗi id hoặc mỗi thứ) ngay cả từ các truy vấn con mà không cần xem hoặc bảng trung gian.

Và trong trường hợp bạn chạy HANA, nó cũng nhanh hơn ~ 7 lần: D


-1

Ok, đây có thể là một hack hoặc dễ bị lỗi, nhưng bằng cách nào đó nó cũng hoạt động tốt-

SELECT id, MAX(user) as user, MAX(time) as time, MAX(io) as io FROM lms_attendance GROUP BY id;

-2

Hãy thử truy vấn này:

  select id,user, max(time), io 
  FROM lms_attendance group by user;

Hãy thử tạo một SQLFiddle về điều này. Bạn có thể sẽ thấy rằng idiolà các cột không kết hợp, không thể được sử dụng trong a group by.
Dewi Morgan

1
không có id đảm bảo sẽ là id với max (thời gian), nó có thể là bất kỳ id nào trong nhóm. đây là vấn đề tôi đến đây để giải quyết, vẫn đang tìm kiếm
robisrob

-3

Có thể bạn có thể làm nhóm theo người dùng và sau đó đặt hàng theo thời gian. Một cái gì đó như dưới đây

  SELECT * FROM lms_attendance group by user order by time desc;

-3

Điều này làm việc cho tôi:

SELECT user, time FROM 
(
    SELECT user, time FROM lms_attendance --where clause
) AS T 
WHERE (SELECT COUNT(0) FROM table WHERE user = T.user AND time > T.time) = 0
ORDER BY user ASC, time DESC
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.