Kullanıcı başına en son tarihi içeren satırı seçin


125

Kullanıcıların giriş ve çıkış sürelerinin aşağıdaki gibi görünen bir tablosu ("lms_attendance") var:

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

Bana "giriş" veya "çıkış" değerini verirken, kullanıcı kimliği başına yalnızca en son kaydı çıkaracak bu tablonun bir görünümünü oluşturmaya çalışıyorum, yani:

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

Şimdiye kadar oldukça yakınım, ancak görünümlerin alt sorguları kabul etmeyeceğini fark ettim, bu da işi çok daha zor hale getiriyor. Aldığım en yakın sorgu şuydu:

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`

Ama aldığım şey:

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

Hangisi yakın ama mükemmel değil. Son grubun orada olmaması gerektiğini biliyorum, ancak onsuz, en son zamanı döndürüyor, ancak göreli IO değeriyle değil.

Herhangi bir fikir? Teşekkürler!



Kılavuza geri dönün. Alt sorgularla birlikte ve alt sorgular olmadan bu soruna çözümler sunduğunu göreceksiniz.
Strawberry

@Barmar, teknik olarak, cevabımda da belirttiğim gibi, bu, grup başına en büyük etiketiyle tüm 700 sorunun bir kopyası .
TMS

@Prodikl, 'io (enum)' nedir?
Monica Heddneck

"IO" adında "içeri veya dışarı" anlamına gelen bir sütunum vardı, "giriş" veya "dışarı" olası değerlere sahip bir enum türüydü. Bu, insanların bir sınıfa ne zaman giriş ve çıkış yaptığını takip etmek için kullanıldı.
Keith

Yanıtlar:


199

Sorgu:

SQLFIDDLEExample

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

Sonuç:

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

Her zaman işe yarayacak çözüm:

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
Vaov! bu sadece işe yaramadı, alt sorgular içerse bile bu sorgu ile bir görünüm oluşturmama izin verildi. daha önce, alt sorgular içeren bir görünüm oluşturmaya çalıştığımda, bana izin vermedi. Buna neden izin verildiğine dair kurallar var ama başka birine izin verilmiyor mu?
Keith

çok garip. çok teşekkürler! belki de alt sorgumun FROM'u seçtiğim sahte bir tablo olmasıydı, bu örnekte WHERE yan tümcesinde kullanılıyordu.
Keith

4
Alt sorgulara gerek yok! Dahası, tam olarak aynı zamana sahip iki kayıt varsa bu çözüm çalışmaz . Her seferinde tekerleği yeniden keşfetmeye gerek yok, çünkü bu yaygın bir sorundur - bunun yerine, önceden test edilmiş ve optimize edilmiş çözümlere gidin - @Prodikl cevabımı görün.
TMS

ah, anlayış için teşekkürler! yarın ofisteyken yeni kodu deneyeceğim.
Keith

3
@TMS Bu çözüm, sorgu en büyük kimliğe sahip kaydı bulduğundan, kayıtlar tam olarak aynı zamana sahipse işe yarar. Bu, tablodaki zamanın yerleştirme zamanı olduğu anlamına gelir ve bu iyi bir varsayım olmayabilir. Çözümünüz bunun yerine zaman damgalarını karşılaştırır ve iki zaman damgası aynı olduğunda, en büyük kimliğe sahip satırı da döndürürsünüz. Dolayısıyla, çözümünüz, bu tablodaki zaman damgasının, her iki sorgunuzdaki en büyük kusur olan ekleme sırasıyla ilgili olduğunu da varsayar.
WebWanderer

73

Tekerleği yeniden keşfetmeye gerek yok, çünkü bu grup başına en büyük sorun . Çok güzel bir çözüm sunuldu .

Alt sorgular olmadan en basit çözümü tercih ediyorum ( bkz. SQLFiddle, güncellenmiş Justin's ) (bu nedenle görünümlerde kullanımı kolay):

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

Bu aynı zamanda aynı grup içinde aynı en büyük değere sahip iki farklı kaydın olduğu bir durumda da işe yarar - ile hilesi sayesinde (t1.time = t2.time AND t1.Id < t2.Id). Burada yaptığım tek şey, aynı kullanıcının iki kaydının aynı zamana sahip olması durumunda, yalnızca birinin seçildiğinden emin olmaktır. Kriterlerin mi yoksa Idbaşka bir şey mi olduğu aslında önemli değil - temelde benzersiz olması garanti edilen herhangi bir kriter burada işi yapacaktır.


1
Maksimum kullanım t1.time < t2.timeve minimum, t1.time > t2.timeilk sezgimin tersi olan olacaktır .
Hiçbiri

1
@ J.Money çünkü gizli olumsuzluk var: t1'den koşulun geçerli olduğu t2'den karşılık gelen kaydı olmayan tüm kayıtları seçersiniz t1.time < t2.time:-)
TMS

4
WHERE t2.user IS NULLbiraz tuhaf. Bu çizgi nasıl bir rol oynuyor?
tumultous_rooster

1
Justin tarafından gönderilen kabul edilen cevap daha uygun olabilir. Kabul edilen yanıt, tablonun birincil anahtarında geriye dönük bir dizin taraması, ardından bir sınır ve ardından tablonun sıralı taramasını kullanır. Bu nedenle, kabul edilen cevap, ek bir indeks ile büyük ölçüde optimize edilebilir. Bu sorgu, iki sekans taraması gerçekleştirdiği için bir indeks tarafından da optimize edilebilir, ancak aynı zamanda sekans taraması ve diğer sekans taramasının karma sonuçlarının bir karma ve bir "hash-anti-birleşimini" içerir. Hangi yaklaşımın gerçekten daha optimal olduğuna dair bir açıklama ile ilgilenirim.
WebWanderer

@TMS OR (t1.time = t2.time AND t1.Id < t2.Id))bölümünü açıklar mısınız?
Oleg Kuts

6

@TMS cevabına dayanılarak, alt sorgulara gerek olmadığı için hoşuma gitti, ancak 'OR'parçayı atlamanın yeterli ve anlaşılması ve okunması çok daha kolay olacağını düşünüyorum .

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

boş zamana sahip satırlarla ilgilenmiyorsanız, bunları WHEREmaddede filtreleyebilirsiniz :

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

ORİki kayıt aynı olabiliyorsa , parçayı çıkarmak gerçekten kötü bir fikirdir time.
TMS

Performans uğruna bu çözümden kaçınırdım. @OlegKuts'un da belirttiği gibi, bu orta ve büyük veri kümelerinde çok yavaşlıyor.
Peter Meadley

4

Zaten çözüldü, ancak sadece kayıt için, başka bir yaklaşım iki görünüm oluşturmak olacaktır ...

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;

SQL Fiddle'da çalışırken görmek için buraya tıklayın


1
Takip için teşekkürler! evet, daha kolay bir yol olmasaydı birden çok görünüm oluşturacaktım. tekrar teşekkürler
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

Teşekkürler. Bunu bir alt sorgu kullanarak yapabileceğimi biliyorum, ancak bunu bir görünüme dönüştürmeyi umuyordum ve AFAIK görünümlerinde alt sorgulara izin vermiyor. her bir alt sorguyu bir görünüme vb. dönüştürmem gerekir mi?
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

MySQL 8.0 veya üzerindeyse, Pencere işlevlerini kullanabilirsiniz :

Sorgu:

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;

Sonuç:

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

Justin tarafından önerilen çözümü kullanmakta gördüğüm avantaj , ara bir görünüme veya tabloya ihtiyaç duymadan alt sorgulardan bile kullanıcı başına (veya kimlik başına veya her neyse) en son verileri içeren satırı seçmenize olanak sağlamasıdır.

Ve bir HANA çalıştırmanız durumunda da ~ 7 kat daha hızlıdır: D


-1

Tamam, bu bir hack veya hataya açık olabilir, ancak bir şekilde bu da çalışıyor.

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

-2

Bu sorguyu deneyin:

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

Bundan bir SQLFiddle yapmayı deneyin. Bunu büyük olasılıkla bulacaksınız idve iobir group by.
Dewi Morgan

1
max (zaman) ile kimlik olacağına dair hiçbir garanti kimliği yoktur, grup içindeki kimliklerden herhangi biri olabilir. Buraya çözmeye geldiğim sorun bu, hala bakıyorum
robisrob

-3

Muhtemelen kullanıcıya göre gruplama yapabilir ve ardından zamana göre sıralama yapabilirsiniz. Aşağıdaki gibi bir şey

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

-3

Bu benim için çalıştı:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.