2011-02-06 17 views
11

Bir sütunda ikinci en büyük değeri ve yalnızca ikinci en büyük değeri bulmaya çalışıyorum.Bir sütunda ikinci en büyük değeri bulmaya çalışırken (postgres sql)

Nedense
select a.name, max(a.word) as word 
from apple a 
where a.word < (select max(a.word) from apple a) 
group by a.name; 

, bende ne şimdi ikinci en büyük değeri VE aynı zamanda tüm alt değerleri döndürür ama neyse en büyük değeri önler.

Bunu düzeltmenin bir yolu var mı?Aşağıda may

select name,word 
    from (
     select name,word 
       , row_number() over (partition by name 
             order by word desc) 
       as rowNum 
      from apple 
     ) x 
where rowNum = 2 

Bu versiyon:

+0

Sorunu, hangi satırları döndürmeyi istediğinizi söylemek için düzeltin (ör. Kaç satır). Bu yardımcı olur. – ijw

cevap

0

Çok kaba kuvvet sorgusu, ancak bu tam olarak ve sadece bir kez tabloyu geçmesi de kaba kuvvet, ama sadece garantilidir

select a.name, a.word 
from apple a 
where (select count(distinct b.word) from apple b 
    where b.word > a.word) = 1 
3

işleri (ad, kelime) üzerinde bir örtme dizini varsa ve ad başına yüksek sayıda sözcük değeri varsa daha iyi performans gösterme:

with recursive myCte as 
(
select name,max(word) as word 
     , 1 as rowNum 
    from apple 
    group by name 
    union all 
select par.name 
     , (select max(word) as word 
      from apple 
      where name = par.name 
      AND word < par.word 
     ) as word 
     , 2 as rowNum 
    from myCte par 
    where par.rowNum = 1 
) 
select * from myCte where rownum = 2 
+0

+1: Fakat ROW_NUMBER, [PostgreSQL 8.4+] (http://explainextended.com/2009/05/11/postgresql-emulating-row_number/) –

5

verimsiz olsa, basit (dizi bellek tüketebileceği):

select student, (array_agg(grade order by grade desc))[2] 
from 
student_grades 
group by student 

verimli bir:

create aggregate two_elements(anyelement) 
(
sfunc = array_limit_two, 
stype = anyarray, 
initcond = '{}' 
); 

create or replace function array_limit_two(anyarray, anyelement) returns anyarray 
as 
$$ 
begin 
    if array_upper($1,1) = 2 then 
     return $1; 
    else 
     return array_append($1, $2); 
    end if; 
end; 
$$ language 'plpgsql'; 

Deney verileri:

create table student_grades 
(
student text, 
grade int 
); 



insert into student_grades values 
('john',70), 
('john',80), 
('john',90), 
('john',100); 


insert into student_grades values 
('paul',20), 
('paul',10), 
('paul',50), 
('paul',30); 


insert into student_grades values 
('george',40); 

test kodu:

-- second largest 
select student, coalesce((two_elements(grade order by grade desc))[2], max(grade) /* min would do too, since it's one element only */) 
from 
student_grades 
group by student 


-- second smallest 
select student, coalesce((two_elements(grade order by grade))[2], max(grade) /* min would do too, since it's one element only */) 
from 
student_grades 
group by student 

Çıkış:

q_and_a=# -- second largest 
q_and_a=# select student, coalesce((two_elements(grade order by grade desc))[2], max(grade) /* min would do too, since it's one element only */) 
q_and_a-# from 
q_and_a-# student_grades 
q_and_a-# group by student; 
student | coalesce 
---------+---------- 
george |  40 
john |  90 
paul |  30 
(3 rows) 


q_and_a=# 
q_and_a=# -- second smallest 
q_and_a=# select student, coalesce((two_elements(grade order by grade))[2], max(grade) /* min would do too, since it's one element only */) 
q_and_a-# from 
q_and_a-# student_grades 
q_and_a-# group by student; 
student | coalesce 
---------+---------- 
george |  40 
john |  80 
paul |  20 
(3 rows) 

DÜZENLEME basit (ve aynı zamanda verimli) @diesel :

-- second largest 
select student, array_min(two_elements(grade order by grade desc)) 
from 
student_grades 
group by student; 

-- second smallest 
select student, array_max(two_elements(grade order by grade)) 
from 
student_grades 
group by student; 

array_max fonksiyonu:

create or replace function array_min(anyarray) returns anyelement 
as 
$$ 
select min(unnested) from(select unnest($1) unnested) as x 
$$ language sql; 

create or replace function array_max(anyarray) returns anyelement 
as 
$$ 
select max(unnested) from(select unnest($1) unnested) as x 
$$ language sql; 

DÜZENLEME

olabilir En basit ve en verimli, sadece PostgreSQL yerleşik bir işlevi array_max yapmak ve toplanma üzerinde SINIR maddesi :-) agregasyonlari SINIR maddesini kolaylaştırır olsaydı

select student, array_max(array_agg(grade order by grade limit 2)) 
from 
student_grades 
group by student; 

Postgresql

benim rüya özelliği toplama o SINIR olmasa da olduğunu

-- second largest 
select student, 

    array_min 
    (

     array ( 
       select grade from student_grades 
       where student = x.student order by grade desc limit 2) 

    ) 

from 
student_grades x 
group by student; 


-- second smallest 
select student, 

    array_max 
    (

     array ( 
       select grade from student_grades 
       where student = x.student order by grade limit 2) 

    ) 

from 
student_grades x 
group by student; 
+0

'un daha basit bir yolu var mı? –

+2

Ben pgplsql işlevleri içeren herhangi bir şey 'basit' olduğunu açıklıyor. ;) – ijw

+0

@ijw: Endişelenme, yalnızca birleşik fonksiyon üzerindeki LIMIT maddesinin Postgresql'de bulunmasından önce geçen süre. Şimdilik, bir şeyleri yapmak için kendi yolunuzu yuvarlamanız gerekiyor ;-) –

0

başka bir yaklaşım, kullanım RANK: MIN

with ranking as 
(
    select student, grade, rank() over(partition by student order by grade desc) as place 
    from 
    student_grades 
) 
select * 
from 
ranking 
where 
    (student, place) 
    in 
    (
     select student, max(place) 
     from ranking 
     where place <= 2 
     group by student 
    ) 

İkincisi:

henüz mevcut, bunu kullan
with ranking as 
(
    select student, grade, 
     rank() 
     -- just change DESC to ASC 
     over(partition by student order by grade ASC) as place 
    from 
    student_grades 
) 
select * 
from 
ranking 
where 
    (student, place) 
    in 
    (    
     select student, max(place) -- still max 
     from ranking 
     where place <= 2 
     group by student 
    ) 
+0

Sırasıyla rank() kullanırken alt seçimin gerekli olmadığını düşünüyorum. İlk versiyon için yer = 2' yeterli olmalı mı? –

+0

Eğer gerçekten PostgreSQL * varsa * Rank(). – PerformanceDBA

+0

@ahwnn: Ben sadece oraya koydum, eğer ikinci bir şey yoksa (örneğin George için), ilkini döndürür. Gerçekten de, "WHERE place = 2" sadece @PerformanceDBA kullanarak alt sorgulama olmadan yeniden yazılabilir: Postgresql Rank(), Postgresql –

1
 
SELECT * 
FROM (
    SELEC name, 
     dense_rank() over (partition by name order by word desc) as word_rank, 
     count(*) over (partition by name) as name_count 
    FROM apple 
) t 
WHERE (word_rank = 2 OR name_count = 1) 

Düzenleme:
name_count = 1 sadece tek bir satır, belirli adı için mevcut olduğu durumlarda ilgilenir.

kullanma yerine rank() ait dense_rank() word_rank ile bir satır olduğu için emin olur = 2 DENSE_RANK yapar gibi emin

+0

@a_horse_with_no_name indirgenebilir. Orable. – PerformanceDBA

+0

@PerformanceDBA: Orable ne anlama geliyor? –

+0

@a_horse_with_no_name. 1) Oracle etiketli shrink-wrap paketini plastikten çıkardığınızda elde ettiğiniz veritabanıdır. 2) PostgreSQL, OP'nin sorusu için mevcut olmayan Orable işlevleri (Standart olmayan SQL) sağladınız. – PerformanceDBA

0

Ümmü, sadece kastetmiyorum herhangi bir boşluk yoktur:

select a.name, max(a.word) as word 
from apple a 
where a.word < (select max(b.word) from apple b WHERE a.name = b.name) 
group by a.name; 

yapmak sen? İsim başına bir satır, ad başına ikinci en yüksek değeri döndürür (veya ikinci en yüksek değer yoksa satır yok).

İstediğiniz şey buysa, sorgunuz sadece bir kısıtlama eksikti, ancak eğer yukarıdaki PostgreSQL'in bir JOIN'e dönüştürmek için bir anlamı varsa, muhtemelen iki tablo taramasından kaynaklandığından şüpheleniyorum.

10

EXPLAIN ANALYZE'e göre, burada 21 milyon satırlık bir tabloda 1 milisaniyede çalışan bir başka kavramsal olarak basit çözüm. Tek bir değerin olduğu durumda hiçbir şey döndürmez. Bana böyle TARAFINDAN SİPARİŞ kullanmasına izin verir benim masa zaten isim, kelime ve (isim, kelime) üzerinde mevcut dizinleri vardı

SELECT a.name, 
(SELECT word FROM apple ap WHERE ap.name=a.name ORDER BY word ASC OFFSET 1 LIMIT 1) 
FROM apple a 

Not.

+0

Bu dizinler yerinde ise harika çalışır. Şu anda 3 milyondan oluşan bir tablodan 2000 satır seçiyor. – AgDude

İlgili konular