Агрегатные функции и сортировка SQL

#sql #oracle #aggregate

#sql #Oracle #агрегат

Вопрос:

Я все еще новичок в SQL и разбираюсь во всей агрегации вложенных запросов, чтобы отобразить некоторые результаты, и искал несколько советов:

Таблицы могут выглядеть примерно так:

 Customer: (custID,  name, address)
Account: (accountID, reward_balance)
Shop: (shopID, name, address)
  

Реляционные таблицы:

 Holds (custID*, accountID*)
With (accountID*, shopID*)
  

Как я могу найти хранилище, которое имеет наименьший reward_balance?
(Информация о клиенте на данный момент не требуется)

Я пытался:

 SELECT accountID AS ACCOUNT_ID, shopID AS SHOP_ID, MIN(reward_balance) AS LOWEST_BALANCE
FROM Account, Shop, With
WHERE With.accountID = Account.accountID
AND With.shopID=Shop.shopID
GROUP BY
Account.accountID,
Shop.shopID
ORDER BY MIN(reward_balance);
  

Это работает так, как не предназначено:

 ACCOUNT_ID | SHOP_ID | LOWEST_BALANCE
 1         |   1     |   10
 2         |   2     |   40
 3         |   3     |   100
 4         |   4     |   1000
 5         |   4     |   5000
  

Как вы можете видеть, Shop_ID 4 на самом деле имеет баланс 6000 (1000 5000), поскольку в нем зарегистрированы два клиента. Я думаю, мне нужно суммировать наименьший баланс магазинов на основе их баланса и отображать его с низкого на высокий.

Я пытался объединить данные перед отображением, но здесь я отклеился:

 SELECT shopID AS SHOP_ID, MIN(reward_balance) AS LOWEST_BALANCE
FROM (SELECT accountID, shopID, SUM(reward_balance) 
     FROM Account, Shop, With
     WHERE 
         With.accountID = Account.accountID
         AND With.shopID=Shop.shopID
         GROUP BY
             Account.accountID,
             Shop.shopID;
  

Когда я запускаю что-то вроде этого оператора, я получаю ошибку недопустимого идентификатора.

 Error at Command Line : 1 Column : 24
Error report -
SQL Error: ORA-00904: "REWARD_BALANCE": invalid identifier
00904. 00000 -  "%s: invalid identifier"
  

Итак, я подумал, что, возможно, мое условие соединения неверно, а агрегированная сортировка неверна, и был бы очень признателен за любые общие советы.

Спасибо за долгое чтение!

Комментарии:

1. Попробуйте добавить псевдоним SUM(reward_balance) As reward_balance внутри вашего внутреннего выбора.

2. Привет, Uporabnik003, я отредактировал его на основе вашего предложения, но теперь я получаю ошибку «столбец неоднозначно определен».

3. Я не знаком с Oracle, но попробуйте назвать его как sum_reward_balance , а затем исправить основное утверждение. Возможно, ему нужно уникальное имя, а не то же самое, что и исходное имя столбца.

Ответ №1:

Подходите к этой проблеме по одному шагу за раз.

Мы собираемся предположить (и нам, вероятно, следует это проверить), что по наименьшему reward_balance это относится к сумме всех reward_balance, связанных с магазином. И мы не просто ищем магазин с наименьшим индивидуальным балансом вознаграждений.

Сначала получите все отдельные «reward_balance» для каждого магазина. Похоже, что запрос должен включать три таблицы…

 SELECT s.shop_id
     , a.reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id
  

Это даст нам подробные строки для каждого магазина вместе с отдельными суммами reward_balance, связанными с магазином, если таковые имеются. (Мы используем внешние соединения для этого запроса, потому что мы не видим никакой гарантии, что магазины будут связаны хотя бы с одной учетной записью. Даже если это верно для данного варианта использования, это не всегда верно в более общем случае.)

Как только у нас будут отдельные суммы, следующим шагом будет их суммирование для каждого магазина. Мы можем сделать это, используя GROUP BY предложение и SUM() агрегат.

 SELECT s.shop_id
     , SUM(a.reward_balance) AS tot_reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id
 GROUP BY s.shop_id
  

На этом этапе с помощью MySQL мы могли бы добавить ORDER BY предложение для упорядочивания строк в порядке возрастания tot_reward_balance и добавить LIMIT 1 предложение, если мы хотим вернуть только одну строку. Мы также можем обработать случай, когда tot_reward_balance равно НУЛЮ, назначив ноль вместо нуля.

 SELECT s.shop_id
     , IFNULL(SUM(a.reward_balance),0) AS tot_reward_balance
  FROM `shop` s
  LEFT
  JOIN `with` w
    ON w.shop_id = s.shop_id 
  LEFT
  JOIN `account` a
    ON a.account_id = w.account_id
 GROUP BY s.shop_id
 ORDER BY tot_reward_amount ASC, s.shop_id ASC
 LIMIT 1
  

Если есть два (или более) магазина с одинаковым наименьшим значением tot_reward_amount , этот запрос возвращает только один из этих магазинов.

В Oracle нет предложения LIMIT, подобного MySQL, но мы можем получить эквивалентный результат, используя аналитическую функцию (которая недоступна в MySQL). Мы также заменяем функцию MySQL IFNULL() эквивалентной функцией Oracle NVL()…

 SELECT v.shop_id
     , v.tot_reward_balance
     , ROW_NUMBER() OVER (ORDER BY v.tot_reward_balance ASC, v.shop_id ASC) AS rn
  FROM ( 
         SELECT s.shop_id
              , NVL(SUM(a.reward_balance),0) AS tot_reward_balance
           FROM shop s
           LEFT
           JOIN with w
             ON w.shop_id = s.shop_id 
           LEFT
           JOIN account a
             ON a.account_id = w.account_id
          GROUP BY s.shop_id
       ) v
HAVING rn = 1
  

Как и запрос MySQL, это возвращает не более одной строки, даже если два или более магазинов имеют одинаковую «наименьшую» сумму reward_balance .

Если мы хотим вернуть все магазины с наименьшим tot_reward_balance, нам нужно использовать несколько иной подход.


Лучший подход к построению запросов — поэтапное уточнение; в этом случае начните с получения всех отдельных значений reward_amount для каждого магазина. Следующим шагом является объединение отдельного значения reward_amount в итоговое значение. Следующие шаги — выбрать строку (строки) с наименьшим общим вознаграждением_amount.

Комментарии:

1. Спасибо за замечательный совет spencer7593. Я действительно ценю время, затраченное на демонстрацию пошагового построения и включение возможностей нулевых объектов и т. Д.

2. @Ben: Другие люди могут одним махом создавать многострочные, многоступенчатые операторы SQL. Я не настолько умен. Я должен делать это шаг за шагом и проверять каждый шаг на этом пути. «Итеративная разработка запросов» (как я это называю) — это подход, который я использую, и он работает для меня. Я думаю, что это позволяет мне выявлять проблемы и исправлять их на этом пути. Когда я сталкиваюсь с проблемой, я могу сделать шаг назад и решить ее. (Я недостаточно умен, чтобы отлаживать «почему этот огромный оператор SQL не возвращает ожидаемый результат» без его обратного проектирования. Я просто «перестраиваю» свои SQL-операторы. YMMV.

3. Форматирование моих операторов SQL может показаться вам странным. Это формат, который работает для меня. Например. Я могу легко выделить запрос встроенного представления в своей IDE и выполнить именно его. Я легко вижу, что запрос встроенного представления возвращает два столбца. Оптимизатор SQL не заботится о форматировании, его не волнуют дополнительные разрывы, лишние пробелы, выравнивание скобок. Я форматирую оператор таким образом, чтобы я мог расшифровать его, когда мне нужно будет вернуться к нему.

Ответ №2:

В SQL Server вы можете попробовать использовать CTE:

   ;with cte_minvalue as
    (
     select rank() over (order by Sum_Balance) as RowRank,
     ShopId,
     Sum_Balance
      from (SELECT Shop.shopID, SUM(reward_balance) AS Sum_Balance 
         FROM 
             With
             JOIN Shop ON With.ShopId = Shop.ShopId
             JOIN Account ON With.AccountId = Account.AccountId
         GROUP BY
            Shop.shopID)ShopSum
      )
      select ShopId, Sum_Balance from cte_minvalue where RowRank = 1
  

Комментарии:

1. Привет, Амит, спасибо за помощь, извините, я новичок в stack overflow и не заметил автоматической пометки, но я использую Oracle, а не SQL server, извините. Теперь я удалил неверный тег.