Попытка найти второе по величине значение в столбце (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;
По какой-то причине то, что у меня теперь, возвращает второе наибольшее значение, а все более низкие значения также, но, к счастью, избегают наибольшего значения.
Есть ли способ исправить это?
Ответы
Ответ 1
Вот еще одно концептуально простое решение, которое выполнялось для меня в .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 таким образом.
Ответ 2
Самый простой, хотя и неэффективный (массив может исчерпывать память):
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 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 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 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 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)
ИЗМЕНИТЬ
@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;
ИЗМЕНИТЬ
Может быть самым простым и эффективным из всех, если только 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;
Ответ 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
Ответ 4
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)
Edit:
name_count = 1
заботится о тех случаях, когда для определенного имени присутствует только одна строка.
Используя dense_rank()
вместо rank()
, убедитесь, что есть строка с word_rank = 2, поскольку dense_rank гарантирует отсутствие пробелов
Ответ 5
Очень грубый запрос, но он работает
select a.name, a.word
from apple a
where (select count(distinct b.word) from apple b
where b.word > a.word) = 1
Ответ 6
Другой подход, используйте 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
)
Ответ 7
Умм, вы не просто имеете в виду:
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.