из каждого раздела выберите строки с N различными значениями

У меня есть таблица с тремя столбцами x, y, z. Я хотел бы написать запрос, который внутри каждого PARTITION BY x возвращает строки, содержащие первые n различных значений y.

Вот пример для n = 2 — первые два различных значения y в первом разделе — это 1 и 2, а во втором разделе — 4 и 5, поэтому включены все строки с этими значениями y.

 x   y   z   included?
----------------------
 1   1   1    true
 1   1   2    true
 1   2   3    true
 1   2   4    true
 1   3   5    false
 1   3   6    false
 2   4   7    true
 2   4   8    true
 2   5   9    true
 2   5  10    true
 2   6  11    false
 2   6  12    false

Существует связанный вопрос, который касается выбора n строк из каждого раздела, но не касается части отдельных значений.


person rcrogers    schedule 30.09.2014    source источник


Ответы (2)


Я не уверен, что вы имеете в виду под «первым». Таблицы SQL представляют собой неупорядоченные наборы. Итак, я предполагаю, что вы имеете в виду «наименьший».

Вы можете сделать это, используя dense_rank():

select t.*
from (select t.*, dense_rank() over (partition by x order by y) as seqnum
      from atable t
     ) t
where seqnum <= 2;
person Gordon Linoff    schedule 30.09.2014
comment
Что, если вместо этого я выбираю из подзапроса, который указывает произвольный порядок, и хочу взять первый n из них? - person rcrogers; 01.10.2014
comment
@rcrogers . . . Вы бы поместили порядок в предложение order by. - person Gordon Linoff; 01.10.2014
comment
Есть ли способ для внешнего запроса соблюдать порядок внутреннего запроса без указания внешнего запроса? - person rcrogers; 01.10.2014
comment
@rcrogers . . . Нет. Если вы хотите получить набор результатов в определенном порядке, вам следует использовать order by на самом внешнем уровне. - person Gordon Linoff; 01.10.2014

Вы можете использовать комбинацию desnse_rank и row_number для устранения дубликатов:

with a as (
  select
    x, y, z,
    dense_rank() over (partition by x order by y) rk,
    row_number() over (partition by x, y order by z) rn
  from
    t
) select
  x, y, z
from
  a
where
  rk <= 2 and
  rn = 1;

это генерирует 1, 2, 4, 5

из этого вы можете получить желаемые результаты, присоединившись к t:

with a as (
  select
    x, y, z,
    dense_rank() over (partition by x order by y) rk,
    row_number() over (partition by x, y order by z) rn
  from
    t
) select
  t.*
from
  t
where
  exists (
    select
      'x'
    from
      a
    where
      a.y = t.y and
      a.rk <= 2 and
      a.rn = 1
  );

Пример SQLFiddle

Хотя использование exists таким образом делает дубликаты неактуальными, поэтому вы можете просто сделать:

with a as (
  select
    x, y, z,
    dense_rank() over (partition by x order by y) rk
  from
    t
) select
  t.*
from
  t
where
  exists (
    select
      'x'
    from
      a
    where
      a.y = t.y and
      a.rk <= 2
  );

Пример SQLFiddle

person Laurence    schedule 30.09.2014