Вычисление совокупных процентов в SQL

У меня есть эта таблица (myt):

CREATE TABLE myt (
  name VARCHAR(50),
  food VARCHAR(50),
  d1 INT
);

INSERT INTO myt (name, food, d1) VALUES
('john', 'pizza', 2010),
('john', 'pizza', 2011),
('john', 'cake', 2012),
('tim', 'apples', 2015),
('david', 'apples', 2020),
('david', 'apples', 2021),
('alex', 'cookies', 2005),
('alex', 'cookies', 2006);

  name    food   d1 food_year
  john   pizza 2010      2010
  john   pizza 2011      2011
  john    cake 2012      2012
   tim  apples 2015      2015
 david  apples 2020      2020
 david  apples 2021      2021
  alex cookies 2005      2005
  alex cookies 2006      2006

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

WITH FoodCounts AS (
    SELECT name, 
           food, 
           COUNT(*) as food_count
    FROM myt
    GROUP BY name, food
),
TotalCounts AS (
    SELECT name, 
           COUNT(*) as total_count
    FROM myt
    GROUP BY name
)
SELECT fc.name, 
       fc.food, 
       (fc.food_count * 100.0) / tc.total_count as percentage
FROM FoodCounts fc
JOIN TotalCounts tc
ON fc.name = tc.name;


  name    food percentage
  alex cookies  100.00000
 david  apples  100.00000
  john    cake   33.33333
  john   pizza   66.66667
   tim  apples  100.00000

Сейчас я пытаюсь изменить этот запрос, чтобы узнать совокупные проценты. Например, по состоянию на 2011 год: каков был график питания Джона? Каков был график питания Джона по состоянию на 2012 год?

Чтобы ответить на этот вопрос, я попытался написать серию CTE, используя оконные функции:

WITH YearlyFoodCounts AS (
    SELECT name, 
           food, 
           food_year,
           COUNT(*) as food_count
    FROM myt
    GROUP BY name, food, food_year
),
CumulativeCounts AS (
    SELECT name, 
           food_year,
           SUM(food_count) OVER (PARTITION BY name ORDER BY food_year) as cumulative_count
    FROM YearlyFoodCounts
)
SELECT yfc.name, 
       yfc.food, 
       yfc.food_year,
       yfc.food_count,
       cc.cumulative_count,
       (yfc.food_count * 100.0) / cc.cumulative_count as percentage
FROM YearlyFoodCounts yfc
JOIN CumulativeCounts cc
ON yfc.name = cc.name AND yfc.food_year = cc.food_year
ORDER BY yfc.name, yfc.food_year;

Результат выглядит в правильном формате:

 name    food food_year food_count cumulative_count percentage
  alex cookies      2005          1                1  100.00000
  alex cookies      2006          1                2   50.00000
 david  apples      2020          1                1  100.00000
 david  apples      2021          1                2   50.00000
  john   pizza      2010          1                1  100.00000
  john   pizza      2011          1                2   50.00000
  john    cake      2012          1                3   33.33333
   tim  apples      2015          1                1  100.00000

Это правильный подход к этой проблеме?

Что произойдет, если на каждый food приходится несколько разных name, food_year? Кажется, ваш запрос будет неправильным и/или недетерминированным.

Charlieface 18.06.2024 13:58

@ Charlieface: спасибо за ответ. Давайте предположим, что невозможно иметь несколько разных блюд на одно имя, food_year. В таком случае, правильный ли мой запрос?

stats_noob 18.06.2024 14:07

не могли бы вы показать мне, как адаптировать запрос к этой ситуации, которую вы описали?

stats_noob 18.06.2024 14:07

Не совсем потому, что трудно понять, чего ты хочешь. Как рассчитать процент, если их несколько на name, food_year? Сделайте это вручную и расскажите мне, какие шаги вы сделали.

Charlieface 18.06.2024 14:13
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
4
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы это слишком усложняете. Ему не нужны соединения или подзапросы, вы можете сделать это на одном уровне, используя оконные функции. Вы можете поместить обычный агрегат внутри оконной функции, поскольку оконные функции выполняются после обычного агрегирования.

Примечание:

  • Используйте ROWS UNBOUNDED PRECEDING, потому что по умолчанию используется RANGE UNBOUNDED PRECEDING, что немного отличается.
  • Вы говорите, что в каждой паре может быть только один food. Поэтому вам следует агрегировать только по этим двум столбцам.
SELECT
    name, 
    MIN(food) AS food, 
    food_year,
    COUNT(*) as food_count,
    SUM(COUNT(*)) OVER (PARTITION BY name ORDER BY food_year ROWS UNBOUNDED PRECEDING) as cumulative_count,
    COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (PARTITION BY name ORDER BY food_year ROWS UNBOUNDED PRECEDING) as percentage
FROM myt
GROUP BY
    name,
    food_year;

db<>рабочий пример

Другие вопросы по теме