Bir seçimde mevcut ve bir sonraki büyük değeri nasıl alabilirim?


18

Sütunları ile bir InnoDB tablo 'idtimes' (MySQL 5.0.22-log) var

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

bileşik benzersiz bir anahtarla

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

böylece kimlik başına birden çok zaman damgası ve zaman damgası başına birden çok kimlik olabilir.

Varsa, bu yüzden tüm girişleri artı her giriş için bir sonraki büyük zaman almak bir sorgu kurmaya çalışıyorum, bu yüzden örneğin dönmelidir:

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

Şu an şimdiye kadar:

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;

ama elbette bu r.time> l.time ile tüm satırları döndürür ve sadece birincisini değil ...

Sanırım bir alt seçime ihtiyacım olacak

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;

ama nasıl şimdiki zaman başvurmak bilmiyorum (yukarıdaki geçerli SQL olmadığını biliyorum).

Bunu tek bir sorgu ile nasıl yaparım (ve her seferinde tablo bir satır adım adım ve son değeri hatırlamak bağlı @ değişkenler kullanmayı tercih etmem)?

Yanıtlar:


20

JOIN yapmak ihtiyacınız olabilecek bir şey.

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

Sanırım dış birleşme kasıtlı ve siz null almak istiyorsunuz. Daha sonra.

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

Sadece r'yi istiyorsun. l.time değerinden daha yüksek (MIN) zamana sahip satır. Alt sorgulamaya ihtiyacınız olan yer burası.

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

Şimdi sıfırlara. "Bir sonraki-yüksek zaman yok" ise, SELECT MIN () null (veya daha kötü) olarak değerlendirilir ve bu hiçbir zaman hiçbir şeyle karşılaştırılmaz, bu yüzden WHERE yan tümceniz asla tatmin olmaz ve "en yüksek zaman" her kimlik için hiçbir zaman sonuç kümesinde görünemedi.

JOIN'ınızı kaldırarak ve skaler alt sorguyu SELECT listesine taşıyarak çözersiniz:

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

Hep alt sorgular kullanmayı önlemek ya SELECTblokta veya FROMbu kodu "ahlaksız" ve bazen de daha az verimli hale getirir, çünkü bloğun.

Bence bunu yapmanın daha zarif bir yolu:

1. Satır zamanından daha büyük süreleri bulun

Bir ile yapabilirsiniz JOINarasındaki idtimes aynı katılmak kısıtlayıcı, kendisiyle masaya kimliği ve kat daha büyük zaman geçerli satırın.

Sen kullanmalıdır LEFT JOINhiçbir vardır satırları hariç önlemek için katı akım sıranın birden büyük.

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

Sorun, belirttiğiniz gibi, next_time öğesinin zamandan büyük olduğu birden fazla satıra sahip olmanızdır .

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

2. bigger_time değerinin yalnızca daha büyük değil next_time olduğu satırları bulun

Bu gereksiz tüm satırları filtrelemek için en iyi yol olup olmadığını bulmaktır kez arasındaki süre (büyüktür) ve greater_time (daha az) Bunun için 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, yanlış bir sonraki_zamanımız var !

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

WHEREAşağıdaki kısıtlamayı ekleyerek, bu etkinliğin gerçekleştiği satırlara filtre uygulamanız yeterlidir

WHERE
    i3.time IS NULL

Voilà, ihtiyacımız olan şey bizde!

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

Umarım 4 yıl sonra hala bir cevaba ihtiyacınız vardır!


Zekice. Yine de anlamanın daha kolay olduğundan emin değilim. Sanırım is nullve i3 ile birleşmeyi değiştirirsek, where not exists (select 1 from itimes i3 where [same clause])kodun ifade etmek istediğimizi daha yakından yansıtacağını düşünüyorum.
Andrew Spencer

Teşekkürler dostum (ertesi gün) kurtardın!
Jakob

2

Çözümü sunmadan önce, bunun güzel olmadığını belirtmeliyim. Masanızda bir AUTO_INCREMENTsütun olsaydı çok daha kolay olurdu (değil mi?)

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

Açıklama:

  • Sizinkiyle aynı katılın: iki masaya katılın, doğru olan sadece daha yüksek zamanları alır
  • Sol tablodan her iki sütunu GRUPLA: Bu, tüm (id, time)kombinasyonları (benzersiz olduğu da bilinir) almamızı sağlar.
  • Her biri için (l.id, l.time), olsun ilk r.time büyüktür ki l.time. Bu, ilk siparişi r.times aracılığıyla GROUP_CONCAT(r.time ORDER BY r.time), ilk jetonu dilimleyerek olur SUBSTRING_INDEX.

İyi şanslar ve bu tablo büyükse iyi performans beklemeyin.


2

Ayrıca iç seçim olmadan min()ve istediğinizi alabilirsiniz 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;

İsterim neredeyse optimizasyoncusu zaten Erwin Smout cevabı olarak aynı şey içine bu döndüğünü paranın büyük bir miktar bahis ve herhangi net olsun 's tartışılır ama şeyiyle orada ...


1
Değeri için, SSMS ve SQLServer 2016, sorgunuzu Erwin'inkinden çok daha fazla beğendi (2s çalışma zamanı ve 24s çalışma süresine karşı ~ 24k sonuç kümesi)
Nathan Lafferty

Andrew bahsi kaybetmiş gibi görünüyor :-)
Erwin Smout

İlginçtir, çünkü PK sütunlarından biri tarafından dış sorgu tablosuna katılan bir alt sorgunun bir grupla aynı olması genel bir durum olmalıdır. Başka veritabanlarının onu daha iyi optimize edip etmeyeceğini merak ediyorum. (BTW veritabanı optimize edicileri hakkında çok az şey biliyorum; sadece meraklıyım.)
Andrew Spencer
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.