Как я могу упростить этот SQL-запрос в Oracle? - PullRequest
0 голосов
/ 05 января 2011

После небольшого поиска и прочтения я придумал следующий запрос SQL для моего приложения:

SELECT
  ROUND(AVG(CASE WHEN gender = 'M' THEN rating END), 1) avgAllM,
  COUNT(CASE WHEN gender = 'M' THEN rating END) countAllM,
  ROUND(AVG(CASE WHEN gender = 'F' THEN rating END), 1) avgAllF,
  COUNT(CASE WHEN gender = 'F' THEN rating END) countAllF,
  ROUND(AVG(CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END), 1) avgU18M,
  COUNT(CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END) countU18M,
  ROUND(AVG(CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END), 1) avgU18F,
  COUNT(CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END) countU18F
FROM movie_ratings mr INNER JOIN accounts a
  ON mr.aid = a.aid
WHERE mid = 5;

И мне интересно, как можно упростить это, если это возможно.Поле birth_date имеет тип DATE, а UserAge - это функция для расчета возраста по этому полю даты.

Структуры таблицы следующие:

[ACCOUNTS]
aid(PK), birth_date, gender

[MOVIE_RATINGS]
mid(PK), aid(PK,FK), rating

IЯ ищу две вещи:

  • Общие упрощения приведенного выше кода, о которых более опытные пользователи знают об этом, а я - нет.запись У меня будет ассоциативный массив со всеми этими переменными.Я ищу способ сгруппировать их в многомерный массив, чтобы облегчить чтение кода PHP.Конечно, я не хочу делать это в самом PHP, это было бы бессмысленно.

Например, что-то вроде этого:

$info[0]['avgAllM']
$info[0]['countAllM']
$info[1]['avgAllF']
$info[1]['countAllF']
$info[2]['avgU18M']
$info[2]['countU18M']
$info[3]['avgU18F']
$info[3]['countU18F']

Вместо:

$info['avgAllM']
$info['countAllM']
$info['avgAllF']
$info['countAllF']
$info['avgU18M']
$info['countU18M']
$info['avgU18F']
$info['countU18F']

Я даже не знаю, возможно ли это, поэтому мне действительно интересно, так ли это и как это можно сделать.

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

Ответы [ 2 ]

3 голосов
/ 05 января 2011

Вы можете создать VIEW со следующим определением

SELECT
      CASE WHEN gender = 'M' THEN rating END AS AllM,
      CASE WHEN gender = 'F' THEN rating END AS AllF,
      CASE WHEN gender = 'M' AND UserAge(birth_date) <= 18 THEN rating END AS U18M,
      CASE WHEN gender = 'F' AND UserAge(birth_date) <= 18 THEN rating END AS U18F
      FROM movie_ratings mr INNER JOIN accounts a
        ON mr.aid = a.aid
      WHERE mid = 5

Затем ВЫБЕРИТЕ из этого

SELECT ROUND(AVG(AllM), 1) avgAllM,
       COUNT(AllM)         countAllM,
       ROUND(AVG(AllF), 1) avg,
       COUNT(AllF)         countAllF,
       ROUND(AVG(U18M), 1) avgU18M,
       COUNT(U18M)         countU18M,
       ROUND(AVG(U18F), 1) avgU18F,
       COUNT(U18F)         countU18F
FROM  yourview

Может немного упростить вещи?

0 голосов
/ 05 января 2011

Это может быть просто случай оптимизации слишком рано. Запрос делает то, что вам нужно, и на самом деле выглядит сложным только потому, что это так. Я не уверен, что есть какие-то хитрости, которые могли бы помочь. Вероятно, это зависит от характеристик ваших данных. Запрос медленный? Как вы думаете, это может быть быстрее?

Возможно, стоит изменить его следующим образом. Поскольку все условия основаны на таблице ACCOUNTS, которая, как я предполагаю, будет значительно меньше, чем таблица MOVIE_RATINGS, вы можете выполнить все вычисления на меньшем наборе данных, что может быть быстрее. Хотя, если вы выбираете только один фильм за раз (mid = 5), то, вероятно, это не так.

Я не совсем уверен, что это сработает, но думаю, что должно.

SELECT
  ROUND(AVG(rating * AllM), 1) avgAllM,
  COUNT(rating * AllM) countAllM,
  ROUND(AVG(rating * AllF), 1) avgAllF,
  COUNT(rating * AllF) countAllF,
  ROUND(AVG(rating * AllM * U18), 1) avgU18M,
  COUNT(rating * AllM * U18) countU18M,
  ROUND(AVG(rating * AllM * U18), 1) avgU18F,
  COUNT(rating * AllM * U18) countU18F
FROM 
  movie_ratings mr 
  INNER JOIN (
    select 
      aid,
      case when gender = 'M' then 1 end as AllM,
      case when gender = 'F' then 1 end as AllF,
      case when UserAge(birth_date) <= 18 then 1 end as U18
    from accounts) a ON mr.aid = a.aid
WHERE mid = 5;

В итоге, я бы, вероятно, просто оставил ваш запрос таким, какой он есть. Ваш запрос прост для понимания и, вероятно, выполняется довольно хорошо.

...