16  Агрегація даних

Data Miorsh Ihor Miroshnychenko Youtube Monobank

16.1 Унікальні значення: DISTINCT

Ключове слово DISTINCT дає змогу відбирати унікальні записи, тобто позбавлятися всіх дублікатів у таблиці.

Вказується DISTINCT одразу після SELECT. Для виведення унікальних значень в одній колонці можна скласти наступний запит:

SELECT DISTINCT column
FROM table

Давайте спершу вирішимо просте завдання.

Завдання 16.1
Виведіть ID всіх унікальних користувачів з таблиці user_actions. Результат відсортуйте за зростанням id.

Поле у результуючій таблиці: user_id

Рішення
%%sql
SELECT DISTINCT user_id
FROM   user_actions
ORDER BY user_id
user_id
0 1
1 2
2 3
3 4
4 5
... ...
21396 21398
21397 21399
21398 21400
21399 21401
21400 21402

21401 rows × 1 columns

Ключове слово DISTINCT можна застосовувати не тільки до однієї колонки, а й одразу до кількох.

Наприклад, запит для двох колонок виглядатиме так:

SELECT DISTINCT column_1, column_2
FROM table

У такому випадку запит поверне унікальні комбінації значень в колонках.

Завдання 16.2
Застосуйте DISTINCT відразу до двох колонок таблиці courier_actions та відберіть унікальні пари значень courier_id та order_id.

Результат відсортуйте спочатку за зростанням id кур’єра, потім за зростанням id замовлення.

Поля у результуючій таблиці: courier_id, order_id

Рішення
%%sql
SELECT DISTINCT courier_id,
                order_id
FROM   courier_actions
ORDER BY courier_id, order_id
courier_id order_id
0 1 98
1 1 194
2 1 233
3 1 522
4 1 1251
... ... ...
59590 3166 56165
59591 3166 56940
59592 3167 56176
59593 3167 58039
59594 3167 58774

59595 rows × 2 columns

16.2 Базова агрегація: COUNT, SUM, AVG, MIN, MAX

Агрегатними функціями називають функції, які обробляють певний набір спостережень і повертають одне узагальнююче значення. Якщо ви коли-небудь працювали в Excel, то, напевно, стикалися з підрахунком суми або максимального/мінімального значення по стовпцю — йдеться саме про це.

Ось кілька прикладів таких функцій у SQL:

  • COUNT: повертає кількість значень у колонці;
  • SUM: обчислює суму значень;
  • AVG: обчислює середнє значення;
  • MAX: обчислює максимальне значення;
  • MIN: обчислює мінімальне значення.

Приклад:

SELECT COUNT(column) AS count
FROM table


SELECT SUM(column_1) AS sum,
       AVG(column_2) AS average
FROM table

Зверніть увагу, що деякі з вищезгаданих функцій не можна застосовувати до колонок з текстом, датами та часом, оскільки не цілком зрозуміло, що, наприклад, означає знайти середнє значення або суму найменувань товарів.

У той самий час «максимальне» найменування товару обчислити можна — функція MAX шукатиме найбільше значення упорядкованої послідовності (відповідно до встановлених правил сортування значень рядкового типу).

Втім, заучувати межі застосування цих функцій не потрібно — просто керуйтеся здоровим глуздом.

Примітка

Докладніше про агрегатні функції в DuckDB можна ознайомитися тут.

Завдання 16.3
Порахуйте максимальну та мінімальну ціни товарів у таблиці products. Поля назвіть відповідно max_price, min_price.

Поля у результуючій таблиці: max_price, min_price

Пояснення: в одному запиті можна використовувати відразу кілька агрегаційних функцій — якщо необхідно, то навіть по кількох різних колонках.

Рішення
%%sql
SELECT max(price) as max_price,
       min(price) as min_price
FROM   products
max_price min_price
0 800.0 1.0

16.3 COUNT(*) vs COUNT(column)

Також при підрахунку кількості записів іноді замість найменування колонки як атрибут функції COUNT використовують зірочку «*»:

SELECT COUNT(*)
FROM table

Однак важливо враховувати один нюанс: запит із зірочкою повертає кількість взагалі всіх записів у таблиці, а запит із зазначенням стовпця — кількість тих записів, де в заданому стовпці значення не є NULL.

Таким чином, якщо в деякій колонці column є перепустки, вирази COUNT(*) і COUNT(column) повернуть різні значення.

Давайте перевіримо!

Завдання 16.4
Як ви пам’ятаєте, у таблиці users в деяких користувачів не було зазначено їх дати народження.

Порахуйте в одному запиті кількість усіх записів у таблиці та кількість лише тих записів, для яких у колонці birth_date вказана дата народження.

Колонку із загальним числом записів назвіть dates, а колонку із записами без перепусток — dates_not_null.

Поля у результуючій таблиці: dates, dates_not_null

Рішення
%%sql
SELECT count(*) as dates,
       count(birth_date) as dates_not_null
FROM   users
dates dates_not_null
0 20331 20281

І ще один важливий момент: агрегатні функції можна застосовувати в поєднанні з ключовим словом DISTINCT. У такому випадку розрахунки будуть проводитися лише за унікальними значеннями.

Якщо у випадку з MIN та MAX це не має особливого сенсу, то при розрахунку AVG, SUM та COUNT іноді це буває корисно:

SELECT SUM(DISTINCT column) AS sum_distinct
FROM table

При цьому досить часто DISTINCT використовується саме в поєднанні з COUNT для підрахунку числа унікальних користувачів, унікальних замовлень тощо.

SELECT COUNT(DISTINCT column) AS count_distinct
FROM table

Завдання 16.5
Порахуйте кількість всіх значень у колонці user_id у таблиці user_actions, а також кількість унікальних значень у цій колонці (тобто кількість унікальних користувачів сервісу).

Колонку з першим отриманим значенням назвіть users, а колонку з другим – unique_users.

Поля у результуючій таблиці: users, unique_users

Рішення
%%sql
SELECT count(user_id) as users,
       count(distinct user_id) as unique_users
FROM   user_actions
users unique_users
0 62574 21401

16.4 Фільтрація та агрегація

А якщо для розрахунків нам потрібні не всі дані в стовпці, а тільки якась частина? Тоді в запит з агрегатними функціями можна включити оператор WHERE, вказавши умову для відбору записів:

SELECT COUNT(column_1) AS count
FROM table
WHERE column_2 > 100

У такому випадку спочатку буде виконана фільтрація таблиці і тільки потім буде проведена агрегація по записах, що залишилися.

Про всяк випадок нагадаємо порядок виконання у запиті відомих нам ключових операторів:

SELECT      -- перерахування полів результуючої таблиці
FROM        -- вказівка джерела даних
WHERE       -- фільтрація даних
ORDER BY    -- сортування результуючої таблиці
LIMIT       -- обмеження кількості записів, що виводяться

Завдання 16.6
Порахуйте кількість кур’єрів жіночої статі у таблиці couriers. Отриманий стовпець з одним значенням назвіть couriers.

Поле в результуючій таблиці: couriers

Рішення
%%sql
SELECT count(distinct courier_id) as couriers
FROM   couriers
WHERE  sex = 'female'
couriers
0 1149

І ще одне завдання на агрегацію із фільтрацією. На цей раз проведемо розрахунки по колонці з даними у форматі TIMESTAMP.

Завдання 16.7
Розрахуйте час, коли було здійснено першу та останню доставку замовлень у таблиці courier_actions.

Колонку з часом першої доставки назвіть first_delivery, а колонку з часом останньої – last_delivery.

Поля у результуючій таблиці: first_delivery, last_delivery

Пояснення: зауважте, що в таблиці з діями кур’єрів є не тільки записи з часом доставки замовлень, але й записи з часом їх прийняття.

Рішення
%%sql
SELECT min(time) as first_delivery,
       max(time) as last_delivery
FROM   courier_actions
WHERE  action = 'deliver_order'
first_delivery last_delivery
0 2022-08-24 02:15:00 2022-09-08 23:59:00

У поєднанні з агрегатними функціями можна використовувати ті ж умовні конструкції, які ми розглядали раніше.

Давайте додамо до нашого запиту більш цікаву фільтрацію.

Завдання 16.8
Уявіть, що один із користувачів сервісу зробив замовлення, до якого увійшли одна пачка крекерів (crackers), одна пачка чіпсів (chips) та один енергетичний напій (energy drink). Порахуйте вартість такого замовлення.

Колонку із розрахованою вартістю замовлення назвіть order_price.

Для розрахунків використовуйте таблицю products.

Поле в результуючій таблиці: order_price

Для вирішення завдання необхідно «сформувати» описане замовлення, відібравши з таблиці products зазначені товари та порахувавши їхню сумарну вартість.

Рішення
%%sql
SELECT sum(price) as order_price
FROM   products
WHERE  name in ('crackers', 'chips', 'energy drink')
order_price
0 235.0

16.5 Довжина списку: ARRAY_LENGTH

Як пам’ятаєте, у таблиці orders вміст замовлень представлено вигляді списків товарів (масивів). Щоб порахувати кількість товарів у кожному замовленні, можна скористатися функцією array_length.

Функція array_length обчислює кількість елементів у масиві (довжину масиву) і записується так:

SELECT array_length(ARRAY['first', 'second', 'third'], 1)

Результат:
3

Синтаксис може здатися вам трохи складним, але це лише на перший погляд! Давайте розбиратися.

ARRAY['first', 'second', 'third'] – це деякий список із трьох значень: 'first', 'second', 'third’.

Одиниця як другий аргумент — це розмірність масиву, за якій рахується його довжина. Так як список у нас одновимірний (просто значення, записані в один рядок), то вибір у нас невеликий – можемо вказати лише першу розмірність.

Якби в нас була таблиця N x N, в якій були б і рядки, і стовпці, то розмірності було б дві: перша відповідала кількості рядків, а друга — числу стовпців. У такому випадку ми могли б вказати або першу, або другу розмірність.

Давайте уявімо, що у нас є проста таблиця наступного виду:

 _______
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
 ‾‾‾‾‾‾‾

У цій таблиці 3 рядки та 2 стовпці, тому її можна описати у вигляді наступного списку з трьох вкладених у нього списків:

ARRAY[[1,2], [3,4], [5,6]]

У прикладі вище кількість списків усередині основного списку — кількість рядків у таблиці, а кількість елементів усередині кожного внутрішнього списку — кількість стовпців.

Зверніть увагу на результат обчислень функції array_length для першої та другої розмірності:

SELECT array_length(ARRAY[[1,2], [3,4], [5,6]], 1)

Результат:
3

SELECT array_length(ARRAY[[1,2], [3,4], [5,6]], 2)

Результат:
2

У це досить складно повірити, але значеннями в основній таблиці справді можуть бути інші таблиці (матриці). На щастя, у нас не такі дані, тому ми працюватимемо із простими однорозмірними списками значень.

В якості аргументу функції array_length на місці масиву можна вказувати ім’я колонки, яка містить масиви (у нашому випадку це колонка product_ids):

SELECT array_length(column, 1)
FROM table
Примітка

Дізнатися більше про функції для роботи з масивами можна у документації. З деякими з них ми познайомимося далі.

Завдання 16.9
Порахуйте кількість замовлень у таблиці orders із дев’ятьма і більше товарами. Для цього скористайтесь функцією array_length, відфільтруйте дані щодо кількості товарів у замовленні та проведіть агрегацію. Отриманий стовпець назвіть orders.

Поле в результуючій таблиці: orders

Пояснення: у наших даних у колонці product_ids містяться звичайні списки товарів лише з однією розмірністю.

Рішення
%%sql
SELECT count(order_id) as orders
FROM   orders
WHERE  array_length(product_ids, 1) >= 9
orders
0 5

16.6 Різниця часу: AGE

В якості агрегатних функцій можуть виступати не тільки стовпці, але також розрахункові колонки або результат іншої функції:

SELECT AVG(some_function(column))
FROM table


SELECT AVG(column_1 + column_2)
FROM table

Спробуємо розрахувати середній вік користувачів чоловічої статі. Для цього скористаємось новою для нас функцією AGE.

Функція AGE повертає різницю між двома значеннями, наведеними у форматі TIMESTAMP. При цьому від першого значення віднімається друге, а сама різниця повертається у форматі INTERVAL:

SELECT AGE('2022-12-12', '2021-11-10')

Результат:
397 days, 0:00:00

Таким чином, з '2021-11-10' до '2022-12-12' пройшло рівно 397 днів.

Якщо в якості першого аргументу нічого не вказати, то на місце першої дати автоматично підставиться поточна дата (опівніч поточного дня, тобто початок дня).

Якщо сьогодні '2022-12-12', то з '2021-11-10' пройшло рівно стільки ж днів, скільки у прикладі вище:

SELECT AGE(TIMESTAMP '2021-11-10')

Результат:
397 days, 0:00:00

Насправді поточній даті відповідає значення current_date, яке можна вказувати як аргумент функції AGE:

SELECT AGE(TIMESTAMP '2021-11-10')

Результат:
397 days, 0:00:00

Можете самостійно запустити два запити - з current_date і без - і порівняти отримані результати. У вашому випадку це будуть нові результати, але вони мають збігтися.

А саме значення current_date можна викликати так:

SELECT current_date

Результат:
2023-06-01  

Для того щоб результат відображався не у кількості днів, а в зручнішому форматі, можна переводити результат обчислень у тип VARCHAR:

SELECT AGE(current_date, '2021-11-10')::VARCHAR

Результат:
1 year 1 mon 2 days

Завдання 16.10
За допомогою функції AGE і агрегатної функції розрахуйте вік наймолодшого кур’єра чоловічої статі в таблиці couriers.

Вік виведіть через кількість років, місяців і днів (як у прикладі вище), перевівши їх у тип VARCHAR.

В якості дати, відносно якої необхідно розрахувати вік кур’єрів, використовуйте свою поточну дату (або не вказуйте її на місці першого аргументу, як показано в прикладах).

Отриману колонку зі значенням віку назвіть min_age.

Поле у результуючій таблиці: min_age

Пояснення: оскільки в якості дати, щодо якої проводиться розрахунок віку, ми використовуємо поточну дату, кожен новий день ми отримуватимемо новий результат.

Зрозуміло, що рахувати вік у такий спосіб не зовсім коректно. Ми навчимося робити це правильно в розділі «Підзапити».

Рішення
%%sql
SELECT age(max(birth_date))::varchar as min_age
FROM   couriers
WHERE  sex = 'male'
min_age
0 17 years 8 months 28 days 13:03:18.214

16.7 Агрегація з CASE WHEN

Аргументом агрегатної функції може бути і складніша розрахункова колонка - наприклад, отримана в результаті роботи конструкції CASE.

У такому випадку сама конструкція CASE поміщається всередину дужок агрегатної функції:

AVG(
    CASE  
    WHEN logical_expression_1 THEN expression_1
    WHEN logical_expression_2 THEN expression_2
    ELSE expression_else
    END
)

Так, якби в нашій таблиці всі товари були розбиті за категоріями і ми захотіли б порахувати середню ціну товарів з урахуванням підвищувальних чи знижувальних коефіцієнтів для кожної категорії, то ми могли б зробити це, наприклад, так:

SELECT AVG(
    CASE 
    WHEN category="м'ясо" THEN price*0.95
    WHEN category="риба" THEN price*0.9
    WHEN category="напої" THEN price*1.05
    ELSE price
    END
    ) AS avg_price
FROM products

Давайте ускладнимо одне з попередніх завдань і розрахуємо вартість ще одного замовлення.

Завдання 16.11
Порахуйте вартість замовлення, в якому будуть три пачки крекерів ('crackers'), дві пачки чіпсів ('chips') та один енергетичний напій ('energy drink'). Колонку із розрахованою вартістю замовлення назвіть order_price.

Для розрахунків використовуйте таблицю products.

Поле в результуючій таблиці: order_price

Для розв’язання задачі необхідно за допомогою конструкції CASE створити розрахункову колонку, в якій навпроти кожного товару, який увійшов у замовлення, проставити ціну, помножену на кількість таких товарів у замовленні. Для товарів, які не увійшли у замовлення, необхідно вказати ціну, що дорівнює 0 або NULL. Потім за допомогою агрегатної функції достатньо порахувати суму значень у новій розрахунковій колонці.

Рішення
%%sql
SELECT sum(case when name = 'crackers' then price * 3
                when name = 'chips' then price * 2
                when name = 'energy drink' then price
                else 0 end) as order_price
FROM   products
order_price
0 355.0

16.8 Агрегатні вирази з фільтрацією

Ми розглянули кілька прикладів, коли в якості агрументу агрегатних функцій виступає результат виконання іншої функції.

Але агрегатна функція може і сама опинитися на місці аргументу якоїсь функції:

SELECT some_function(SUM(column)) AS result
FROM table

Наочний приклад — застосування функції ROUND до результату агрегації:

SELECT ROUND(SUM(column)) AS rounded_sum
FROM table

Давайте вирішимо схоже завдання і заразом ненадовго повернемося до наших напоїв та оператора LIKE.

Завдання 16.12
Розрахуйте середню ціну товарів у таблиці products, у назвах яких є слова 'tea' чи 'coffee'. Виключіть з розрахунку товари, що містять «іван-чай» ('ivan-tea') або «чайний гриб» ('tea mushroom').

Середню ціну округліть до двох знаків після коми. Стовпець із отриманим значенням назвіть avg_price.

Поле у результуючій таблиці: avg_price

Пояснення: пам’ятайте, що виконання логічних виразів можна керувати за допомогою дужок. Це може стати в нагоді.

Рішення
%%sql
SELECT round(avg(price), 2) as avg_price
FROM   products
WHERE  (name like '%tea%'
    or name like '%coffee%')
   and name not like '%ivan-tea%'
   and name not like '%mushroom%'
avg_price
0 135.23

Втім, бувають і випадки, коли як аргументи деякої функції виступають відразу кілька агрегатних функцій:

SELECT some_function(SUM(column_1), SUM(column_2)) AS result
FROM table

Наприклад, нам уже знайома функція AGE, яка може приймати одразу два аргументи - дату кінця та дату початку деякого періоду часу.

Завдання 16.13
Скористайтеся функцією AGE та розрахуйте різницю у віці між найстарішим та наймолодшим користувачами чоловічої статі у таблиці users.

Різницю у віці висловіть кількістю років, місяців та днів, перевівши її у тип VARCHAR.

Назвіть колонку з порахованим значенням age_diff.

Поле у результуючій таблиці: age_diff

Це завдання можна вирішити різними способами: можна просто знайти різницю між датами народження наймолодшого і найстарішого користувачів, а можна порахувати різницю між їх віком. Можете піти будь-яким шляхом. Якщо рахуватимете вік, то в якості першої дати використовуйте свою поточну дату (current_date).

Рішення
%%sql
SELECT age(max(birth_date), min(birth_date))::varchar as age_diff
FROM   users
WHERE  sex = 'male'
age_diff
0 21 years 9 months 27 days

А тепер розглянемо приклад, коли агрегатна функція приймає в якості аргументу одну функцію і при цьому сама є аргументом іншої функції.

Виглядати це може так:

SELECT function_two(SUM(funtion_one(column))) AS result
FROM table

У цьому прикладі спочатку до колонки column застосовується функція funtion_one, потім за допомогою функції SUM буде пораховано суму отриманих значень, і тільки потім до результату агрегації застосовується функція funtion_two.

Завдання 16.14
Розрахуйте середню кількість товарів у замовленнях з таблиці orders, які користувачі оформляли у вихідні дні (субота та неділя) протягом усього часу роботи сервісу.

Отримане значення округліть до двох знаків після коми. Назвіть колонку з ним avg_order_size.

Поле у результуючій таблиці: avg_order_size

Для розрахунку кількості товарів у замовленнях скористайтесь функцією array_length. Для фільтрації таблиці у вихідні дні можна використовувати функцію DATE_PART з параметром 'dow'.

Рішення
%%sql
SELECT round(avg(array_length(product_ids, 1)), 2) as avg_order_size
FROM   orders
WHERE  date_part('dow', creation_time) in (6, 0)
avg_order_size
0 3.39

Результати декількох агрегатних функцій можна використовувати в одному запиті для проведення над ними арифметичних операцій:

SELECT (SUM(column_1) + SUM(column_2)) / 2
FROM table

У запиті вище буде пораховано середнє арифметичне двох сум — значень колонки column_1 і значень колонки column_2.

Завдання 16.15
На основі даних у таблиці user_actions порахуйте: - кількість унікальних користувачів сервісу; - кількість унікальних замовлень - скільки замовлень припадає на одного користувача.

У результуючій таблиці відобразіть усі три значення – поля назвіть відповідно unique_users, unique_orders, orders_per_user.

Показник числа замовлень користувача округліть до двох знаків після коми.

Поля у результуючій таблиці: unique_users, unique_orders, orders_per_user

Важливо

Щоб отримати коректний результат поділу, необхідно хоча б одне із значень попередньо привести до типу DECIMAL (або помножити на 1.0). Інакше результатом розподілу цілого числа інше ціле число буде теж ціле число.

До речі, з подібною проблемою можна ознайомитись на stackoverflow. Це дуже корисний ресурс для пошуку відповідей на свої запитання. Навіть досвідчені програмісти часто ним користуються. Можете вже зараз почати виробляти звичку звертатися до нього за необхідності.

Також пам’ятайте, що використовувати в розрахунках аліаси нових колонок не можна.

Рішення
%%sql
SELECT count(distinct user_id) as unique_users,
       count(distinct order_id) as unique_orders,
       round(count(distinct order_id) * 1.0 / count(distinct user_id),
             2) as orders_per_user
FROM   user_actions

-- Рішення через DECIMAL
-- SELECT count(distinct user_id) as unique_users,
--        count(distinct order_id) as unique_orders,
--        round(count(distinct order_id)::DECIMAL / count(distinct user_id),
--              2) as orders_per_user
-- FROM   user_actions
unique_users unique_orders orders_per_user
0 21401 59595 2.78

І насамкінець познайомимося з більш просунутим функціоналом — агрегатними виразами з фільтрацією.

Якщо після агрегатної функції вказати ключове слово FILTER і помістити в дужках деяку умову condition після WHERE, то агрегатній функції на вхід будуть подані тільки ті рядки, для яких умова фільтра виявиться істинною.

Загалом ця конструкція виглядає так:

SELECT agg_function(column) FILTER (WHERE condition)
FROM table

Наприклад, якби ми захотіли порахувати середню ціну тільки для товарів категорії 'fish', то запит виглядав би так:

SELECT AVG(price) FILTER (WHERE category = 'fish') AS avg_fish_price
FROM table

Зверніть увагу: це дуже схоже на звичайну фільтрацію з агрегацією, яку ми розглядали в попередніх завданнях, тільки в даному випадку умова відбору записів вказується відразу в блоці SELECT.

Перевага такого запису в тому, що він дозволяє проводити розрахунки без проміжних запитів з умовами у блоці WHERE.

Розглянемо ще один приклад.

У нашому випадку досить зрозуміло, як порахувати загальну кількість користувачів. Також зрозуміло, як порахувати кількість користувачів, які хоча б раз скасовували замовлення — досить просто вказати потрібну умову в операторі WHERE. Але як у рамках одного запиту порахувати тих, хто ніколи не скасовував своє замовлення?

Оскільки об’єднувати кілька запитів разом ми поки що не вміємо, нам допоможе прийти агрегатний вираз.

Завдання 16.16
Порахуйте скільки користувачів ніколи не скасовували своє замовлення. Для цього із загальної кількості всіх унікальних користувачів відніміть кількість унікальних користувачів, які хоча б раз скасовували замовлення. Подумайте, яку умову необхідно вказати у FILTER, щоб отримати коректний результат.

Отриманий стовпець назвіть users_count.

Поле в результуючій таблиці: users_count

Рішення
%%sql
-- Рішення через різницю
SELECT count(distinct user_id) - count(distinct user_id) filter (WHERE action = 'cancel_order') as users_count
FROM   user_actions

-- Рішення через вкладений запит
-- SELECT count(distinct user_id) filter (WHERE user_id not in (SELECT DISTINCT user_id
--                                                              FROM   user_actions
--                                                              WHERE  action = 'cancel_order')) as users_count
-- FROM   user_actions
users_count

Давайте вирішимо ще одне завдання на агрегатні вирази з фільтрацією - цього разу з більш складними розрахунками.

Завдання 16.17
Порахуйте:

  • загальну кількість замовлень у таблиці orders
  • кількість замовлень із п’ятьма та більше товарами
  • частку замовлень із п’ятьма та більше товарами у загальній кількості замовлень.

У результуючій таблиці відобразіть усі три значення - поля назвіть відповідно orders, large_orders, large_orders_share.

Частку замовлень із п’ятьма та більше товарами у загальній кількості товарів округліть до двох знаків після коми.

Поля в результуючій таблиці: orders, large_orders, large_orders_share

Пояснення: при розрахунку частки не забудьте хоча б одне із значень попередньо привести до типу DECIMAL (або домножити на 1.0). Також пам’ятайте, що використовувати в розрахунках аліаси нових колонок не можна.

Рішення
%%sql
SELECT count(distinct order_id) as orders,
       count(distinct order_id) filter (WHERE array_length(product_ids, 1) >= 5) as large_orders,
       round(count(distinct order_id) filter (WHERE array_length(product_ids, 1) >= 5)::decimal / count(distinct order_id),
             2) as large_orders_share
FROM   orders
orders large_orders large_orders_share
0 59595 11498 0.19

Data Miorsh Ihor Miroshnychenko Youtube Monobank