Как я могу проверить определенное значение во всех агрегированных строках? - PullRequest
2 голосов
/ 17 сентября 2010

Предположим, у меня есть три таблицы: user, group и xref, таблица, которая дает им RI "многие ко многим".

Возможно, я хочу посмотреть, к каким группам принадлежит каждый пользователь:

select
    user.user_id,
    user.user_name,
    count(*) as group_count
from
    user
        inner join xref on user.user_id = xref.user_id
        inner join group on group.group_id = xref.group_id
group by user.user_id, user.user_name

Пока все в порядке. Но что, если я хочу получить дополнительную информацию? Я пишу отчет и хочу узнать, является ли каждый пользователь разработчиком или менеджером контента. Теперь появляется анти-паттерн:

select
    user.user_id,
    user.user_name,
    count(*) as group_count,
    max( case group.group_name when 'Developers' then 'Y' else null end )
        as is_dev
    max( case group.group_name when 'Content Management' then 'Y' else null end )
        as is_cm
from
    user
        inner join xref on user.user_id = xref.user_id
        inner join group on group.group_id = xref.group_id
group by user.user_id, user.user_name

Это работает и дает ожидаемые результаты, но кажется, что это неправильно. Что я хочу спросить у Оракула, так это:

«Для каждого пользователя покажите мне, во сколько групп он входит. Кроме того, для всех имен групп на пользователя укажите, является ли« Разработчики »одним из значений».

Что я на самом деле спрашиваю так:

"Для каждого пользователя покажите мне, сколько групп в нем. Кроме того, для всех имен групп на пользователя покажите мне наибольшее значение, полученное этим выражением case."

Причина того, что это анти-паттерн, заключается в том, что я в основном полагаюсь на тот факт, что Y происходит , чтобы "всплыть" выше null при оценке с помощью max(). Если кто-то захочет скопировать или дополнить этот запрос, он может легко забыть об анти-паттерне и случайно изменить возвращаемые значения на то, что не использует такое же неинтуитивное совпадение.

По сути, я бы хотел написать следующий запрос:

select
    user.user_id,
    user.user_name,
    count(*) as group_count,
    any(group.group_name, 'Developers', 'Y', null) as is_dev,
    any(group.group_name, 'Content Management', 'Y', null) as is_cm
from
    user
        inner join xref on user.user_id = xref.user_id
        inner join group on group.group_id = xref.group_id
group by user.user_id, user.user_name

Я искал варианты, и кажется, что есть несколько потенциалов:

  • first_value может работать, но я не могу понять, как ограничить соответствующее окно partition правыми строками.
  • Аналитические функции с предложением over могут работать, но я делаю хочу свернуть столбцы, по которым я группируюсь, так что это не совсем подходит.
  • К сожалению, похоже, что any здесь задокументирована функция , но она существует только на таинственном диалекте, называемом Oracle OLAP DML, к которому я не могу получить доступ, используя только SQL на 10г. Но, кажется, он делает точно , что я хочу.

Это все, что я получил. Есть идеи?

Я понимаю, что есть две очень простые идеи: «Сделай это в коде» или «Сделай это в PL / SQL», но это обман. : -)

Ответы [ 3 ]

3 голосов
/ 17 сентября 2010

Я бы переключился с MAX на SUM (с 1, а не Y), так что вы говорите: «Подсчитайте количество групп, в которых этот человек находится, где имя группы -« Разработчики »».

Тогда шаблон аналогичен «посчитать количество продаж, где стоимость покупки была более $ 30».

Вы можете, если хотите, добавить другое выражение, чтобы сказать «Если счет больше нуля, тогда« да », этот человек является разработчиком». Очень явно и, вероятно, не нужно.

2 голосов
/ 17 сентября 2010
SELECT  user.user_id,
        user.user_name,
        COUNT(*) group_count,
        COUNT(DISTINCT DECODE(group_name, 'Developers', 'Y', NULL)) AS is_developer
        COUNT(DISTINCT DECODE(group_name, 'Content Management', 'Y', NULL)) AS is_content_manager
FROM    the_query

Что касается ANY, это предикат, похожий на IN, а не функция:

SELECT  *
FROM    dual
WHERE   'baz' = ANY('foo', 'bar', 'baz')
0 голосов
/ 17 сентября 2010

Я предпочитаю ответ Гэри , но если вы хотите придерживаться логического возврата, вы можете сделать порядок более явным, возвращая 'N' вместо нуля.

select
    user.user_id,
    user.user_name,
    count(*) as group_count,
    max( case group.group_name when 'Developers' then 'Y' else 'N' end )
        as is_dev
    max( case group.group_name when 'Content Management' then 'Y' else 'N' end )
        as is_cm
from
    user
        inner join xref on user.user_id = xref.user_id
        inner join group on group.group_id = xref.group_id
group by user.user_id, user.user_name

(+1 для хорошо написанного вопроса)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...