2011-02-06 1 views
11

Я пытаюсь найти второе по величине значение в столбце и только второе по величине значение.Попытка найти второе по величине значение в столбце (postgres sql)

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; 

По какой-то причине, что я теперь возвращает второе по величине значение, и все более низкие значения также, но, к счастью, избегает наибольшее значение.

Есть ли способ исправить это?

+0

Устраните свой вопрос, чтобы сказать, какие строки вы хотите вернуть (например, сколько строк). Это поможет. – ijw

ответ

0

очень перебором запрос, но он работает

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

Это тоже перебор, но гарантированно передавать только таблицу точно и только один раз:

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 

Эта версия ниже может лучше работать, если у вас есть индекс покрытия (имя, слово), и имеется большое количество значений слов для каждого имени:

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: Но ROW_NUMBER является [PostgreSQL 8.4+] (http://explainextended.com/2009/05/11/postgresql-emulating-row_number/) –

5

Самый простой, хотя и неэффективный (массив может исчерпать память):

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

Эффективное одно:

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'; 

Данные испытаний:

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); 

Тестовый код:

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

Выход:

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) 

EDIT @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 функция:

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; 

EDIT

Может быть простой и эффективный из всех, если только Postgresql бы array_max встроенную функцию и облегчает положение LIMIT на агрегатах :-) п LIMIT на агрегацию моя особенность мечта о Postgresql

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

Хотя это LIMIT на агрегацию не еще доступны, используйте:

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

есть ли более простой способ? –

+2

Я обсуждаю утверждение, что все, что связано с функциями pgplsql, является «простым». ;) – ijw

+0

@ijw: Не волнуйтесь, это всего лишь вопрос времени, прежде чем предложение LIMIT об агрегированной функции будет доступно на Postgresql. На данный момент вам нужно сворачивать свой собственный способ делать вещи ;-) –

0

Другой подход, использование RANK:

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 
    ) 

Второй к MIN:

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

Я не думаю, что при выборе ранга() требуется подвыбор. Не должно ли 'WHERE place = 2' быть достаточно для первой версии? –

+0

Если действительно PostgreSQL * имеет * Rank(). – PerformanceDBA

+0

@ahwnn: Я просто положил его туда, чтобы не было секунды (например, для Джорджа), он вернет первый. Действительно можно переписать без подзапроса, используя 'WHERE place = 2' only @PerformanceDBA: 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) 

Редактировать:
name_count = 1 заботится о тех случаях, когда только одна строка присутствует для конкретного имени.

Использование dense_rank() вместо rank() убеждается там является ряд с word_rank = 2, как DENSE_RANK делает, что нет никаких зазоров

+0

@a_horse_with_no_name. Orable. – PerformanceDBA

+0

@PerformanceDBA: Что означает Orable? –

+0

@a_horse_with_no_name. 1) это база данных, которую вы получаете, когда вы снимаете пластик с пакета термоусадочной упаковки, который был помечен как Oracle. 2) вы предоставили функции Orable (нестандартный SQL), которые могут не существовать для PostgreSQL, вопрос OP. – PerformanceDBA

0

Гм, вы не просто имели в виду:

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; 

делать ты? Одна строка для каждого имени возвращает второе наибольшее значение для имени (или нет строки, если нет второго наивысшего значения).

Если это то, чего вы хотите, в вашем запросе просто отсутствовало ограничение, хотя я подозреваю, что вышеупомянутое, вероятно, это два сканирования таблицы, если PostgreSQL имеет смысл преобразовать его в JOIN.

10

Вот еще одно концептуально простое решение, которое работает для меня в .1 миллисекундах на столе из 21 миллиона строк, в соответствии с EXPLAIN ANALYZE. Он ничего не возвращает в случае, когда есть только одно значение.

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

Обратите внимание, что мой стол уже существующие индексы по имени, слова и (имя, слово), что позволяет мне использовать ORDER BY, как это.

+0

Это отлично работает, если индексы на месте. В настоящее время выбираем 2000 строк из таблицы 3 миллиона. – AgDude