Her GROUP BY grubundaki ilk satır seçilsin mi?


1322

Başlıktan da anlaşılacağı gibi, a ile gruplandırılmış her satır kümesinin ilk satırını seçmek istiyorum GROUP BY.

Özellikle, purchasesşöyle görünen bir tablo varsa :

SELECT * FROM purchases;

Çıktım:

id | müşteri | Toplam
--- + ---------- + ------
 1 | Joe | 5
 2 | Sally | 3
 3 | Joe | 2
 4 | Sally | 1

Her biri tarafından yapılan iden büyük satın alma ( total) sorgulamak istiyorum customer. Bunun gibi bir şey:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

Beklenen çıktı:

İLK (id) | müşteri | BİRİNCİ (toplam)
---------- + ---------- + -------------
        1 | Joe | 5
        2 | Sally | 3

sadece en büyüklerinden birini aradığın için neden sorgulamıyorsun MAX(total)?
phil294

4
@ phil294 max (toplam) sorgusu, bu toplamı oluştuğu satırın 'id' değeriyle ilişkilendirmez.
gwideman

Yanıtlar:


1116

Oracle 9.2+ (başlangıçta belirtildiği gibi 8i + değil), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rk
      FROM PURCHASES p)
SELECT s.*
  FROM summary s
 WHERE s.rk = 1

Herhangi bir veritabanı tarafından desteklenir:

Ancak bağları koparmak için mantık eklemeniz gerekir:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

2
Informix 12.x ayrıca pencere işlevlerini de destekler (CTE'nin türetilmiş bir tabloya dönüştürülmesi gerekir). Firebird 3.0 ayrıca Pencere işlevlerini de destekleyecektir
a_horse_with_no_name

37
ROW_NUMBER() OVER(PARTITION BY [...])diğer bazı optimizasyonlarla birlikte 30 saniyeden birkaç milisaniyeye kadar bir sorgu almama yardımcı oldu. Teşekkürler! (PostgreSQL 9.2)
Sam

8
Bir totalmüşteri için eşit derecede yüksek birden fazla satın alma işlemi varsa , 1. sorgu keyfi bir kazanan döndürür (uygulama ayrıntılarına bağlı olarak; idher yürütme için değişiklik yapılabilir!). Genellikle (her zaman değil) , "en küçük olanı " gibi ek ölçütlerle tanımlanan, müşteri başına bir satır istersiniz id. Düzeltmek idiçin ORDER BYlistesine ekleyin row_number(). Sonra bu sorgu için çok verimsiz 2. sorgu ile aynı sonucu alırsınız . Ayrıca, her ek sütun için başka bir alt sorguya ihtiyacınız olacaktır.
Erwin Brandstetter

2
Google'ın BigQuery'si, ilk sorgunun ROW_NUMBER () komutunu da destekler. Bizim için bir cazibe gibi çalıştı
Praxiteles

2
Window işlevine sahip ilk sürümün SQLite sürüm 3.25.0'dan itibaren çalıştığını unutmayın: sqlite.org/windowfunctions.html#history
brianz

1147

In PostgreSQL bu genellikle daha basit ve daha hızlı (daha fazla performans optimizasyonu aşağıda):

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

Veya sıralı sayıda çıkış sütunu ile daha kısa (net değilse):

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Eğer totalnull olabilir (her iki şekilde zarar vermez, ancak isteyeceksiniz mevcut dizinleri maç ):

...
ORDER  BY customer, total DESC NULLS LAST, id;

Önemli noktalar

  • DISTINCT ONstandardın PostgreSQL uzantısıdır (yalnızca DISTINCTtüm SELECTlistede tanımlanmıştır).

  • Cümledeki herhangi bir sayıda ifadeyi listeleyin DISTINCT ON; birleştirilmiş satır değeri kopyaları tanımlar. Kullanım kılavuzu:

    Açıkçası, en az bir sütun değerinde farklı olmaları durumunda iki satır ayrı kabul edilir. Bu karşılaştırmada null değerler eşit kabul edilir.

    Cesur vurgu benim.

  • DISTINCT ONile birleştirilebilir ORDER BY. Baştaki ifadeler, ORDER BYiçindeki ifadeler kümesinde olmalıdır DISTINCT ON, ancak bunlar arasında düzeni serbestçe yeniden düzenleyebilirsiniz. Misal. Her eş grubundan belirli bir satır seçmek için ek ifadeler ekleyebilirsiniz ORDER BY. Veya kılavuzun belirttiği gibi :

    DISTINCT ONİfade (lar) en soldaki eşleşmelidir ORDER BY ifade (ler). ORDER BYMaddesi, normal olarak, her içinde satır istenen öncelik belirleyen ek ifade (ler) içerecektir DISTINCT ONgrubu.

    idBağları koparmak için son öğe olarak ekledim :
    " idHer gruptan en küçük olanı en yüksek olan satırı seçin total."

    Sonuçları, grup başına ilkini belirleyen sıralama düzenine uymayacak şekilde sıralamak için, dış sorguyu yukarıdaki sorguyu başka bir sorgu ile iç içe yerleştirebilirsiniz ORDER BY. Misal.

  • Eğer totalnull olabilir, sen büyük olasılıkla büyük boş olmayan değere sahip satır istiyorum. NULLS LASTGösterildiği gibi ekleyin . Görmek:

  • SELECTListe halinde ifadeleri ile sınırlı değildir DISTINCT ONya ORDER BYherhangi bir şekilde. (Yukarıdaki basit durumda gerekli değildir):

    • Sen gerekmez içinde ifadelerin hiçbirini içermeyen DISTINCT ONveya ORDER BY.

    • Sen olabilir başka bir ifade içermektedir SELECTlistede. Bu, çok daha karmaşık sorguların alt sorgular ve toplama / pencere işlevleriyle değiştirilmesinde etkilidir.

  • Postgres 8.3 - 12 sürümlerini test ettim. Ancak özellik en azından 7.1 sürümünden beri oradaydı, bu yüzden temelde her zaman.

indeks

Mükemmel Yukarıdaki sorgu için endeks bir olacağını çok sütunlu endeksi dizisini eşleşen ve eşleşen sıralama düzeni ile her üç sütunu kapsayan:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Çok uzman olabilir. Ancak, belirli bir sorgu için okuma performansı önemliyse kullanın. Eğer varsa DESC NULLS LASTsorguda, bu sıralama düzeni maçlarda böylece endeksinde aynı kullanmak ve dizin geçerlidir.

Verimlilik / Performans optimizasyonu

Her sorgu için özel dizinler oluşturmadan önce maliyet ve avantajı tartın. Yukarıdaki endeksin potansiyeli büyük ölçüde veri dağıtımına bağlıdır .

Dizin, önceden sıralanmış veriler sağladığı için kullanılır. Postgres 9.2 veya sonraki sürümlerinde, sorgu yalnızca dizin temel tablodan küçükse dizin taramasından yararlanabilir . Bununla birlikte, dizin bütünüyle taranmalıdır.

Karşılaştırma

Burada şimdiye kadar modası geçmiş basit bir kriter vardı. Bu ayrı cevapta ayrıntılı bir kıyaslama ile değiştirdim .


28
Bu, çoğu veritabanı boyutu için harika bir cevap, ama yaklaştıkça ~ milyon satır DISTINCT ONson derece yavaş olduğunu belirtmek istiyorum . Uygulama her zaman tüm tabloyu sıralar ve tüm dizinleri yok sayarak (gerekli çok sütunlu dizini oluşturmuş olsanız bile) yinelemeler için tarar. Olası bir çözüm için bkz. Explainextended.com/2009/05/03/postgresql-optimizing-distinct .
Meekohi

14
"Kodu kısaltmak" için ordinal kullanmak korkunç bir fikirdir. Sütun adlarını okunabilir hale getirmek için bırakmaya ne dersiniz?
KOTJMF

13
@KOTJMF: Kişisel tercihinize göre gitmenizi öneririm. Her iki eğitimi de gösteririm. Sözdizimi steno, SELECTlistedeki uzun ifadeler için yararlı olabilir .
Erwin Brandstetter

1
@jangorecki: Orijinal kriter 2011'den geldi, artık kuruluma sahip değilim. Ama yine de pg 9.4 ve pg 9.5 ile testler yapma zamanı gelmişti. Eklenen cevaptaki ayrıntılara bakın. . Aşağıdaki kurulumunuzdan sonuç içeren bir yorum ekleyebilirsiniz?
Erwin Brandstetter

2
@PirateApp: Başımın üstünden değil. grup başına DISTINCT ONyalnızca bir satır almak için iyidir .
Erwin Brandstetter

134

Karşılaştırma

Postgres ile en ilginç adayları Test 9.4 ve 9.5 arasında bir yarım gerçekçi tabloyla 200k satır halinde purchasesve 10k ayrıcustomer_id ( ort. Müşteri başına 20 satır ).

Postgres 9.5 için etkin bir şekilde 86446 farklı müşteriyle 2. bir test yaptım. Aşağıya bakın ( müşteri başına ortalama 2,3 satır ).

Kurmak

Ana tablo

CREATE TABLE purchases (
  id          serial
, customer_id int  -- REFERENCES customer
, total       int  -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);

Daha tipik bir kurulum olduğu için serial(aşağıda eklenen PK kısıtlaması) ve bir tam sayı kullanıyorum customer_id. Ayrıca some_columntipik olarak daha fazla sütun oluşturmak için de eklendi .

Kukla veriler, PK, dizin - tipik bir tabloda bazı ölü tupl'ler de vardır:

INSERT INTO purchases (customer_id, total, some_column)    -- insert 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9; -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);

VACUUM ANALYZE purchases;

customer tablo - üstün sorgu için

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

Benim içinde ikinci testte 9.5 için ben aynı kurulumu uygulandı fakat random() * 100000oluşturmak için customer_idbaşına sadece birkaç satır olsun customer_id.

Tablo için nesne boyutları purchases

Bu sorgu ile oluşturuldu .

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

Sorguları

1. row_number()CTE'de, ( diğer cevaba bakınız )

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

2. row_number()alt sorguda (optimizasyonum)

SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3. DISTINCT ON( diğer cevaba bakınız )

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4. LATERALalt sorgu ile rCTE ( buraya bakınız )

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5. ile customertablo LATERAL( buraya bakınız )

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. array_agg()ile ORDER BY( diğer cevaba bakınız )

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

Sonuçlar

Yukarıdaki sorgular için yürütme süresi EXPLAIN ANALYZE(ve tüm seçenekler kapalı ), en iyi 5 çalıştırma .

Tüm sorgular bir kullanılmış Sadece Dizini Tara üzerinde purchases2_3c_idx(diğer adımların yanı sıra). Bazıları sadece indeksin daha küçük boyutu için, diğerleri daha etkili.

A. 200k satırlı postgres 9.4 ve başına ~ 20 customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B. Postgres 9.5 ile aynı

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  
5.  33.944 ms  -- winner
6. 211.540 ms  

C.B ile aynı, ancak başına ~ 2.3 satır customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

İlgili kriterler

İşte "Öğr" ile test ederek yeni bir tanesidir 10M satır ve 60k benzersiz "müşteriler" üzerine Postgres 11.5 (Eylül 2019 itibariyle akımı). Sonuçlar hâlâ gördüğümüzle aynı doğrultuda:

2011'den orijinal (modası geçmiş) karşılaştırması

PostgreSQL 9.1 ile 65579 satırlık gerçek bir yaşam tablosunda üç test yaptım ve ilgili üç sütunun her birinde tek sütunlu btree dizinleri kullandım ve 5 çalışmanın en iyi yürütme süresini aldım .
Karşılaştırma @OMGPonies' ilk sorgu ( Akadar) yukarıdaki DISTINCT ONçözeltiye ( B):

  1. Tüm tabloyu seçin, bu durumda 5958 satırla sonuçlanır.

    A: 567.218 ms
    B: 386.673 ms
    
  2. WHERE customer BETWEEN x AND y1000 sıra ile sonuçlanan koşul kullanın .

    A: 249.136 ms
    B:  55.111 ms
    
  3. İle tek bir müşteri seçin WHERE customer = x.

    A:   0.143 ms
    B:   0.072 ms
    

Aynı cevap diğer cevapta açıklanan indeks ile tekrarlandı

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

5
Harika bir karşılaştırma için teşekkürler. Toplam yerine bir zaman damgası olan olay verileri sorgulama yeni BRIN endeksi fayda sağlayacak olup olmadığını merak ediyordum . Bu, geçici sorgular için potansiyel olarak hızlanmaya neden olabilir.
jangorecki

3
@jangorecki: Fiziksel olarak sıralanmış veriler içeren büyük tablolar bir BRIN dizininden yararlanabilir.
Erwin Brandstetter

@ErwinBrandstetter 2. row_number()ve 5. customer table with LATERALörneklerinde, kimliğin en küçük olmasını sağlayan nedir?
Artem Novikov

@ArtemNovikov: Hiçbir şey. Amaç, customer_id en yüksek sıraya göre almaktır total. Sorunun test verilerinde id, seçilen satırlarda da en küçük başına gelenin yanıltıcı bir tesadüf olması customer_id.
Erwin Brandstetter

1
@ArtemNovikov: Yalnızca dizin taranmasına izin vermek için.
Erwin Brandstetter

55

Bu yaygın zaten iyi test edilmiş ve son derece optimize edilmiş çözümlere sahip bir problem . Şahsen Bill Karwin'in ( birçok başka çözüm içeren orijinal yazı) sol birleşim çözümünü tercih ediyorum .

Bu ortak soruna yönelik birçok çözümün şaşırtıcı bir şekilde en resmi kaynaklardan biri olan MySQL kılavuzunda bulunabileceğini unutmayın ! Bkz . Ortak Sorgu Örnekleri :: Belirli Bir Sütunun Grup Olarak En Fazla Tutarı Satırlar .


22
MySQL kılavuzu Postgres / SQLite (SQL'den bahsetmiyorum) soruları için nasıl herhangi bir şekilde "resmi"? Ayrıca, net olmak gerekirse, DISTINCT ONsürüm çok daha kısa, daha basittir ve genellikle Postgres'de kendiliğinden LEFT JOINveya yarı anti-birleştirme ile alternatiflerden daha iyi performans gösterir NOT EXISTS. Ayrıca "iyi test edilmiştir".
Erwin Brandstetter

3
Ayrıca Erwin yazdıklarını, ben (ortak SQL işlevi günümüzde ise) bir pencere fonksiyonu kullanılarak her zaman neredeyse daha hızlı bir türetilmiş bir tablo ile katılmak kullanmaktan daha olduğunu söyleyebilirim
a_horse_with_no_name

6
Harika referanslar. Bunun grup başına en büyük n problemi olduğunu bilmiyordum. Teşekkür ederim.
David Mann

Soru yapar değil gibi büyük grup başına n ancak ilk n.
reinierpost

1
Denediğim iki sipariş alanında, "Bill Karwin'in soldan ayrılma çözümü" kötü performans verdi. Aşağıdaki
Johnny Wong

30

Postgres'de şöyle kullanabilirsiniz array_agg:

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

Bu size idher müşterinin en büyük satın alımını verecektir .

Dikkat edilmesi gereken bazı noktalar:

  • array_aggbir toplama işlevidir, bu nedenle birlikte çalışır GROUP BY.
  • array_aggyalnızca kendisinin kapsamını belirten bir sipariş belirtmenize olanak tanır, bu nedenle tüm sorgunun yapısını kısıtlamaz. Varsayılandan farklı bir şey yapmanız gerekiyorsa, NULL'ları nasıl sıraladığınıza ilişkin sözdizimi de vardır.
  • Diziyi oluşturduktan sonra, ilk öğeyi alırız. (Postgres dizileri 1 dizinli, 0 dizinli değil).
  • array_aggÜçüncü çıktı sütununuz için benzer bir şekilde kullanabilirsiniz , ancak max(total)daha basittir.
  • Aksine DISTINCT ONkullanarak array_aggsize tutmak sağlar GROUP BY, eğer başka nedenlerden dolayı bunu istiyorum.

14

SubQ'ların varlığı nedeniyle çözüm Erwin'in işaret ettiği gibi çok verimli değil

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;

Teşekkürler, evet size katılıyorum, subq ve dış sorgu arasındaki bağlantı aslında daha uzun sürüyor. "In" burada bir sorun olmayacak çünkü subq sadece bir satır oluşturacak. BTW, hangi sözdizimi hatasına işaret ediyorsunuz ??
user2407394

ohh .. "Teradata" için kullanılır .. şimdi düzenlenmiş .. burada her müşteri için en yüksek toplamı bulmak gerekir gibi kopan bağlar gerekli değildir ..
user2407394

Beraberlik durumunda tek bir müşteri için birden fazla satır aldığınızın farkında mısınız? Bunun istenip istenmeyeceği kesin gereksinimlere bağlıdır. Normalde değil. Eldeki soru için, başlık oldukça açık.
Erwin Brandstetter

Bu sorudan net değil, aynı müşterinin 2 farklı kimlik için satın alma = Max varsa, her ikisini de göstermeliyiz.
user2407394

10

Bu yolu kullanıyorum (yalnızca postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

O zaman örneğiniz neredeyse şu şekilde çalışmalıdır :

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

CAVEAT: NULL satırlarını yok sayar


Düzenle 1 - Bunun yerine postgres uzantısını kullanın

Şimdi şu şekilde kullanıyorum: http://pgxn.org/dist/first_last_agg/

Ubuntu 14.04 üzerine kurmak için:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

Size ilk ve son işlevleri veren bir postgres uzantısıdır; görünüşte yukarıdaki yoldan daha hızlı.


Düzenle 2 - Sipariş verme ve filtreleme

Toplama işlevlerini (bunlar gibi) kullanıyorsanız, verilerin önceden sipariş edilmesine gerek kalmadan sonuçları sipariş edebilirsiniz:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

Yani, eşdeğer örnek, sipariş ile şöyle bir şey olurdu:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

Tabii ki agrega içinde uygun gördüğünüz gibi sipariş verebilir ve filtreleyebilirsiniz; çok güçlü bir sözdizimi.


Bu özel fonksiyon yaklaşımını kullanarak. Yeterince evrensel ve basit. Neden işleri karmaşıklaştırıyor, bu diğerlerine göre önemli ölçüde daha az performanslı bir çözüm mü?
Sergey Shcherbakov

9

Sorgu:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

BU NASIL ÇALIŞIYOR! (Ben orada oldum)

Her satın alma işlemi için yalnızca en yüksek toplama sahip olduğumuzdan emin olmak istiyoruz.


Bazı Teorik Öğeler (yalnızca sorguyu anlamak istiyorsanız bu bölümü atlayın)

Total, isim ve id değeri verilen bir değer döndürdüğü bir işlev T (müşteri, id) olsun. Verilen toplamın (T (müşteri, id)) en yüksek olduğunu kanıtlamak için,

  • ∀x T (müşteri, kimlik)> T (müşteri, x) (bu toplam, o müşteri için diğer tüm toplamdan daha yüksektir)

VEYA

  • ¬∃x T (müşteri, kimlik) <T (müşteri, x) (bu müşteri için daha yüksek bir toplam yok)

İlk yaklaşım, gerçekten sevmediğim o isim için tüm kayıtları almamıza ihtiyaç duyacak.

İkincisi, bundan daha yüksek bir kayıt olamayacağını söylemek için akıllı bir yola ihtiyaç duyacaktır.


SQL'e geri dön

Eğer ayrılırsak, adı ve toplamı birleştirilen tablodan daha az olan tabloya katılırsak:

      LEFT JOIN purchases as p 
      ON 
      p.customer = purchases.customer 
      AND 
      purchases.total < p.total

aynı kullanıcının katılması için toplamı daha yüksek olan başka bir kayda sahip tüm kayıtların tutulmasını sağlıyoruz:

purchases.id, purchases.customer, purchases.total, p.id, p.customer, p.total
1           , Tom           , 200             , 2   , Tom   , 300
2           , Tom           , 300
3           , Bob           , 400             , 4   , Bob   , 500
4           , Bob           , 500
5           , Alice         , 600             , 6   , Alice   , 700
6           , Alice         , 700

Bu, gruplandırma gerekmeden her satın alma işlemi için en yüksek toplamı filtrelememize yardımcı olur:

WHERE p.total IS NULL

purchases.id, purchases.name, purchases.total, p.id, p.name, p.total
2           , Tom           , 300
4           , Bob           , 500
6           , Alice         , 700

Ve ihtiyacımız olan cevap bu.


8

Çok hızlı çözüm

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

ve tablo id ile indekslenmişse gerçekten çok hızlı:

create index purchases_id on purchases (id);

KULLANIM yan tümcesi çok standarttır. Sadece bazı küçük veritabanı sistemlerinde yoktur.
Holger Jakobs

2
Bu, müşterilerin en büyük toplamla satın alımını bulamaz
Johnny Wong

7

SQL Server'da şunları yapabilirsiniz:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

Açıklama: İşte göre gruplama müşteri bazında yapılır ve daha sonra Strank olarak toplam ardından her tür grup verilir seri numarasına göre sipariş ve biz kimin Strank 1'dir ilk 1 müşteriyi dışarı alıyor


Teşekkür ederim! Bu mükemmel çalıştı ve anlaşılması ve uygulanması çok kolaydı.
ruohola


4

PostgreSQL'de başka bir olasılık first_valuepencere işlevini aşağıdakilerle birlikte kullanmaktır SELECT DISTINCT:

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

Bir kompozit oluşturdum (id, total), böylece her iki değer de aynı toplama tarafından döndürülüyor. Tabii ki her zaman first_value()iki kez başvurabilirsiniz .


3

Kabul edilen OMG Ponies'in "Herhangi bir veritabanı tarafından desteklenir" çözümü testimden iyi bir hıza sahip.

Burada aynı yaklaşım, ancak daha eksiksiz ve temiz herhangi bir veritabanı çözümü sağlar. Bağlar dikkate alınır (her müşteri için yalnızca bir satır, hatta müşteri başına maksimum toplam için birden fazla kayıt alma isteğinin olduğunu varsayalım) ve satın alma tablosundaki gerçek eşleşen satırlar için diğer satın alma alanları (örneğin satın alma_ödeme_kimliği) seçilecektir.

Herhangi bir veritabanı tarafından desteklenir:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

Bu sorgu, özellikle satın alma tablosunda (müşteri, toplam) gibi bileşik bir dizin olduğunda oldukça hızlıdır.

remark:

  1. t1, t2, veritabanına bağlı olarak kaldırılabilen alt sorgu takma adlarıdır.

  2. Dikkat : using (...)fıkra şu anda Ocak 2017'de MS-SQL ve Oracle db'de desteklenmemektedir. Örneğin kendinize genişletmeniz gerekir on t2.id = purchase.id. KULLANIM sözdizimi SQLite, MySQL ve PostgreSQL'de çalışır.


2

Snowflake / Teradata, pencereli fonksiyonlar için QUALIFYçalışan cümleyi destekler HAVING:

SELECT id, customer, total
FROM PURCHASES
QUALIFY ROW_NUMBER() OVER(PARTITION BY p.customer ORDER BY p.total DESC) = 1

1
  • Birleştirilmiş satırlar kümesinden (belirli bir koşulunuza göre) herhangi bir satır seçmek istiyorsanız.

  • Buna sum/avgek olarak başka bir ( ) toplama işlevi kullanmak istiyorsanız max/min. Böylece ipucu kullanamazsınız.DISTINCT ON

Sonraki alt sorguyu kullanabilirsiniz:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

İstediğiniz amount = MAX( tf.amount )herhangi bir koşulu tek bir kısıtlamayla değiştirebilirsiniz: Bu alt sorgu birden fazla satır döndürmemelidir

Ama böyle şeyler yapmak istiyorsanız, muhtemelen pencere işlevlerini arıyorsunuz


1

SQl Server için en etkili yol:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

kullanılmış sütunlar için kümelenmiş dizin oluşturmayı unutmayın

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.