From f8bf5e5b06ba4367f65690d80263ffdb855d5abb Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 19 Feb 2026 23:21:05 +0400 Subject: [PATCH 001/123] Clickhouse-Homewok --- dsolovianov/clickhouse/{ => Homework}/image.png | Bin .../{ => Homework}/Типы таблиц - Домашки.sql | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename dsolovianov/clickhouse/{ => Homework}/image.png (100%) rename dsolovianov/clickhouse/{ => Homework}/Типы таблиц - Домашки.sql (100%) diff --git a/dsolovianov/clickhouse/image.png b/dsolovianov/clickhouse/Homework/image.png similarity index 100% rename from dsolovianov/clickhouse/image.png rename to dsolovianov/clickhouse/Homework/image.png diff --git a/dsolovianov/clickhouse/Типы таблиц - Домашки.sql b/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql similarity index 100% rename from dsolovianov/clickhouse/Типы таблиц - Домашки.sql rename to dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql From c5a5df7c25be961c46e991ee7c1dd2402fa08a0e Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 01:54:22 +0400 Subject: [PATCH 002/123] =?UTF-8?q?1)=20=D0=9F=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BA?= =?UTF-8?q?=D1=83=20=D0=B2=20=D0=A2=D0=B8=D0=BF=D1=8B=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86=20=D0=BF=D0=BE=D0=B4=20ON=20CLUSTER.=202)=20?= =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=B4=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D1=88=D0=BA=D1=83=20=D0=B2=20=D0=A2=D0=B8=D0=BF=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20-=20=D0=97=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Типы данных - Домашка.sql | 26 ++++++++++++++++++ .../Homework/Типы таблиц - Домашки.sql | 27 ++++++++++++------- 2 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql diff --git a/dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql b/dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql new file mode 100644 index 0000000..963af35 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql @@ -0,0 +1,26 @@ +--Типы данных + +--Задача 1 +CREATE TABLE raw_data +ON CLUSTER clickhouse_cluster +( + user_id String, + balance String, + created_at String +) ENGINE = MergeTree ORDER BY tuple(); + +SELECT DISTINCT + toDecimal64OrNull(balance, 2) AS balance_decimal, + toDateTime64OrNull(created_at, 3) AS created_at_date +FROM raw_data +WHERE + toDecimal64OrNull(balance, 2) IS NOT NULL + AND toDateTime64OrNull(created_at, 3) IS NOT NULL + +/* + CAST(balance AS Decimal(10, 2)) - этом случае будет ошибка, так как данный метод не поддеживает NULL + + Следующие два метода по сути своей преобразование типа данных с условием ИЛИ, то есть: +toDecimal64OrNull(balance, 2) - в этом методе на выходе будут корректные данные, в противном случае NULL +toDecimal64OrZero(balance, 2) - аналогично с NULL, только будет ноль. +*/ \ No newline at end of file diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql b/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql index 04cda20..af9dcc3 100644 --- a/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql +++ b/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql @@ -1,6 +1,9 @@ --Задача 1: Создай таблицу page_views +DROP TABLE page_views; -CREATE TABLE page_views ( +CREATE TABLE page_views +ON CLUSTER clickhouse_cluster +( user_id UInt64, url String, view_time DateTime @@ -11,7 +14,9 @@ ORDER BY (user_id, view_time); --Задача 2: Создай таблицу users -CREATE TABLE users ( +CREATE TABLE users +ON CLUSTER clickhouse_cluster +( id UInt64, name String, updated_at DateTime @@ -21,7 +26,9 @@ ORDER BY id; -- Задача 3: Создай таблицу region_sales -CREATE TABLE region_sales ( +CREATE TABLE region_sales +ON CLUSTER clickhouse_cluster +( region String, date Date, sales UInt64 @@ -30,17 +37,19 @@ ENGINE = SummingMergeTree() PARTITION BY toYYYYMM(date) ORDER BY (region, date); ---Задача 4.1: Создай таблицу user_metrics +--Задача 4: Создай таблицу user_metrics -CREATE TABLE user_metrics ( +CREATE TABLE user_metrics +ON CLUSTER clickhouse_cluster +( region String, avg_age_state AggregateFunction(avg, UInt8), active_users_state AggregateFunction(uniq, UInt64) ) -ENGINE = AggregatingMergeTree() -ORDER BY region; +ENGINE = AggregatingMergeTree() --Подзадача 4.2 +ORDER BY region; --Подзадача 4.3 ---Задача 4.3: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` +--Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` INSERT INTO user_metrics SELECT @@ -61,7 +70,7 @@ FROM ) GROUP BY region; ---Задача 4.4: - Читай их через `avgMerge()` и `uniqMerge()` +--Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` SELECT region, From ae3392a2a3540a7d38f7b5836e40cfa9b1536376 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:05:53 +0400 Subject: [PATCH 003/123] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B7=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=BB=20=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BA=D1=83?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D0=A2=D0=B8=D0=BF=D1=8B=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Типы таблиц - Домашки.sql => CH - Типы таблиц - Домашка.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dsolovianov/clickhouse/Homework/{Типы таблиц - Домашки.sql => CH - Типы таблиц - Домашка.sql} (100%) diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql b/dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql similarity index 100% rename from dsolovianov/clickhouse/Homework/Типы таблиц - Домашки.sql rename to dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql From 555b38e192e1ab2d61b5302b493c0860ebb60ce1 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:06:46 +0400 Subject: [PATCH 004/123] delete --- .../Homework/CH - Типы таблиц - Домашка.sql | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql diff --git a/dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql b/dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql deleted file mode 100644 index af9dcc3..0000000 --- a/dsolovianov/clickhouse/Homework/CH - Типы таблиц - Домашка.sql +++ /dev/null @@ -1,81 +0,0 @@ ---Задача 1: Создай таблицу page_views -DROP TABLE page_views; - -CREATE TABLE page_views -ON CLUSTER clickhouse_cluster -( - user_id UInt64, - url String, - view_time DateTime - ) -ENGINE = MergeTree() -PARTITION BY toYYYYMM(view_time) -ORDER BY (user_id, view_time); - ---Задача 2: Создай таблицу users - -CREATE TABLE users -ON CLUSTER clickhouse_cluster -( -id UInt64, -name String, -updated_at DateTime - ) -ENGINE = ReplacingMergeTree(updated_at) -ORDER BY id; - --- Задача 3: Создай таблицу region_sales - -CREATE TABLE region_sales -ON CLUSTER clickhouse_cluster -( -region String, -date Date, -sales UInt64 - ) -ENGINE = SummingMergeTree() -PARTITION BY toYYYYMM(date) -ORDER BY (region, date); - ---Задача 4: Создай таблицу user_metrics - -CREATE TABLE user_metrics -ON CLUSTER clickhouse_cluster -( -region String, -avg_age_state AggregateFunction(avg, UInt8), -active_users_state AggregateFunction(uniq, UInt64) - ) -ENGINE = AggregatingMergeTree() --Подзадача 4.2 -ORDER BY region; --Подзадача 4.3 - ---Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` - -INSERT INTO user_metrics -SELECT - region, - avgState(age) AS avg_age_state, - uniqState(toUInt64(user_id)) AS active_users_state -FROM -( - SELECT 'Samara' AS region, 25 AS age, 1 AS user_id - UNION ALL - SELECT 'Moscow', 30, 2 - UNION ALL - SELECT 'Moscow', 22, 3 - UNION ALL - SELECT 'Samara', 22, 4 - UNION ALL - SELECT 'Sochi', 34, 5 -) -GROUP BY region; - ---Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` - -SELECT - region, - avgMerge(avg_age_state), - uniqMerge(active_users_state) -FROM user_metrics -GROUP BY region; - From 13f1c9af89d523b8bfaadf3cb221400eadc32b72 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:07:40 +0400 Subject: [PATCH 005/123] new vers. --- .../Homework/Типы таблиц - Homework.sql | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql new file mode 100644 index 0000000..af9dcc3 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql @@ -0,0 +1,81 @@ +--Задача 1: Создай таблицу page_views +DROP TABLE page_views; + +CREATE TABLE page_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + url String, + view_time DateTime + ) +ENGINE = MergeTree() +PARTITION BY toYYYYMM(view_time) +ORDER BY (user_id, view_time); + +--Задача 2: Создай таблицу users + +CREATE TABLE users +ON CLUSTER clickhouse_cluster +( +id UInt64, +name String, +updated_at DateTime + ) +ENGINE = ReplacingMergeTree(updated_at) +ORDER BY id; + +-- Задача 3: Создай таблицу region_sales + +CREATE TABLE region_sales +ON CLUSTER clickhouse_cluster +( +region String, +date Date, +sales UInt64 + ) +ENGINE = SummingMergeTree() +PARTITION BY toYYYYMM(date) +ORDER BY (region, date); + +--Задача 4: Создай таблицу user_metrics + +CREATE TABLE user_metrics +ON CLUSTER clickhouse_cluster +( +region String, +avg_age_state AggregateFunction(avg, UInt8), +active_users_state AggregateFunction(uniq, UInt64) + ) +ENGINE = AggregatingMergeTree() --Подзадача 4.2 +ORDER BY region; --Подзадача 4.3 + +--Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` + +INSERT INTO user_metrics +SELECT + region, + avgState(age) AS avg_age_state, + uniqState(toUInt64(user_id)) AS active_users_state +FROM +( + SELECT 'Samara' AS region, 25 AS age, 1 AS user_id + UNION ALL + SELECT 'Moscow', 30, 2 + UNION ALL + SELECT 'Moscow', 22, 3 + UNION ALL + SELECT 'Samara', 22, 4 + UNION ALL + SELECT 'Sochi', 34, 5 +) +GROUP BY region; + +--Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` + +SELECT + region, + avgMerge(avg_age_state), + uniqMerge(active_users_state) +FROM user_metrics +GROUP BY region; + From 8076bfd5ed82db2ed02096d392942c13ae18ce96 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:10:30 +0400 Subject: [PATCH 006/123] =?UTF-8?q?=D0=A2=D0=B8=D0=BF=D1=8B=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20-=20=D0=97=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{Типы данных - Домашка.sql => Типы данных - Homework.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dsolovianov/clickhouse/Homework/{Типы данных - Домашка.sql => Типы данных - Homework.sql} (100%) diff --git a/dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql b/dsolovianov/clickhouse/Homework/Типы данных - Homework.sql similarity index 100% rename from dsolovianov/clickhouse/Homework/Типы данных - Домашка.sql rename to dsolovianov/clickhouse/Homework/Типы данных - Homework.sql From 266da092457be285a5552c79883f9de76fa1e101 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:12:05 +0400 Subject: [PATCH 007/123] delete --- .../Homework/Типы данных - Homework.sql | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 dsolovianov/clickhouse/Homework/Типы данных - Homework.sql diff --git a/dsolovianov/clickhouse/Homework/Типы данных - Homework.sql b/dsolovianov/clickhouse/Homework/Типы данных - Homework.sql deleted file mode 100644 index 963af35..0000000 --- a/dsolovianov/clickhouse/Homework/Типы данных - Homework.sql +++ /dev/null @@ -1,26 +0,0 @@ ---Типы данных - ---Задача 1 -CREATE TABLE raw_data -ON CLUSTER clickhouse_cluster -( - user_id String, - balance String, - created_at String -) ENGINE = MergeTree ORDER BY tuple(); - -SELECT DISTINCT - toDecimal64OrNull(balance, 2) AS balance_decimal, - toDateTime64OrNull(created_at, 3) AS created_at_date -FROM raw_data -WHERE - toDecimal64OrNull(balance, 2) IS NOT NULL - AND toDateTime64OrNull(created_at, 3) IS NOT NULL - -/* - CAST(balance AS Decimal(10, 2)) - этом случае будет ошибка, так как данный метод не поддеживает NULL - - Следующие два метода по сути своей преобразование типа данных с условием ИЛИ, то есть: -toDecimal64OrNull(balance, 2) - в этом методе на выходе будут корректные данные, в противном случае NULL -toDecimal64OrZero(balance, 2) - аналогично с NULL, только будет ноль. -*/ \ No newline at end of file From f0e4c59112bfb68d6ced13ceb643cbdb52462d51 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 26 Feb 2026 02:13:06 +0400 Subject: [PATCH 008/123] =?UTF-8?q?=D0=A2=D0=B8=D0=BF=D1=8B=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20v.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Homework - Типы данных.sql | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Homework - Типы данных.sql diff --git a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql new file mode 100644 index 0000000..963af35 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql @@ -0,0 +1,26 @@ +--Типы данных + +--Задача 1 +CREATE TABLE raw_data +ON CLUSTER clickhouse_cluster +( + user_id String, + balance String, + created_at String +) ENGINE = MergeTree ORDER BY tuple(); + +SELECT DISTINCT + toDecimal64OrNull(balance, 2) AS balance_decimal, + toDateTime64OrNull(created_at, 3) AS created_at_date +FROM raw_data +WHERE + toDecimal64OrNull(balance, 2) IS NOT NULL + AND toDateTime64OrNull(created_at, 3) IS NOT NULL + +/* + CAST(balance AS Decimal(10, 2)) - этом случае будет ошибка, так как данный метод не поддеживает NULL + + Следующие два метода по сути своей преобразование типа данных с условием ИЛИ, то есть: +toDecimal64OrNull(balance, 2) - в этом методе на выходе будут корректные данные, в противном случае NULL +toDecimal64OrZero(balance, 2) - аналогично с NULL, только будет ноль. +*/ \ No newline at end of file From f7b9920af2f7251ac0340841cbcbe149cb7eedcb Mon Sep 17 00:00:00 2001 From: dmitry solovianov Date: Fri, 27 Feb 2026 19:02:32 +0400 Subject: [PATCH 009/123] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BA=D1=83=20=D0=A2=D0=B8=D0=BF?= =?UTF-8?q?=D1=8B=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Перезалью в новой ветке --- .../Homework/Типы таблиц - Homework.sql | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql deleted file mode 100644 index af9dcc3..0000000 --- a/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql +++ /dev/null @@ -1,81 +0,0 @@ ---Задача 1: Создай таблицу page_views -DROP TABLE page_views; - -CREATE TABLE page_views -ON CLUSTER clickhouse_cluster -( - user_id UInt64, - url String, - view_time DateTime - ) -ENGINE = MergeTree() -PARTITION BY toYYYYMM(view_time) -ORDER BY (user_id, view_time); - ---Задача 2: Создай таблицу users - -CREATE TABLE users -ON CLUSTER clickhouse_cluster -( -id UInt64, -name String, -updated_at DateTime - ) -ENGINE = ReplacingMergeTree(updated_at) -ORDER BY id; - --- Задача 3: Создай таблицу region_sales - -CREATE TABLE region_sales -ON CLUSTER clickhouse_cluster -( -region String, -date Date, -sales UInt64 - ) -ENGINE = SummingMergeTree() -PARTITION BY toYYYYMM(date) -ORDER BY (region, date); - ---Задача 4: Создай таблицу user_metrics - -CREATE TABLE user_metrics -ON CLUSTER clickhouse_cluster -( -region String, -avg_age_state AggregateFunction(avg, UInt8), -active_users_state AggregateFunction(uniq, UInt64) - ) -ENGINE = AggregatingMergeTree() --Подзадача 4.2 -ORDER BY region; --Подзадача 4.3 - ---Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` - -INSERT INTO user_metrics -SELECT - region, - avgState(age) AS avg_age_state, - uniqState(toUInt64(user_id)) AS active_users_state -FROM -( - SELECT 'Samara' AS region, 25 AS age, 1 AS user_id - UNION ALL - SELECT 'Moscow', 30, 2 - UNION ALL - SELECT 'Moscow', 22, 3 - UNION ALL - SELECT 'Samara', 22, 4 - UNION ALL - SELECT 'Sochi', 34, 5 -) -GROUP BY region; - ---Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` - -SELECT - region, - avgMerge(avg_age_state), - uniqMerge(active_users_state) -FROM user_metrics -GROUP BY region; - From 3acf36e7c82a0105c8de0a23c5e8c908f1a60b48 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Fri, 27 Feb 2026 22:08:09 +0400 Subject: [PATCH 010/123] =?UTF-8?q?+=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=202=20=D0=B2=20=D0=A2=D0=B8=D0=BF=D1=8B=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20-=20Homework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Homework - Типы данных.sql | 37 +++++++++++++++++- .../Homework/Типы данных - Задача 2.png | Bin 0 -> 112412 bytes .../{image.png => Типы таблиц - Homework.png} | Bin 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png rename dsolovianov/clickhouse/Homework/{image.png => Типы таблиц - Homework.png} (100%) diff --git a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql index 963af35..f15301b 100644 --- a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql +++ b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql @@ -7,7 +7,7 @@ ON CLUSTER clickhouse_cluster user_id String, balance String, created_at String -) ENGINE = MergeTree ORDER BY tuple(); +) ENGINE = MergeTree ORDER BY tuple(); SELECT DISTINCT toDecimal64OrNull(balance, 2) AS balance_decimal, @@ -23,4 +23,37 @@ WHERE Следующие два метода по сути своей преобразование типа данных с условием ИЛИ, то есть: toDecimal64OrNull(balance, 2) - в этом методе на выходе будут корректные данные, в противном случае NULL toDecimal64OrZero(balance, 2) - аналогично с NULL, только будет ноль. -*/ \ No newline at end of file +*/ + +--Задача 2 +CREATE TABLE events +ON CLUSTER clickhouse_cluster +( + event_id String, + event_date String, -- форматы: "2023-01-01", "01.02.2023", "invalid" + event_time String -- форматы: "12:30:45", "25:99:99" +) +ENGINE = MergeTree +ORDER BY event_id; + +--Заполнил данными для проверки +INSERT INTO events (event_id, event_date, event_time) VALUES +('1', '2023-01-01', '12:30:45'), +('2', '01.02.2023', '08:15:00'), +('3', 'invalid', '25:99:99'), +('4', '2023-05-20', 'invalid'), +('5', '31.12.2023', '23:59:59'); + +SELECT * FROM events; + +SELECT DISTINCT + toDate(DateTime_Valid) AS Event_Date, + toHour(DateTime_Valid) AS Event_Hour, + DateTime_Valid +FROM ( + SELECT + parseDateTimeBestEffortOrNull(concat(event_date, ' ', event_time)) AS DateTime_Valid + FROM events +) +WHERE DateTime_Valid IS NOT NULL; + diff --git a/dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png b/dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png new file mode 100644 index 0000000000000000000000000000000000000000..3e8757a70e49bd7cec059bddf534fb262cd007c6 GIT binary patch literal 112412 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

1Bor0tk7k1}a_EU$IX8@lzF#ed0{Q|>sP z5*Nq-(%1%>u+~%aCC$9*V$lbi^bCfQjBM!9D(NTaf+6wspN-XA^0wind&Y2@Q@s#I; zg4_KcW|SQ~Du{6%<$%xuI(n4EypF}{;r z1O1)Yl!IEQbfIK|p>&T2-&2nZTCd2A9GiIfBP;F#IkmvK@&2TUnIVw_?KX4&^V5(h=fXw24x7 zNPCFzs5r=n27S??+c>(SWakUEIx3OjHFEu1wa7nT2Pks01p<&?5Uj``ZOM}M#9<`k@}XL) z4W67Z{<>g>hpsX$CFP6&lj?EB0bnwV!228GcXSvbN6Y`!M{_X=33v>WC%zKV29xTb zYXVR^oX~b-n5Y%=4N+@P1DYvTisEh0L}d`g^8k=i8UGN;3WqBr^aD6P7uBE(>CwA% zGUv>6$6NQP*jT`;^da-<`$$_{(xiQ9`Sili`O1`)<#0e8SDsve!?~;R#e>9>LImxo zbz|Fe2In!KtAC-NfM1Iwyd={GkLo^26Y-k*)qm**qFcQDfeaNk$h@TzP<3iu$F?Q7 zI;t}0pk(zfk|iCa?Vz%cAgfE51>m9{%`pi7kn2WkQ*PU%@wpwC^RU)XAk%-ATwksq zvT4`oZcfRsqI(YQ`&C6D!%pz{y|7e!DNq<~y&;Qp1cEg^2KBkaRHQL{AX^!cs|3|Hv1YhK6EGzzVvKrMpoblx~bPUqMj3`Q-I$MRC z{d6^=5NLgN0avIoadDtUEJ7eySCYK4k*7Tsuver0^;nV;2ni9f=2r^Wa;vj zC&49|Ff#a25aR%6`8J-gR#HYLVLfu7^Tg-bg?uqb_rk5_<8f%&HH%YejrHc{r%Lhw zevy?qS07VVr-#X;vrHmwhcDus+A;7U8lnYP-(4aw1cfs6MxS@4n%g`2SWWhX@A^Oa zn~7H^#F+Hktk?7PRQ4x93{2AP4XwR~XQ||(9nbNL1$VRW478^Y(v*KY;TFH64Hf*`QQA5vGb17j+Tuj^2Y41437QZ5o@4IcLXP5e)9;;0MDG1Wt z9zdL@3p~F;;PLo3`w__5z{u*+$iHp%?z(2BZT{8j6|T}&%sloc`Zu}Y*+1uk2yQ*b z*4F5CARJ-Ne^fW3;oZaWg#eNx)$kP!P9ZCyH73i})jgvYGplE7o%{=iu~NNriksAA z1Pww<8o9H7Qa`p}Q}kBB4`Uxb@7VccbEsFf;c^%fC~4tsMJ(91=r|y1Y7jIOJy3S* z&rYb5ISv@?dg{bh+{%s*P>t~X6tACseEb|br$sPwNnHFEq=X8K9ZP6>G9Vjn5RKZG zG#FL@fQ_)C9L8|kf5D7|2Ysx?Z;bz6Nh2i5TL4XJKOMK6J4#3D1WhGDYOlgzk#ZVR=ZW~Z9hgvNq(XF zW2q2bq&JNeqvZ$-J-G-ZyS6C_#X8r;)>eSfz2LJd(!5c)u}Nn}2W_q(GG8xWL)1Je*LWHsA^MJqOIk`bca_js0QxT`KXm_w@<>x>?y`KN1B{40&mpwsny57erF_4@9!p!HOOW zQLL(;<@e{0>}$+ypg;Q#t#G@_6&@4(c=yB^|NAo1Kt#v(cYRqZns56Uf%&<0;A=ZpU5Z!e?CDu# znM}>;5BLfVCA_NP??L(rV4WTtK!q({Bar%N)Z2&952EOqkBIU*Fk*CWf3mi=x(Gs+GfEszk zzwwu-|0r1RTmFLPfBDSs5?06`kF)lN8zmB<2HZp6xAy>yp+_KjcDTB^s8(kBi0E%% zF!@m1Et)seT#LJ>tAE6OhA0Uftx6EU0}!2>a8HW-4w|;H1hF?gG(!ernmr2-dF$?E zGV5|||6tJwcx{?L5kzHk<}m`);7&qIL210)lgJ}v_d6*o$?Tol;V1`?=>oR;r+pWQ z>g1Sz`6v=H?}WHSvC|<;B}^U|`MeJI0#$0tr*cDic~2WIHuJIIHQb;^=tmcTqCJ53 zP@xjkBW9&e3zoD7JJD~VfvZ3jADH#EZ;d=3r@Qy#Go9;;W7Y%^X-`E%f0Q%$M-JcI zm$b8%uy(^62Z-+`8oyxzz=bCq{BqM{h?K8YR9VT3VgQ;FiV_{5Y1QmtUo$nGEW_+S z94@I^l8BpL<@*ah_O{5tJ{eHCx&(UT>m-+A^4ps}2gDNhss}Mo7d}|U#yc@9lhlc_ zH8F$&0F5k3uummee9-Z{0oF&RB8~FgL6P;p&X!5Rq*qZiZXK*;H@p;00`FRYMXYba zKiThJfF?so%G~o3WnD`>)BaXxdpZhA$=;)pbCs~@hf{U|>k2M~d_d~)*jHz(s(zGLUC#!DV(tvIz(5G2qq{*y$n=NW ze&~YLql9-KgacRNKbuH54u-?@Av9I0Z!g#QoncmC%lPs zeVw?Y9@&HcDSM6QxCS)a=|EqQv#B20@z&w^?;`5|L!9y9Ek06tuqp_9Y`Gq3X2qwR zUm`6fQV4MVN6aHuWyIZ@Wd#HHO_iHTqr`?zxjQ_#cvv7%^6|xd!oVPAWyNM}Z2VS3 zgLI7QJrXMj(^OG4kqZ-uBIWUfOF%C{B*GiCgcNZ^3_c0vEnX^9Fay0SQV zE!+m{xaD@_LSbizl6m7HaUC&xma~QpNo`)c)T!J^8;u1#^5g9r4}LLPOJOrP^YA4N zZu!_TI$vzfb%6H2BSUn)q0jO6jp%#muA%z<#$HuC8yp zVtZgS-`zGZy_;ofw)TQIKTKOU6!)Kl&0uZw#!$C(EdRtt&Jr7&p+5OC21#}fbXu-l zuElD94nORS#FiyJJ+W?L`;vnUJzo*Q`EXS z*j~nm@7&EPTL)Hd0Tbg>>epdpg)o6yqD@27`o=_iF0{>A?6^jRFB?Ay#)mQ-8}q>! z;JDxZ5K%ERMy_k7Ff(2SKjzS~&cAn592xGp*xo`*=ra^yAmi~v57~oboSHZVl7HTQ z#wNI?Jve~om%|Mk%jdJeNLRl}PcJ~z%ln-oG*lFu6AP6lE#V7~LAQ0(Efo3Jl=k8y zoQT0PprgL9Kc1ydgF2yS$S}d2W{>{0a(I80g}+cOJO#=lirW$qU#TAfTj+@@b87hy zznblkaB@FHKT<9T-OrZ~nhGwlg!nEHy144s*SV-UlOqZt6vyL>ebl>4c{DmLiy{p| zSkfcZbCaKzdve}7x!~AQu7yQs=C7{@OPyfQw!ANp8s3ejF#2R>{L5IL0X!B1$WQ^< zSyzYnxA(W6TaUUtL8y`Mv!fro*WS5eV*{+g`+fC!=IrkxLQO|)?|B7%Zw(A_x?Br> zT_W(fc_(01<-W+5DHH&YRA)wU+NY|DioVKPZk*|<`MUyzUYhe3g6M3ITb3=r%Uk~Q zyg;t-^LS%T*%x_I7TE(^Hy)k785XI+c8`-;ZfVDM=Qjw`o(6k_G#qGw*DRz(Ii8l_A>zTcW&exeHB@>bZ2SorB>+e?UlQ+777Ocrx#m;>)*RDR)^f?I}51H@tBj=EOgn>v+Kej&M5x# zO|XDh<5)y!JG-DHYONL0;+%(F*(La-7m#IcACL0bcj48A0GNQ^8h(WUf}u27boGZj zNR{|Ts3NPl^qb*V4%bsz(V`3*rzuXr2ljR5vBUM3La<1?~D@Ja*y#E5cib_Xk#Oz z)Df_o~zX7$2 zW^P`WI%Q2^^B$~7^3SOXMupqeLK>|h)mKh6Zxoj%euP;yxgnr5Rl}4QLM_M4+tz{! zMxLVoktTxVH(B5=Yl;59JNV-b+u}kKC0dRV7!V+U)wq@zzjf3ZZw$uH2;8FS>qM6} zH#z|F^PkI_zznFjvc#$Ge;Z=8z#sSs0;>3Wat6mtO4`5*SK{a&sD?3Ii7B&Br|NG) zt3Y@D?U=hFLG-Y3JBXqNv_#QbFaPNNp1PHNq>x8KiujBsCTJXeH-#&#(FA{R<+ViV zvyhgX>&`)L((ZcY^RTyt)6uaGfhbf!RGHSeo+1b&ktnKp9U=gVhD(?&4MX?}d?AkfK{dLPigu~iVHS8RxO)s`Qk9jJ(Fr(L|G$W1K$7&6 z_)l@ZzXTadG@ZP2?vt*@bv257kGCPvc!$Llp@`<{z;m_zVL_2mliNTw9oN`9zn*A? zcVY9@D$CH|ASPf|(M3xQ{RVPOjX;zY&Z`q0H>()vvE{JrTP+9FwzKhnVsp_7e7(h4 z`FQz8>tlQ0GUCt>%kzNqJKHnG++(#L&7t@{Rk^fpwFb`-gq(}jCdX+j$gT}0Xi~11 z;W!$OoA}+;_XdEkxxWxNsn9ob>ZpPZrF0@_t& z#!G$%nz8|cQbr{XD_%27xg~Vc^00<~AQ+w>gR*bE5CGa6@;rrlIIc*oub;Z(qt0b< zIW+pw5rkCH*bQd`j{FB6HZLa(k`GtoIP^eK$ODd z0iy=>cPIkyW&yxdv__BHCO&$adEb(@N$^KmRPsBL9gEoJuGVUAKxhG^t3usQ4vpUw z@;WX$rys_d#nn>>AQ?P?8DIrk-l{Bq2$0-cEvpQifL>fSmlqGZO< z5&_pkeH#O`5}vdS1w!{U$~{?GI!tik9SaTdAR>U*X($kOyZ>nI_Z(d|DflEao6$bR zm7=X8sQEn8ZheC1sfWKT?1Rs=8#q@xZrZ~Rha>MX^YNEWkF^%|jPEhn-vO-u9S-H! z!WE96AWI$F_Jh@Kj08E8k^4@44$ek9;EWAR>kctLwTK0|v?Lx>uQ z%K9L|oz^bAlllO5%v%O9E4R)uGP-UQYkxxLW>J5uO!{sAw`!Df)dDp?WD7dsQG<8+ zkbBvoYRIc!tfR)O)k=N3`QMB``Ja`6aJ|(@sF@^3DvMt3@DbF3YrqV z&B^G3t-6IRzsV-Fy97odA2z<&7ckTZpy)jZNc_O#9>sg4jm`_{5MC|cQ@~zYg5hyq z&>YJJ0&6hB4zC-wU{)6Xe29svJuy_+kNl*7s}k*dhL!H|T#l|qH!VcOLuy^F5U1x6 z;uk)d_Go!%v|Pmj*BE>S_}>d?;{<9Tf1kc{kfIBMZw>Pz;G*8M>ODaw9DsL6yPcw+ zu|?4OX*@DhyofZE74tr3H2{uv377;`L9i4~lKD{dr>Ier019V#<~Qe`ok&F$PLFkw za^`)7AYTb3?LxzbGL)_}fBNvYtSg@;7Fw8v91nnbYkK$8AKOLy-S|8HeH6fvk5Tja zt1OlNIi7U2F#OF`WgU3wN*EeSyF|_ZH{Sy+>tFz}LvmNy(jD<;lZySq7Q)%fh%UwVfQ^QkI*akbwV+lYWOm3NFI|x!=DHWd zw=ljos+L(yd$n=5zi&Nv(NfxlxzD83d;=tgw1hrKEmAg2t{?P*p6vAQSYQ@n{39G0 znfV(&V0@57()MG(=7LGjum@!qhHLtD({9-S%H4YyOO%usO?RJ}3Wh&)vBT~ERO=~5 zA^H))tlPn369|*j4a@#l%@ZJ%a-w`1_6wtGp)1RScX z(E2rax?@qy@_o)b?RzpEo82!u*Yu3>cKawtBwx>juKz0V&Z$I2#Pq*Z&H|`xwm!n= z$@!5Nob*#N{HKkCcwDTlF)8sUKaKjAp(~M_5d|WEVUyK3S?ND}!@qY_1>)=2 z_jmct-huk{p-wz8za`E?(ZP`v?CeuRwz}Wdsu}?^5%j~q*vKb8A1ccvKjZdWF@kNd z`}t%_EtHgaMd)r=OLoQZ>a54YHNEgX{Q2ydVWJdR6Nx~Zs$ZGhD;?dH)qDFP>wVK6 z_4|*A+_Lo=X9MKjk--Q?JncW2!|MU+$pF{CbjKfkou$S_``(K^D6;5DEiukCCr8F@Ftoy#~l0{0rO7w+ol-6a$t_+RfhN#NZcq z_Yh=^kb2H39r?(rMe%=3Pr0)G0~3Bw0rzdzT2PCrrrK`a?fUu7p z6X0De35RCSe*x1{@d(eSPK0K=q)Faaevh

S6sXME1(=eOFFI)}P3ea&K?6G^nTa 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 QLBOFbuP#^l)a=cF08!H*7XSbN literal 0 HcmV?d00001 diff --git a/dsolovianov/clickhouse/Homework/image.png b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.png similarity index 100% rename from dsolovianov/clickhouse/Homework/image.png rename to dsolovianov/clickhouse/Homework/Типы таблиц - Homework.png From 860d31c75c469e5ee453102823c94ff69609df6a Mon Sep 17 00:00:00 2001 From: dmitry solovianov Date: Fri, 27 Feb 2026 22:23:37 +0400 Subject: [PATCH 011/123] revert f7b9920af2f7251ac0340841cbcbe149cb7eedcb --- .../Homework/Типы таблиц - Homework.sql | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql new file mode 100644 index 0000000..af9dcc3 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Типы таблиц - Homework.sql @@ -0,0 +1,81 @@ +--Задача 1: Создай таблицу page_views +DROP TABLE page_views; + +CREATE TABLE page_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + url String, + view_time DateTime + ) +ENGINE = MergeTree() +PARTITION BY toYYYYMM(view_time) +ORDER BY (user_id, view_time); + +--Задача 2: Создай таблицу users + +CREATE TABLE users +ON CLUSTER clickhouse_cluster +( +id UInt64, +name String, +updated_at DateTime + ) +ENGINE = ReplacingMergeTree(updated_at) +ORDER BY id; + +-- Задача 3: Создай таблицу region_sales + +CREATE TABLE region_sales +ON CLUSTER clickhouse_cluster +( +region String, +date Date, +sales UInt64 + ) +ENGINE = SummingMergeTree() +PARTITION BY toYYYYMM(date) +ORDER BY (region, date); + +--Задача 4: Создай таблицу user_metrics + +CREATE TABLE user_metrics +ON CLUSTER clickhouse_cluster +( +region String, +avg_age_state AggregateFunction(avg, UInt8), +active_users_state AggregateFunction(uniq, UInt64) + ) +ENGINE = AggregatingMergeTree() --Подзадача 4.2 +ORDER BY region; --Подзадача 4.3 + +--Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` + +INSERT INTO user_metrics +SELECT + region, + avgState(age) AS avg_age_state, + uniqState(toUInt64(user_id)) AS active_users_state +FROM +( + SELECT 'Samara' AS region, 25 AS age, 1 AS user_id + UNION ALL + SELECT 'Moscow', 30, 2 + UNION ALL + SELECT 'Moscow', 22, 3 + UNION ALL + SELECT 'Samara', 22, 4 + UNION ALL + SELECT 'Sochi', 34, 5 +) +GROUP BY region; + +--Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` + +SELECT + region, + avgMerge(avg_age_state), + uniqMerge(active_users_state) +FROM user_metrics +GROUP BY region; + From dfc094300c266acbc4b3ebe150aa5c25a64e9fa7 Mon Sep 17 00:00:00 2001 From: dmitry solovianov Date: Fri, 27 Feb 2026 22:23:59 +0400 Subject: [PATCH 012/123] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20dsolovianov/clickhouse/Homework/=D0=A2=D0=B8=D0=BF?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20-=20=D0=97?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=202.png?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Типы данных - Задача 2.png | Bin 112412 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png diff --git a/dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png b/dsolovianov/clickhouse/Homework/Типы данных - Задача 2.png deleted file mode 100644 index 3e8757a70e49bd7cec059bddf534fb262cd007c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112412 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

1Bor0tk7k1}a_EU$IX8@lzF#ed0{Q|>sP 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

S6sXME1(=eOFFI)}P3ea&K?6G^nTa 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 QLBOFbuP#^l)a=cF08!H*7XSbN From 27f21ebd444e7a3de4b8dd1e46489f9fe93d6884 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Fri, 27 Feb 2026 22:25:27 +0400 Subject: [PATCH 013/123] =?UTF-8?q?=D0=9D=D0=B5=D1=82=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clickhouse/Homework/Типы таблиц.sql | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Типы таблиц.sql diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц.sql b/dsolovianov/clickhouse/Homework/Типы таблиц.sql new file mode 100644 index 0000000..af9dcc3 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Типы таблиц.sql @@ -0,0 +1,81 @@ +--Задача 1: Создай таблицу page_views +DROP TABLE page_views; + +CREATE TABLE page_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + url String, + view_time DateTime + ) +ENGINE = MergeTree() +PARTITION BY toYYYYMM(view_time) +ORDER BY (user_id, view_time); + +--Задача 2: Создай таблицу users + +CREATE TABLE users +ON CLUSTER clickhouse_cluster +( +id UInt64, +name String, +updated_at DateTime + ) +ENGINE = ReplacingMergeTree(updated_at) +ORDER BY id; + +-- Задача 3: Создай таблицу region_sales + +CREATE TABLE region_sales +ON CLUSTER clickhouse_cluster +( +region String, +date Date, +sales UInt64 + ) +ENGINE = SummingMergeTree() +PARTITION BY toYYYYMM(date) +ORDER BY (region, date); + +--Задача 4: Создай таблицу user_metrics + +CREATE TABLE user_metrics +ON CLUSTER clickhouse_cluster +( +region String, +avg_age_state AggregateFunction(avg, UInt8), +active_users_state AggregateFunction(uniq, UInt64) + ) +ENGINE = AggregatingMergeTree() --Подзадача 4.2 +ORDER BY region; --Подзадача 4.3 + +--Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` + +INSERT INTO user_metrics +SELECT + region, + avgState(age) AS avg_age_state, + uniqState(toUInt64(user_id)) AS active_users_state +FROM +( + SELECT 'Samara' AS region, 25 AS age, 1 AS user_id + UNION ALL + SELECT 'Moscow', 30, 2 + UNION ALL + SELECT 'Moscow', 22, 3 + UNION ALL + SELECT 'Samara', 22, 4 + UNION ALL + SELECT 'Sochi', 34, 5 +) +GROUP BY region; + +--Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` + +SELECT + region, + avgMerge(avg_age_state), + uniqMerge(active_users_state) +FROM user_metrics +GROUP BY region; + From e943afb57d0b3865fead1e00f64ee1506cf8e3b4 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Fri, 27 Feb 2026 22:30:04 +0400 Subject: [PATCH 014/123] - --- .../clickhouse/Homework/Типы таблиц.sql | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 dsolovianov/clickhouse/Homework/Типы таблиц.sql diff --git a/dsolovianov/clickhouse/Homework/Типы таблиц.sql b/dsolovianov/clickhouse/Homework/Типы таблиц.sql deleted file mode 100644 index af9dcc3..0000000 --- a/dsolovianov/clickhouse/Homework/Типы таблиц.sql +++ /dev/null @@ -1,81 +0,0 @@ ---Задача 1: Создай таблицу page_views -DROP TABLE page_views; - -CREATE TABLE page_views -ON CLUSTER clickhouse_cluster -( - user_id UInt64, - url String, - view_time DateTime - ) -ENGINE = MergeTree() -PARTITION BY toYYYYMM(view_time) -ORDER BY (user_id, view_time); - ---Задача 2: Создай таблицу users - -CREATE TABLE users -ON CLUSTER clickhouse_cluster -( -id UInt64, -name String, -updated_at DateTime - ) -ENGINE = ReplacingMergeTree(updated_at) -ORDER BY id; - --- Задача 3: Создай таблицу region_sales - -CREATE TABLE region_sales -ON CLUSTER clickhouse_cluster -( -region String, -date Date, -sales UInt64 - ) -ENGINE = SummingMergeTree() -PARTITION BY toYYYYMM(date) -ORDER BY (region, date); - ---Задача 4: Создай таблицу user_metrics - -CREATE TABLE user_metrics -ON CLUSTER clickhouse_cluster -( -region String, -avg_age_state AggregateFunction(avg, UInt8), -active_users_state AggregateFunction(uniq, UInt64) - ) -ENGINE = AggregatingMergeTree() --Подзадача 4.2 -ORDER BY region; --Подзадача 4.3 - ---Подзадача 4.4: - Вставь данные и Читай их через `avgMerge()` и `uniqMerge()` - -INSERT INTO user_metrics -SELECT - region, - avgState(age) AS avg_age_state, - uniqState(toUInt64(user_id)) AS active_users_state -FROM -( - SELECT 'Samara' AS region, 25 AS age, 1 AS user_id - UNION ALL - SELECT 'Moscow', 30, 2 - UNION ALL - SELECT 'Moscow', 22, 3 - UNION ALL - SELECT 'Samara', 22, 4 - UNION ALL - SELECT 'Sochi', 34, 5 -) -GROUP BY region; - ---Подзадача 4.5: - Читай их через `avgMerge()` и `uniqMerge()` - -SELECT - region, - avgMerge(avg_age_state), - uniqMerge(active_users_state) -FROM user_metrics -GROUP BY region; - From 980aaf9638d814ce56c6f5b9c2f5d0d36bedd40e Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Sat, 28 Feb 2026 00:02:04 +0400 Subject: [PATCH 015/123] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Homework - Типы данных.sql | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql index f15301b..68c8236 100644 --- a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql +++ b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql @@ -57,3 +57,35 @@ FROM ( ) WHERE DateTime_Valid IS NOT NULL; +--Задача 3 +DROP TABLE IF EXISTS user_activity ON CLUSTER clickhouse_cluster; + +CREATE TABLE user_activity +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + actions String -- JSON: ["login", "purchase", "logout"] +) ENGINE = MergeTree ORDER BY tuple(); + +--Тест данные +INSERT INTO user_activity (user_id, actions) VALUES +(1, '["login", "purchase", "logout"]'), +(2, '["login", "logout"]'), +(3, '["login"]'), +(4, '["login","purchase"]'); + +SELECT + user_id, + -- Сначала реобразовал записи из набора "каких-то" букв (String) в массив строк Array(String). Потом считваем длину массива и дальше суммируем длину всех массивов действий для каждого пользователя + sum(length(JSONExtract(actions, 'Array(String)'))) AS count_actions +FROM user_activity +WHERE + -- Проверяем наличие 'purchase' в массиве + has(JSONExtract(actions, 'Array(String)'), 'purchase') +GROUP BY + user_id; +/* +Преобразовал записи из набора "каких-то" букв (String) в массив строк Array(String). Нашел пользователей в новом массиве Array(String), совершивших действие "purchase". +Просто написать actions = 'purchase' не получится, так как изначально actions просто (String) и соответсвенно "["login", "purchase", "logout"]" нельзя сравнить с "purchase". Поэтому сначала преобразовываем в массив, а потом с функцией has(array, elem) ищем то, что нужно. +То есть менять бы не пришлось, если бы в actions было изначально разные значения в каждой строке ("login", "purchase" или "logout") +*/ \ No newline at end of file From 8c2a5fcfc8d015a6b269665b8a7fb40285c6fd02 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Sat, 28 Feb 2026 00:46:40 +0400 Subject: [PATCH 016/123] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=20?= =?UTF-8?q?4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Homework - Типы данных.sql | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql index 68c8236..05b588e 100644 --- a/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql +++ b/dsolovianov/clickhouse/Homework/Homework - Типы данных.sql @@ -88,4 +88,30 @@ GROUP BY Преобразовал записи из набора "каких-то" букв (String) в массив строк Array(String). Нашел пользователей в новом массиве Array(String), совершивших действие "purchase". Просто написать actions = 'purchase' не получится, так как изначально actions просто (String) и соответсвенно "["login", "purchase", "logout"]" нельзя сравнить с "purchase". Поэтому сначала преобразовываем в массив, а потом с функцией has(array, elem) ищем то, что нужно. То есть менять бы не пришлось, если бы в actions было изначально разные значения в каждой строке ("login", "purchase" или "logout") -*/ \ No newline at end of file +*/ + +--Задача 4 +DROP TABLE IF EXISTS sensor_data ON CLUSTER clickhouse_cluster; + +CREATE TABLE sensor_data +ON CLUSTER clickhouse_cluster +( + sensor_id UInt32, + value Float64, + status Enum('ok' = 1, 'error' = 2) +) ORDER BY sensor_id; + +--Тест данные +INSERT INTO sensor_data (sensor_id, value, status) VALUES +(1, 25.5, 'ok'), +(2, 120.75, 'error'), +(3, 15.0, 1), +(4, 9999999.99, 'ok'), +(5, 0.001, 'ok'); + +SELECT + toString(status) AS status_string, + avg(value) AS avg_value --Среднее значение value для каждого status +FROM sensor_data +WHERE accurateCastOrNull(value, 'Decimal(10,2)') IS NOT NULL --Фильтруем строки, где value нельзя преобразовать в Decimal(10,2) +GROUP BY status_string; \ No newline at end of file From 83c172e5465157f4fe053726e6c6343ba640dea3 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 7 Mar 2026 11:01:55 +0300 Subject: [PATCH 017/123] first DDL --- .../DDL_create_first_tables.sql | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql diff --git a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql new file mode 100644 index 0000000..8d6f3e2 --- /dev/null +++ b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql @@ -0,0 +1,47 @@ +-- ПРАКТИКА DDL +-- Задание 1 +DROP TABLE IF EXISTS mnoskov.users; +CREATE TABLE mnoskov.users ( + id bigint PRIMARY KEY, + name varchar(100) NOT NULL, + age SMALLINT, + email varchar(100), + country char(2), -- прогуглил что распространено сохранять iso-код а не все название + is_active boolean, + created_at timestamptz NOT NULL -- думаю timestamptz, если не учитывать часовой пояс, то даты могут отображаться неверно +); + + +-- Задание 2 +DROP TABLE IF EXISTS mnoskov.products; +CREATE TABLE mnoskov.products ( + id bigint PRIMARY KEY, + name varchar(100) NOT NULL, + category varchar(50), + price decimal(10,2) NOT NULL, + in_stock boolean +); + + +-- Задание 3 +DROP TABLE IF EXISTS mnoskov.orders; +CREATE TABLE mnoskov.orders ( + id bigint PRIMARY KEY, + user_id bigint, + order_date date NOT NULL, -- в названии date, и тип думаю date + status int, -- по идее отдельная таблица с тремя типами займет меньше места, чем каждый раз прописывать в varchar статус + FOREIGN KEY (user_id) REFERENCES users(id) +); + + +-- Задание 4 +DROP TABLE IF EXISTS mnoskov.order_items; +CREATE TABLE mnoskov.order_items ( + order_id bigint, + product_id bigint, + quantity int, -- думал надо smallint но потом подумал что вдруг 50к гвоздей + price_at_purchase decimal(10,2), + PRIMARY KEY (order_id, product_id), + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id) +); \ No newline at end of file From d13281f9cba2d60d217a86ca83077ef26ed2ac25 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 7 Mar 2026 12:17:44 +0300 Subject: [PATCH 018/123] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20first=20DDl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql index 8d6f3e2..0c6d42b 100644 --- a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql +++ b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql @@ -29,7 +29,7 @@ CREATE TABLE mnoskov.orders ( id bigint PRIMARY KEY, user_id bigint, order_date date NOT NULL, -- в названии date, и тип думаю date - status int, -- по идее отдельная таблица с тремя типами займет меньше места, чем каждый раз прописывать в varchar статус + status varchar(20), -- думал лучше int, но судя по заданию DML лучше varchar FOREIGN KEY (user_id) REFERENCES users(id) ); From 68d4c8b02e43b63251bfcce5663e46dbc33f1cfc Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 7 Mar 2026 12:34:29 +0300 Subject: [PATCH 019/123] first insert --- .../DML_first_inserts_in_tables.sql | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql diff --git a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql new file mode 100644 index 0000000..7c3e15f --- /dev/null +++ b/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql @@ -0,0 +1,50 @@ +-- Задание 1 +INSERT INTO mnoskov.users (id, name, age, email, country, is_active, created_at) +VALUES +(1, 'Alice', 25, 'alice@example.com', 'US', true, '2024-01-10 10:00:00'), +(2, 'Bob', 30, 'bob@example.com', 'UK', true, '2024-02-05 09:30:00'), +(3, 'Carol', 27, 'carol@example.com', 'US', false, '2024-03-01 15:20:00'), +(4, 'Diana', 22, 'diana@GMAIL.com', 'US', true, '2024-01-12 08:15:00'), +(5, 'Evan', 35, 'EVAN@example.com', 'UK', true, '2024-01-18 19:40:00'), +(6, 'Farida', 29, 'farida@example.com', 'KZ', true, '2024-02-20 11:05:00'), +(7, 'George', 31, 'george+test@example.com', 'US', true, '2024-03-05 16:45:00'), +(8, 'Helen', 24, NULL, 'UK', false, '2024-03-06 08:20:00'), +(9, 'Ivan', 26, 'ivan@example.com', 'RU', true, '2024-01-11 14:30:00'), +(10, 'Julia', 28, 'julia@example.com', 'US', true, '2024-03-07 18:05:00'); + +-- Задание 2 +INSERT INTO mnoskov.products (id, name, category, price, in_stock) +VALUES +(1, 'iPhone 15', 'Электроника', 80000, true), +(2, 'Kindle', 'Электроника', 15000, true), +(3, 'Кроссовки Nike', 'Одежда', 9000, true), +(4, 'Кроссовки NoName', 'Одежда', 3000, false), +(5, 'Наушники Pro', 'Электроника', 12000, true), +(6, 'Фитнес-браслет', 'Гаджеты', 6000, true), +(7, 'Подарочная карта', 'Сервисы', 1000, true); + +-- Задание 3 +INSERT INTO mnoskov.orders (id, user_id, order_date, status) +VALUES +(1, 1, '2025-01-10 12:00:00', 'paid'), +(2, 3, '2025-01-12 09:30:00', 'cancelled'), +(3, 1, '2025-01-15 18:20:00', 'paid'), +(4, 2, '2025-01-20 11:05:00', 'created'), +(5, 4, '2025-02-01 17:45:00', 'paid'), +(6, 5, '2025-02-10 10:10:00', 'refunded'), +(7, 7, '2025-03-01 09:00:00', 'paid'), +(8, 9, '2025-03-05 19:30:00', 'paid'); + +-- Задание 4 +INSERT INTO mnoskov.order_items (order_id, product_id, quantity, price_at_purchase) +VALUES +(1, 1, 1, 78000), +(1, 3, 2, 8500), +(2, 2, 1, 14000), +(3, 5, 1, 11000), +(3, 7, 1, 1000), +(4, 2, 1, 15000), +(5, 6, 1, 6000), +(6, 1, 1, 82000), +(7, 3, 1, 9000), +(8, 4, 2, 2800); \ No newline at end of file From 51295e9a4f4f9d76b83635a64c7586d6210daf58 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 7 Mar 2026 13:42:20 +0300 Subject: [PATCH 020/123] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D1=83=20=D0=BF=D0=BE?= =?UTF-8?q?=20'=D0=91=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9=20SQL'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Базовый_SQL/DQL_basic_SQL_practice.sql | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql diff --git a/mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql b/mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql new file mode 100644 index 0000000..641bfa7 --- /dev/null +++ b/mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql @@ -0,0 +1,59 @@ +-- Задание 1 +SELECT + id, + name, + country, + created_at, + email +FROM mnoskov.users +ORDER BY created_at DESC, name; +-- выбрал эти столбцы потому что так сказано в задании, я бы добавил ещё is_active +-- такая сортировка для того, чтобы даты шли от новых к старым и имена людей за одну дату шли по алфавиту + + +-- Задание 2 +SELECT + id, + name, + country +FROM mnoskov.users +WHERE country = 'US' +ORDER BY name; + +SELECT + id, + name, + country +FROM mnoskov.users +WHERE country = 'UK' +ORDER BY name; +-- для маркетолого полезнее первый запрос т.к. там больше данных + + +-- Задание 3 +SELECT + id, + name, + email +FROM mnoskov.users +WHERE email IS NOT NULL +ORDER BY name; + +SELECT DISTINCT + lower(email) AS email_lower +FROM mnoskov.users +WHERE email IS NOT NULL; +-- приводить email к одному регистру мб для красоты, вроде везде email принимается и в нижем и в верхнем регистре +-- дубликаты убираем чтобы вытрина занимала меньше места, и чтобы не было повторных рассылок + + +-- Задание 4 +SELECT + id, + name, + created_at +FROM mnoskov.users +WHERE created_at > '2024-02-20 23:59' +ORDER BY created_at DESC +LIMIT 2 OFFSET 1; +-- OFFSET может пригодиться, чтобы взять интервал данных \ No newline at end of file From 936be276bbd4402ec8816362d5903fd2e936aa21 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 7 Mar 2026 15:20:23 +0300 Subject: [PATCH 021/123] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D1=83=20=D0=BF=D0=BE?= =?UTF-8?q?=20'=D0=A4=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Фильтрация/DQL_filtration_practice.sql | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql diff --git a/mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql b/mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql new file mode 100644 index 0000000..df898a7 --- /dev/null +++ b/mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql @@ -0,0 +1,78 @@ +-- Задание 1 +SELECT + name, + category, + price, + price * 1.30 AS price_with_markup, + price * 1.50 AS price_with_vat +FROM + mnoskov.products +WHERE + in_stock = TRUE AND + category != 'smartwatch' AND -- категории smartwatch нет в предоставленных данных для заполнения + price BETWEEN 100 AND 1300; +-- Что лучше читается BETWEEN 100 AND 1300 или price >= 100 AND price <= 1300 и почему? +-- Лучше читается первый вариант, потому что просто прочитал и понял, а не думаешь над знаками, и первый вариант короче + + +-- Задание 2 +SELECT + name, + price, + price * 0.85 AS price_with_discount +FROM + mnoskov.products +WHERE + (price < 100 OR price > 1000); +-- Нужно отделять OR скобками, потому что если в условии будет ещё и AND, то логика может поломаться потому что AND выполняется раньше чем OR + + +-- Задание 3 +SELECT + id, + name, + category, + price +FROM + mnoskov.products +WHERE + category IN ('tablet', 'smartphone', 'mouse') AND + price >= 100 +ORDER BY + price DESC; + + +--Задание 4 +SELECT + name, + category, + price +FROM + mnoskov.products +WHERE + name NOT LIKE '%iPhone%'; + + +-- Задание 5 +-- 1) +SELECT + name, + category, + price +FROM + mnoskov.products +WHERE + name ILIKE '%pro%'; + +-- 2) +SELECT + name, + category, + price +FROM + mnoskov.products +WHERE + name ILIKE '%mx%' +ORDER BY + price DESC; +-- Для поисковой строи лучше ILIKE, потому что он не учитывает регистр - кто-то пишет с большой буквы, кто-то с маленькой, а кто-то случайно включил капс. \ No newline at end of file From 221ca37da69a8d9300147c0e5ab16fb3a3527b9a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 8 Mar 2026 18:21:19 +0300 Subject: [PATCH 022/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=BE=20JOIN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/postgresql/JOIN/DQL_join_practice.sql | 188 ++++++++++++++++++ .../Базовый_SQL/DQL_basic_SQL_practice.sql | 0 .../DDL_create_first_tables.sql | 0 .../DML_first_inserts_in_tables.sql | 0 .../Фильтрация/DQL_filtration_practice.sql | 0 5 files changed, 188 insertions(+) create mode 100644 mnoskov/postgresql/JOIN/DQL_join_practice.sql rename mnoskov/postgresql/{Основы_sql => Основы}/Базовый_SQL/DQL_basic_SQL_practice.sql (100%) rename mnoskov/postgresql/{Основы_sql => Основы}/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql (100%) rename mnoskov/postgresql/{Основы_sql => Основы}/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql (100%) rename mnoskov/postgresql/{Основы_sql => Основы}/Фильтрация/DQL_filtration_practice.sql (100%) diff --git a/mnoskov/postgresql/JOIN/DQL_join_practice.sql b/mnoskov/postgresql/JOIN/DQL_join_practice.sql new file mode 100644 index 0000000..8ba0e40 --- /dev/null +++ b/mnoskov/postgresql/JOIN/DQL_join_practice.sql @@ -0,0 +1,188 @@ +-- Задание 1 +SELECT + o.id AS order_id, + o.order_date, + o.status, + u.name AS user_name, + u.country +FROM + mnoskov.orders o +LEFT JOIN + mnoskov.users u ON o.user_id = u.id; +-- Логичнее начать с orders, тк надо вывести пользователей по заказам, а не наоборот. +-- Здесть уместнее LEFT, потому что в условии сказано ВСЕ заказы и уже по ним пользователей. Попробовал и INNER и LEFT - вывод один и тот же. + + +-- Задание 2 +SELECT + oi.order_id, + p.name AS prouct_name, + oi.quantity, + oi.price_at_purchase +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +WHERE + oi.order_id = 1; -- если 101, как в условии то ничего не будет +-- Если фильтрацию поставить после ON, то выведутся все продукты по 2 раза, у всех id=1, quantity и price возьмутсы из двух строк в order_items где id=1. + + +-- Задание 3 +SELECT + oi.order_id, + u.name AS user_name, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +JOIN + mnoskov.orders o ON oi.order_id = o.id +JOIN + mnoskov.users u ON o.user_id = u.id; +-- Заказ с id=3 повторяется два раза, потому что в заказе два товара, и они отображаются на двух строках. +-- Поскольку мы выводис все заказы, у нас больше строк по сравнению с заданием-2, и ещё мы выводим имена заказчиков. + + +--Задание 4 +SELECT + u.name AS user_name, + o.id AS order_id, + o.status +FROM + mnoskov.users u +LEFT JOIN + mnoskov.orders o ON u.id = o.user_id; +-- Этот запрос отличается от INNER тем, что тут отображаются ещё и пользователи без заказов. + + +-- Задание 5 +SELECT + u.id AS user_id, + u.name AS user_name +FROM + mnoskov.users u +LEFT JOIN + mnoskov.orders o ON u.id = o.user_id +WHERE + o.user_id IS NULL; +-- Важно фильтровать по колонке из orders, потому что она будет NULL из-за LEFT JOIN. Если есть несостыковки, то NULL будет в правой таблице после LEFT JOIN. + + +-- Задание 6 +SELECT + oi.order_id, + u.id AS user_name, + p.name AS product_name, + oi.quantity +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +JOIN + mnoskov.orders o ON oi.order_id = o.id +JOIN + mnoskov.users u ON o.user_id = u.id +WHERE + p.name LIKE 'Kindle'; +-- Заказ 2 встречается 1 раз, потому что там всего один товар, и потому что мы ищем именно товар с названием - Kindle. + + +-- Задание 7 +SELECT + p.name AS product_name, + oi.order_id, + oi.quantity +FROM + mnoskov.products p +LEFT JOIN + mnoskov.order_items oi ON p.id = oi.product_id; +-- Если товар не покупали то в order_id будет NULL +-- Если использовать INNER, то выведутся только товары у которых есть заказы, в случае с LEFT выведутся все заказы. + + +-- Задание 8 +SELECT + oi.order_id, + u.name AS user_name, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +JOIN + mnoskov.orders o ON oi.order_id = o.id +JOIN + mnoskov.users u ON o.user_id = u.id +WHERE + o.status = 'paid'; +-- Если написать status = 'paid' после ON в orders, то ничего не поменяется, потому что после ON пишутся условия соединения таблиц также как и в WHERE, но как я понял лучше писать в WHERE. + + +-- Задание 9 +SELECT + oi.order_id, + o.order_date, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +JOIN + mnoskov.order_items oi ON o.id = oi.order_id +JOIN + mnoskov.order_items oi2 ON oi.product_id = oi2.product_id +JOIN + mnoskov.orders o2 ON oi2.order_id = o2.id +JOIN + mnoskov.products p ON oi2.product_id = p.id +WHERE + u.name = 'Alice'; +-- Если у Alice не будет заказов то ничего не выведется. +-- Мы получается берем Alice -> идем до id товаров Alice -> идем обратно к заказам и выводим товары которые были у Alice. + +-- Мне подсказала нейронка как использовать SELF JOIN - я понимал что он нужен, но не понимал как именно им воспользоваться. +-- Только после такого запроса я разобрался как это работает: +/*SELECT + oi.order_id, + o.order_date, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase, + u2.name, + u2.id +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +JOIN + mnoskov.order_items oi ON o.id = oi.order_id +JOIN + mnoskov.order_items oi2 ON oi.product_id = oi2.product_id +JOIN + mnoskov.orders o2 ON oi2.order_id = o2.id +JOIN + mnoskov.users u2 ON o2.user_id = u2.id +JOIN + mnoskov.products p ON oi2.product_id = p.id +WHERE + u.name = 'Alice';*/ + + +-- Задание 10 +SELECT + o.id AS order_id, + o.user_id, + o.status +FROM + mnoskov.orders o +LEFT JOIN + mnoskov.order_items oi ON o.id = oi.order_id; +-- Если написать INNER JOIN, то появятся только существующие заказы, которые есть в обеих таблицах. \ No newline at end of file diff --git a/mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql b/mnoskov/postgresql/Основы/Базовый_SQL/DQL_basic_SQL_practice.sql similarity index 100% rename from mnoskov/postgresql/Основы_sql/Базовый_SQL/DQL_basic_SQL_practice.sql rename to mnoskov/postgresql/Основы/Базовый_SQL/DQL_basic_SQL_practice.sql diff --git a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql similarity index 100% rename from mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql rename to mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql diff --git a/mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql b/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql similarity index 100% rename from mnoskov/postgresql/Основы_sql/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql rename to mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql diff --git a/mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql b/mnoskov/postgresql/Основы/Фильтрация/DQL_filtration_practice.sql similarity index 100% rename from mnoskov/postgresql/Основы_sql/Фильтрация/DQL_filtration_practice.sql rename to mnoskov/postgresql/Основы/Фильтрация/DQL_filtration_practice.sql From 398e11f4d74aec77f7783ead17ab4693bef2190c Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 9 Mar 2026 21:07:03 +0300 Subject: [PATCH 023/123] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20DDL=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql index 0c6d42b..db89114 100644 --- a/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql +++ b/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql @@ -32,6 +32,8 @@ CREATE TABLE mnoskov.orders ( status varchar(20), -- думал лучше int, но судя по заданию DML лучше varchar FOREIGN KEY (user_id) REFERENCES users(id) ); +ALTER TABLE mnoskov.orders +ALTER COLUMN order_date TYPE timestamptz; -- Задание 4 From 53368945d1cdeac4f7d3ea307e650f00cf41b33d Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 9 Mar 2026 22:21:17 +0300 Subject: [PATCH 024/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20UNION?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgresql/UNION/DQL_union_practice.sql | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 mnoskov/postgresql/UNION/DQL_union_practice.sql diff --git a/mnoskov/postgresql/UNION/DQL_union_practice.sql b/mnoskov/postgresql/UNION/DQL_union_practice.sql new file mode 100644 index 0000000..7ff2b1c --- /dev/null +++ b/mnoskov/postgresql/UNION/DQL_union_practice.sql @@ -0,0 +1,75 @@ +-- Задание 1 +SELECT + id, + name, + 'active' AS user_status +FROM + mnoskov.users +WHERE + is_active = TRUE + +UNION ALL + +SELECT + id, + name, + 'inactive' AS user_status +FROM + mnoskov.users +WHERE + is_active = FALSE +ORDER BY name; +-- Если написать просто UNION, то в данном задании ничего не изменится + + +-- Задание 2 +SELECT + id AS order_id, + user_id, + order_date, + 'old' AS period +FROM + mnoskov.orders +WHERE + order_date < '2025-02-01' + +UNION ALL + +SELECT + id AS order_id, + user_id, + order_date, + 'new' AS period +FROM + mnoskov.orders +WHERE + order_date >= '2025-02-01' +ORDER BY + order_date, order_id; +-- По колонке period я пойму старый(old) заказ или новый(new) +-- Я не могу ответить, потому что CASE WHEN ещё не проходили + + +-- Задание 3 +SELECT DISTINCT + oi.product_id, + p.name AS product_name, + 'ordered' AS product_status +FROM + mnoskov.products p +JOIN + mnoskov.order_items oi ON p.id = oi.product_id + +UNION ALL + +SELECT DISTINCT + p.id AS product_id, + p.name AS product_name, + 'never_ordered' AS product_status +FROM + mnoskov.products p +LEFT JOIN + mnoskov.order_items oi ON p.id = oi.product_id +WHERE oi.product_id = NULL; +-- Проверка IS NULL по полю из order_items, потому что это права таблица, и null будут добавляться в неё +-- 1) Понизить цену товара 2) лучше прорекламировать 3) снять с продажи \ No newline at end of file From b52fa039323d0677f44bacd8bcc726b53cb88e53 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 10 Mar 2026 10:25:45 +0300 Subject: [PATCH 025/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=90=D0=B3=D1=80=D0=B5=D0=B3=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Базовый_SQL/DQL_basic_SQL_practice.sql | 0 .../DDL_create_first_tables.sql | 0 .../DML_first_inserts_in_tables.sql | 0 .../Фильтрация/DQL_filtration_practice.sql | 0 .../{JOIN => 2. JOIN}/DQL_join_practice.sql | 0 .../DQL_union_practice.sql | 0 .../DQL_aggregations&groupings_practice.sql | 130 ++++++++++++++++++ 7 files changed, 130 insertions(+) rename mnoskov/postgresql/{Основы => 1. Основы}/Базовый_SQL/DQL_basic_SQL_practice.sql (100%) rename mnoskov/postgresql/{Основы => 1. Основы}/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql (100%) rename mnoskov/postgresql/{Основы => 1. Основы}/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql (100%) rename mnoskov/postgresql/{Основы => 1. Основы}/Фильтрация/DQL_filtration_practice.sql (100%) rename mnoskov/postgresql/{JOIN => 2. JOIN}/DQL_join_practice.sql (100%) rename mnoskov/postgresql/{UNION => 3. UNION}/DQL_union_practice.sql (100%) create mode 100644 mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql diff --git a/mnoskov/postgresql/Основы/Базовый_SQL/DQL_basic_SQL_practice.sql b/mnoskov/postgresql/1. Основы/Базовый_SQL/DQL_basic_SQL_practice.sql similarity index 100% rename from mnoskov/postgresql/Основы/Базовый_SQL/DQL_basic_SQL_practice.sql rename to mnoskov/postgresql/1. Основы/Базовый_SQL/DQL_basic_SQL_practice.sql diff --git a/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql similarity index 100% rename from mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql rename to mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql diff --git a/mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql b/mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql similarity index 100% rename from mnoskov/postgresql/Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql rename to mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql diff --git a/mnoskov/postgresql/Основы/Фильтрация/DQL_filtration_practice.sql b/mnoskov/postgresql/1. Основы/Фильтрация/DQL_filtration_practice.sql similarity index 100% rename from mnoskov/postgresql/Основы/Фильтрация/DQL_filtration_practice.sql rename to mnoskov/postgresql/1. Основы/Фильтрация/DQL_filtration_practice.sql diff --git a/mnoskov/postgresql/JOIN/DQL_join_practice.sql b/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql similarity index 100% rename from mnoskov/postgresql/JOIN/DQL_join_practice.sql rename to mnoskov/postgresql/2. JOIN/DQL_join_practice.sql diff --git a/mnoskov/postgresql/UNION/DQL_union_practice.sql b/mnoskov/postgresql/3. UNION/DQL_union_practice.sql similarity index 100% rename from mnoskov/postgresql/UNION/DQL_union_practice.sql rename to mnoskov/postgresql/3. UNION/DQL_union_practice.sql diff --git a/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql b/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql new file mode 100644 index 0000000..28ee152 --- /dev/null +++ b/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql @@ -0,0 +1,130 @@ +-- Задание 1 +SELECT + category, + count(*) products_count +FROM + mnoskov.products +GROUP BY + category +ORDER BY + products_count; +-- Если убрать order by, то статистика будет в случайном разрбосанном виде, а не по возрастанию +-- Увеличится products_count на одно значение + + +-- Задание 2 +SELECT + order_id, + sum(quantity) AS total_quantity, + sum(quantity * price_at_purchase) AS order_revenue +FROM + mnoskov.order_items +GROUP BY + order_id +ORDER BY + order_revenue DESC; +-- Если убрать GROUP BY, то будет ошибка +-- Нельзя просто добавить в SELECT ещё одну колонку (например, product_id) без добавления её в GROUP BY или агрегат, потому что вылезет ошибка. Ну и колонка должна быть либо сгруппирована либо в агрегате, она не может существовать сама по себе в запросе с grouup by + + +-- Задание 3 +SELECT + p.category, + sum(oi.quantity * oi.price_at_purchase) AS category_revenue +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +GROUP BY + p.category +ORDER BY + category_revenue DESC; +-- Группировка по p.category - потому что мы хотим узнать сумму прибли по каждой категории +-- Если добавить p.name - будет ошибка потому что p.name тоже нужно будет заносить в GROUP BY + + +-- Задание 4 +SELECT + oi.order_id, + sum(oi.quantity * oi.price_at_purchase) AS order_revenue +FROM + mnoskov.orders o +JOIN + mnoskov.order_items oi ON o.id = oi.order_id +WHERE + o.status = 'paid' +GROUP BY + oi.order_id +ORDER BY + order_revenue DESC; +-- В задаче-2 были все заказы, а тут только оплаченные +-- Я бы смотрел толлько на paid, чтобы понять прибыль которая уже есть + + +-- Задание 5 +SELECT + p.category, + sum(oi.quantity * oi.price_at_purchase) AS category_revenue +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +GROUP BY + p.category +HAVING + sum(oi.quantity * oi.price_at_purchase) > 1000 +ORDER BY + category_revenue DESC; +-- Если добавить это условие в WHERE, то терминал будет ругаться мол агрегаты недопустимы в WHERE +-- Обычно условие(прим: WHERE price > 1000) отличается от агрегированного условия(прим: HAVING SUM(...) > 1000) тем, что в первом случае ничего считать не нужно, значение уже есть, а во втором нужно сначала чтобы функция все посчитала. Ну и HAVING запускается уже после группировки + + +-- Задание 6 +SELECT + country, + count(*) AS active_user_count +FROM + mnoskov.users +WHERE + is_active = true +GROUP BY + country +HAVING + count(*) >= 2 +ORDER BY + active_user_count DESC; +-- is_active = true логичнее писать в HAVING, потому что is_active - не агрегат +-- Если нет страны где больше двух активных юзеров, то ничего не выведется + + +-- Задание 7 +SELECT + p.name AS product_name, + sum(oi.quantity) AS total_quantity_sold +FROM + mnoskov.order_items oi +JOIN + mnoskov.products p ON oi.product_id = p.id +GROUP BY + p.name +ORDER BY + total_quantity_sold DESC +LIMIT 3; +-- total_quantity_sold - количество проданного товара, а product_revenue - выручка каждого товара +-- Бизнес сморит на количество, может быть когда товара очень много и нужно понять какой % уже продан, и когда нужно найти популярные или непопулярные товары + + +-- Задание 8 +SELECT + order_id, + sum(quantity * price_at_purchase) AS order_revenue +FROM + mnoskov.order_items +GROUP BY + order_id +HAVING + sum(quantity * price_at_purchase) < 10000 +ORDER BY + order_revenue; +-- Не понял вопрос: Какие реальные причины могут быть у очень маленьких заказов (1–2 гипотезы)? +-- Проще увидеть глазами отфильтрованные микро-заказы \ No newline at end of file From 74745c2a125bb5c62e42e3f5ce6c235e494bf744 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 10 Mar 2026 13:58:26 +0300 Subject: [PATCH 026/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=A4=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_functions_practice.sql | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql diff --git a/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql b/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql new file mode 100644 index 0000000..f37e1ef --- /dev/null +++ b/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql @@ -0,0 +1,90 @@ +-- Задание 1 +SELECT + name, + created_at::date, + current_date - created_at::date AS days_since_registration +FROM + mnoskov.users +WHERE + is_active = TRUE + AND current_date - created_at::date > 180 +ORDER BY + days_since_registration desc; +-- Очень старым пользователям можно кинуть рассылку на почту на подобии 'Представляете, мы вместе уже 777 дней, вот вам купон на скидку' +-- Удобнее вычитать ::date, чтобы days_since_registration красиво выглядел в таблице + + +-- Задание 2 +SELECT + name, + lower(email) AS email_clean, + substring(email FROM POSITION('@' IN email) + 1) AS domain +FROM + mnoskov.users +WHERE email IS NOT NULL +ORDER BY domain, name; +-- email приводят к нижнему регистру для нормализации записи, а зачем выделяют домен не понимаю, может быть для аналитики по типу кто какой почтой пользуется +-- Получу NULL в domain + + +-- Задание 3 +SELECT + name, + age, + CASE + WHEN age < 25 THEN 'young' + WHEN age BETWEEN 25 AND 35 THEN 'adult' + WHEN age > 35 THEN 'senior' + END AS age_group +FROM + mnoskov.users +ORDER BY + age; +-- Почему удобнее держать логику сегментации в SQL (CASE), а не только в коде приложения? Я не понял этот вопрос. +-- Если забыть в ветку ELSE, то будет NULL у тех кто никуда не попал + + +-- Задание 4 +SELECT + name, + created_at::date, + current_date - created_at::date AS days_since_registration, + CASE + WHEN created_at >= current_date - INTERVAL '30 days' THEN 'hot' ELSE 'regular' + END AS hot_flag +FROM + mnoskov.users +WHERE + created_at >= current_date - INTERVAL '30 days'; +-- Фильтрация по дате в WHERE исключает неподходящие варианты, а простой CASE в нашем случае возвращает результат в виде двух вариантов - подошло/не подошло и эти оба варианта остаются в таблице +-- Стоит запускать какие нибудь привлекательные варианты со скидкам и акциями, чтобы удержать пользователся + + +-- Задание 5 +SELECT + count(*) AS total_users, + count(email) AS with_email, + count(*) - count(email) AS without_email, + ((count(*) - count(email))::float / count(*)) * 100.0 AS percent_without_email +FROM + mnoskov.users; +-- Агрегаты дают более точную информацию чем просто на глаз +-- Я бы предолжил запустить активно вылезающие блоки с предложением зарегестрироваться, или же запретить пользоваение продуктом без регистрации + + +-- Задание 6 +SELECT + name, + current_date - created_at::date AS days_since_registraion, + CASE + WHEN current_date - created_at::date <= 30 THEN 'new' + WHEN current_date - created_at::date BETWEEN 31 AND 180 THEN 'middle' + WHEN current_date - created_at::date > 180 THEN 'old' + END AS tenure_group, + is_active +FROM + mnoskov.users +ORDER BY + tenure_group, days_since_registraion; +-- Сегментация по стажу говорит о опыте человека, а по возрасту только о возрасте +-- Пересчитывать каждый день - плюсы: не занимает места на диске, минусы: тратит больше вычислительных ресурсов, а если хранить статическое поле, то все наоборот \ No newline at end of file From 2be43992c8e50c13783c4f5113515cd400dd6a0c Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 10 Mar 2026 17:23:31 +0300 Subject: [PATCH 027/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=9F=D0=BE=D0=B4=D0=B7=D0=B0=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D1=8B=20=D0=B8=20CTE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../6. Подзапросы и CTE/DQL_cte_practice.sql | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql diff --git a/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql b/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql new file mode 100644 index 0000000..3682f94 --- /dev/null +++ b/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql @@ -0,0 +1,155 @@ +-- Задание 1 +SELECT + name +FROM + mnoskov.users +WHERE id IN ( + SELECT user_id + FROM mnoskov.orders +) +ORDER BY + name; +-- Тут не приходится делать JOIN и DISTINCT, а просто ищем айдишник в списке +-- N (подзапрос) может быть более читаемым, когда подзапрос очень простой и короткий + + +-- Задание 2 +SELECT + u.id, + u.name, + u.email +FROM + mnoskov.users u +WHERE NOT EXISTS ( + SELECT user_id + FROM mnoskov.orders o + WHERE + u.id = o.user_id +) +-- Работает быстрее чем NOT IN +-- Определить какое количество людей не делают заказы, и кинуть им например купон на скидку на первый заказ + + +-- Задание 3 +SELECT + u.name, + ( + SELECT + count(*) + FROM + mnoskov.orders o + WHERE + u.id = o.user_id + ) AS orders_count +FROM + mnoskov.users u +ORDER BY + orders_count DESC, u.name; +-- Подзапрос в SELECT не может возвращать несколько строк, потому что SELECT ожидает толбко одну +-- Как изменится сложность запроса, если пользователей станет, например, 1 млн? Не совсем понял этот вопрос, но думаю запрос просто будет выполняться дольше + + +-- Задание 4 +WITH revenue_by_user AS ( + SELECT + o.user_id, + sum(oi.quantity * oi.price_at_purchase) AS total_revenue + FROM + mnoskov.orders o + JOIN + mnoskov.order_items oi ON o.id = oi.order_id + GROUP BY + o.user_id +) +SELECT + u.name, + rbu.total_revenue +FROM + mnoskov.users u +JOIN + revenue_by_user rbu ON u.id = rbu.user_id +ORDER BY + rbu.total_revenue desc; +-- Проще читать когда шаг «посчитать выручку по пользователям» вынесен отдельно +-- Чтобы избежать дублирования импользуется CTE, а не простой подзапрос + + +-- Задание 5 +WITH revenue_by_product AS ( + SELECT + product_id, + sum(quantity * price_at_purchase) AS product_revenue + FROM + mnoskov.order_items + GROUP BY + product_id +) +SELECT + p.name, + p.category, + rbp.product_revenue +FROM + mnoskov.products p +JOIN + revenue_by_product rbp ON p.id = rbp.product_id +ORDER BY + rbp.product_revenue +LIMIT 3; +-- Сначала удобнее считать агрегаты, чтобы код не выгляжел грооздким и чтобы в нем было легче разобраться +-- Отличае в удобстве чтения и написания. И этот агрегат можно потом ещё и переиспользовать + + +-- Задание 6 +WITH revenue_user AS ( + SELECT + o.user_id, + sum(oi.quantity * oi.price_at_purchase) AS total_revenue + FROM + mnoskov.order_items oi + JOIN + mnoskov.orders o ON oi.order_id = o.id + GROUP BY + o.user_id +) +SELECT + u.name, + u.email, + ru.total_revenue +FROM + mnoskov.users u +JOIN + revenue_user ru ON u.id = ru.user_id +WHERE + u.is_active = TRUE AND + ru.total_revenue > 10000 +ORDER BY + ru.total_revenue DESC, u.name; +-- Я бы назвал этот сегмегт - frequent_buyers +-- Таким пользователям надо предлагать какую ниюудь специальную программу лояльности + + +-- Задание 7 +WITH revenue_order AS ( + SELECT + order_id, + sum(quantity * price_at_purchase) AS order_revenue + FROM + mnoskov.order_items + GROUP BY + order_id +) +SELECT + ro.order_id, + o.order_date::date, + ro.order_revenue +FROM + mnoskov.orders o +JOIN + revenue_order ro ON o.id = ro.order_id +WHERE + o.status = 'paid' AND + ro.order_revenue < 10000 +ORDER BY + ro.order_revenue; +-- Причины: злоупотребление промокодами, ошибки +-- По 'paid' удобнее фильтровать снаржуи, потому тут мы подключаем orders, а подключать orders ещё и внутри было бы бессмысленно \ No newline at end of file From 4b96e6012fe6d877b49d3d290b04ad5244ff7db9 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 11 Mar 2026 13:40:31 +0300 Subject: [PATCH 028/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=9E=D0=BA=D0=BE=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_window_functions_practice.sql | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql diff --git a/mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql b/mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql new file mode 100644 index 0000000..4d582be --- /dev/null +++ b/mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql @@ -0,0 +1,168 @@ +-- Задание 1 +SELECT + o.user_id, + u.name AS user_name, + o.id AS order_id, + o.order_date::date, + row_number() OVER (PARTITION BY o.user_id ORDER BY o.order_date) AS order_number_for_user +FROM + mnoskov.orders o +JOIN + mnoskov.users u ON o.user_id = u.id; +-- Отличае от обычного order by в том, что мы не просто сортируем, а сортируем в пределах одного пользователя, и ещё выводим нумерацию заказов + + +-- Задание 2 +WITH order_total_revenue AS ( + SELECT + o.user_id, + oi.order_id, + o.order_date::date, + sum(oi.quantity * oi.price_at_purchase) AS order_revenue + FROM + mnoskov.order_items oi + JOIN + mnoskov.orders o ON oi.order_id = o.id + GROUP BY + o.user_id, oi.order_id, o.order_date +) +SELECT + otr.user_id, + u.name AS user_name, + otr.order_id, + otr.order_date::date, + otr.order_revenue, + sum(otr.order_revenue) OVER (PARTITION BY otr.user_id ORDER BY otr.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_revenue_for_user +FROM + mnoskov.users u +JOIN + order_total_revenue otr ON u.id = otr.user_id; +-- Если не задвать рамку, то могут пояиться проблемы с дубликатами +-- Если накопительная выручка пользователя становится больше, значит он начинает тратить больше + + +-- Задание 3 +WITH user_total_revenue AS ( + SELECT + o.user_id, + sum(oi.quantity * oi.price_at_purchase) AS total_revenue + FROM + mnoskov.orders o + JOIN + mnoskov.order_items oi ON o.id = oi.order_id + GROUP BY + o.user_id +) +SELECT + u.name AS user_name, + utr.total_revenue, + rank() OVER (ORDER BY utr.total_revenue) AS revenue_rank, + dense_rank() OVER (ORDER BY utr.total_revenue) AS revenue_dense_rank +FROM + mnoskov.users u +JOIN + user_total_revenue utr ON u.id = utr.user_id; +-- Когда появляется одинаковая выручка, dense_rank нумерует по порядку, без пропусков(1,2,2,3), а rank по 'расположению'(1,2,2,4) + + +-- Задание 4 +SELECT + u.name AS user_name, + o.id AS order_id, + o.order_date::date, + first_value(o.order_date::date) OVER (PARTITION BY u.id ORDER BY o.order_date) AS first_order_date, + last_value(o.order_date::date) OVER (PARTITION BY u.id ORDER BY o.order_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_order_date +FROM + mnoskov.orders o +JOIN + mnoskov.users u ON o.user_id = u.id; +-- Первый заказ можно использовать для отчета 'как давно клиент начал активничать', а последний заказ, например если давно был последний заказ, то кинуть рассылку с промокодом, чтобы клиент снова начала заказывать как вариант +-- Если не задать рамку, то дата последнего заказа будет равна текущей строке, а не последней + + +-- Задание 5 +SELECT + u.name AS user_name, + o.id AS order_id, + count(*) OVER (PARTITION BY u.id) AS orders_per_user +FROM + mnoskov.orders o +JOIN + mnoskov.users u ON o.user_id = u.id; +-- Если использовать просто group by, то пользователи сожмутся в одну ячейку + + +-- Задание 6 +WITH order_total_revenue AS ( + SELECT + o.user_id, + oi.order_id, + o.order_date::date, + sum(oi.quantity * oi.price_at_purchase) AS order_revenue + FROM + mnoskov.orders o + JOIN + mnoskov.order_items oi ON o.id = oi.order_id + GROUP BY + o.user_id, oi.order_id, o.order_date +) +SELECT + u.name AS user_name, + otr.order_id, + otr.order_date::date, + otr.order_revenue, + lag(otr.order_revenue) OVER (PARTITION BY u.id ORDER BY otr.order_date) AS prev_order_revenue +FROM + mnoskov.users u +JOIN + order_total_revenue otr ON u.id = otr.user_id; +-- если текущее значение среднего чека больше чем предидущие, то увеличивается, а если наоборот, то уменьшается + + +-- Задание 7 +-- По месяцам: +WITH category_total_revenue AS ( + SELECT + p.category, + date_trunc('month', o.order_date)::date AS order_date_month, + sum(oi.quantity * oi.price_at_purchase) AS monthly_category_revenue + FROM + mnoskov.products p + JOIN + mnoskov.order_items oi ON p.id = oi.product_id + JOIN + mnoskov.orders o ON oi.order_id = o.id + GROUP BY + p.category, order_date_month +) +SELECT + category, + order_date_month, + monthly_category_revenue, + sum(monthly_category_revenue) OVER (PARTITION BY category ORDER BY order_date_month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_category_revenue +FROM + category_total_revenue; + +-- По дням: +WITH category_total_revenue AS ( + SELECT + p.category, + o.order_date::date AS order_date, + sum(oi.quantity * oi.price_at_purchase) AS daily_category_revenue + FROM + mnoskov.products p + JOIN + mnoskov.order_items oi ON p.id = oi.product_id + JOIN + mnoskov.orders o ON oi.order_id = o.id + GROUP BY + p.category, order_date +) +SELECT + category, + order_date, + daily_category_revenue, + sum(daily_category_revenue) OVER (PARTITION BY category ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_category_revenue +FROM + category_total_revenue; +-- По такому графику мы можем видеть РОСТ выручки по дням например, а не просто сколько в какой день принесла категория \ No newline at end of file From 2bb2b78dce785e563adf68c17a70821242f1870a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 12 Mar 2026 23:14:26 +0300 Subject: [PATCH 029/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=9F=D1=80=D0=B5=D0=B4=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BD=D1=8B=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_views_practice.sql | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql diff --git a/mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql b/mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql new file mode 100644 index 0000000..2e53170 --- /dev/null +++ b/mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql @@ -0,0 +1,66 @@ +-- Задание 1 +CREATE MATERIALIZED VIEW mv_category_revenue AS +SELECT + p.category, + sum(oi.quantity * oi.price_at_purchase) AS total_revenue +FROM + mnoskov.products p +JOIN + mnoskov.order_items oi ON p.id = oi.product_id +JOIN + mnoskov.orders o ON oi.order_id = o.id +WHERE o.status = 'paid' +GROUP BY + p.category +ORDER BY + total_revenue desc; +-- удобнее иметь wiev, чтобы небыло дублирования кода, и чтобы легко обращаться к запросу не переписывая все заново + + +-- Задание 2 +CREATE MATERIALIZED VIEW mv_user_activity AS +SELECT + u.id AS user_id, + u.name AS user_name, + count(*) AS orders_count +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +WHERE + o.status = 'paid' +GROUP BY + u.id, u.name +HAVING + count(*) > 0 +ORDER BY + orders_count desc; +-- Чем больше у пользователя заказов тем он более 'лояльный' +-- Тогда будет отображаться статистика только за последний месяц - 30 дней + + +-- Задание 3 +CREATE MATERIALIZED VIEW mv_daily_revenue AS +SELECT + o.order_date::date AS sales_date, + sum(oi.quantity * oi.price_at_purchase) AS daily_revenue +FROM + mnoskov.orders o +JOIN + mnoskov.order_items oi ON o.id = oi.order_id +WHERE + o.status = 'paid' +GROUP BY + o.order_date +ORDER BY + sales_date; +-- Можно построить график, и видеть пики с просадками +-- Быстрее считать выручку из mv_daily_revenue, тк витрина уже готова, и её не нужно высчитывать + + +-- Задание 4 +REFRESH MATERIALIZED VIEW mv_daily_revenue; +-- 1. интернет‑магазин с несколькими заказами в день - каждые сутки в 8 утра например +-- 2. крупный магазин с тысячами заказов в час - каждые 30 минут +-- Слишком редкий REFRESH сделает витрину бесполезной для аналитиков +-- Обновлять каждую секунжу нельзя, тк это тратит ресурсы компьютера, и тогда уж проще просто VIEW сделать, и вызывать когда надо \ No newline at end of file From da6270bf90a64a70083298ea6c0c29a0cba6946a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 13 Mar 2026 10:39:23 +0300 Subject: [PATCH 030/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A2=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DDL_create_first_tables.sql | 0 .../DML_first_inserts_in_tables.sql | 0 .../DQL_basic_SQL_practice.sql | 0 .../DQL_filtration_practice.sql | 0 .../1. Транзакции/DQL_transactions_practice.sql | 26 +++++++++++++++++++ 5 files changed, 26 insertions(+) rename mnoskov/postgresql/1. Основы/{Создание_и_изменение_таблиц_и_данных => 1. Создание_и_изменение_таблиц_и_данных}/DDL_create_first_tables.sql (100%) rename mnoskov/postgresql/1. Основы/{Создание_и_изменение_таблиц_и_данных => 1. Создание_и_изменение_таблиц_и_данных}/DML_first_inserts_in_tables.sql (100%) rename mnoskov/postgresql/1. Основы/{Базовый_SQL => 2. Базовый_SQL}/DQL_basic_SQL_practice.sql (100%) rename mnoskov/postgresql/1. Основы/{Фильтрация => 3. Фильтрация}/DQL_filtration_practice.sql (100%) create mode 100644 mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql diff --git a/mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql rename to mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql diff --git a/mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql b/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql rename to mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql diff --git a/mnoskov/postgresql/1. Основы/Базовый_SQL/DQL_basic_SQL_practice.sql b/mnoskov/postgresql/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/Базовый_SQL/DQL_basic_SQL_practice.sql rename to mnoskov/postgresql/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql diff --git a/mnoskov/postgresql/1. Основы/Фильтрация/DQL_filtration_practice.sql b/mnoskov/postgresql/1. Основы/3. Фильтрация/DQL_filtration_practice.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/Фильтрация/DQL_filtration_practice.sql rename to mnoskov/postgresql/1. Основы/3. Фильтрация/DQL_filtration_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql b/mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql new file mode 100644 index 0000000..c4a8939 --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql @@ -0,0 +1,26 @@ +-- Задание 1. Оформление закзаза +INSERT INTO mnoskov.orders (id, user_id, order_date, status) VALUES + (9, 10, '2026-03-13', 'paid'), + (10, 10, '2026-03-14', 'paid'); + +INSERT INTO mnoskov.order_items (order_id, product_id, quantity, price_at_purchase) VALUES + (9, 3, 2, 88000), + (10, 6, 1, 5900); + +UPDATE mnoskov.products +SET in_stock = FALSE +WHERE id IN(3, 6); +-- Если выполнятся не все шаги, то транзакция откатится и все останется как было +-- Потому что заказ если оформляется, то полностью, частично он выполняться не должен + + +-- Задание 2 +-- При таком сценарии все должно откатаиться к самому началу, ничего не сохранится +-- Если не делать rollback, то можно получить заказы без продуктов, или без стомости, или полностью оформленный заказ, но продуктов нет в наличии +-- Любые 'битые' заказы повлияют на отчеты и витрины + + +-- Задание 3 +-- При изменении цены товара в products, это действие могут начать выполнять два человека одновременное, и если это не будет транзакцией то получится непонятно что, а если как транзакция, то сначала запишется 1 результат, а потом 2, и не будет никаких ошибок +-- Если выполняетя как транзакция, то просто откатится назад и все будет супер +-- Тк база сама откатывает транзакции в случае надобности, это освобождает инженера от доли ответственности \ No newline at end of file From f4eaf9d6403210a5d768e6a36d606c8ec9385979 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 13 Mar 2026 12:18:33 +0300 Subject: [PATCH 031/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=91=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Блокировки/DQL_blocking_prectice.sql | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql b/mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql new file mode 100644 index 0000000..1cf1cbd --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql @@ -0,0 +1,21 @@ +-- Задание 1 +SELECT * FROM mnoskov.users WHERE id = 1; +-- Если две одновременные сессии выполнят этот запрос, то в обеих все выведется, и ошибок или очереди не бкдет +-- Потому что этот запрос только на чтение + + +-- Задание 2 +-- Первая сессия будет просто ждать окончания выполнения первой +-- Если разрешить обоимUPDATE пройти одновременно, то получится какая-то херня пологаю, они смешаются, или че-то такое +-- У Alice в итоге останется второй email, тк он выполнится последним. Порядок COMMIT важен, тк в последствии этого порядка будут выполняться запросы + + +-- Задание 3 +-- select выполнится тк просто читает, а update встанет в очередь, тк он попытается изменить 'забронированные данные' +-- FOR UPDATE помогает избежать ошибок когда происходит покупка товара или че то такое, когда идет процесс изменений в данных + + +-- Задание 4 +-- Дедлок возникает потому что каждая сессия заблокировал то, что нужно другой сессии. База отменит одну из сессий, чтобы прошла хоть какая-то +-- Чтобы дедлок не возникал, нужно обновления все производить в одном порядке +-- Если при массовых обновлениях не соблюдать порядок строк, то будет возникать жопа из дедлоков \ No newline at end of file From 508e077da3e386f57f932e8fea3452643f562c5a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 13 Mar 2026 16:07:02 +0300 Subject: [PATCH 032/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=BE=D0=B2'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3. Оптимизатор запросов/DQL_optimizer_pracitce.sql | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql diff --git a/mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql b/mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql new file mode 100644 index 0000000..ec5086f --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql @@ -0,0 +1,165 @@ +-- Задание 1 +-- Вариант А +EXPLAIN ANALYZE +SELECT name, age +FROM mnoskov.users +WHERE country = 'RU' AND age > 25; + +-- Вариант Б +EXPLAIN ANALYZE +SELECT name, age +FROM mnoskov.users +WHERE age > 25 AND country = 'RU'; +-- Выводят они один и тот же результат, потому что условия у них одинаковые +-- По скорости выполнения судя по планировщику тоже одинаковоы + + +-- Задание 2 +-- Вариант 1 +EXPLAIN ANALYZE +SELECT + u.name, + u.email, + o.order_date::date, + o.status +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +WHERE o.status = 'refunded'; + +-- Вариант 2 +EXPLAIN ANALYZE +SELECT + u.name, + u.email, + o.order_date::date, + o.status +FROM + mnoskov.orders o +JOIN + mnoskov.users u ON o.user_id = u.id +WHERE o.status = 'refunded'; +-- Между первой и второй таблицей результаты одинаковые. Если в запросе нет никаких условий связанных с таблицами, то первой оптимизатор смотреть users, но если если условие по orders.status, то сначала он сканирует orders + + +-- Задание 3 +EXPLAIN ANALYZE +SELECT name, email +FROM mnoskov.users +WHERE country = 'RU'; +-- Тип сканирования: Seq Scan +-- Количество прочитанных строк: 1 +-- Время выполнения примерно: 0.023 ms(+-5) + + +-- Задание 4 +-- Запрос А +EXPLAIN ANALYZE +SELECT * +FROM mnoskov.users +WHERE country = 'RU'; + +-- Запрос Б +EXPLAIN ANALYZE +SELECT name, email +FROM mnoskov.users +WHERE country = 'RU'; +-- Поскольку данных в таблице мало у меня время выполнения одинаковое, но могу предположить, что второй выполнился быстрее, тк у него в селекте два поля, а не вся таблица + + +-- Задание 5 +EXPLAIN ANALYZE +SELECT + u.name, o.id, o.order_date +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +WHERE + u.country = 'RU'; +-- Читать начала с users, тк мы в where пишем условие для users, и быстрее сначала отсеять ненужных юзеров + + +-- Задание 6 +SELECT u.name, COUNT(o.id) AS orders_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.country = 'Россия' + AND LOWER(u.name) LIKE '%алекс%' +GROUP BY u.name; +-- Его замедляет LOWER(не надо использовать функции), и думаю что LIKE(лучше использовать просто =), и ещэ group by по имени - это дольше чем по первичному ключу(надо группировать по ПК) + + +-- Задание 7 +EXPLAIN ANALYZE +SELECT u.name, p.name AS product_name, oi.quantity +FROM users u +JOIN orders o ON u.id = o.user_id +JOIN order_items oi ON o.id = oi.order_id +JOIN products p ON oi.product_id = p.id +WHERE u.country = 'RU'; +-- users -> orders -> order_items -> products - читает в таком порядке, потому что в where условие по users +-- Если убрать where, то будет - order_items -> users -> orders -> products +-- Как ускорить не знаю, может индексов добавть, но это пока не проходил + +-- Задание 8 +-- Вариант А +EXPLAIN ANALYZE +SELECT name, price +FROM mnoskov.products +WHERE category = 'Электроника' + AND price > 50000; + +-- Вариант Б +EXPLAIN ANALYZE +SELECT * +FROM mnoskov.products +WHERE category = 'Электроника' + AND price > 50000; +-- Данных в таблицах мало, врем выполнения не оценить, но смотря просто со стороны понятно что выриант А будет быстрее, потому что у него пару колонок в селекте, а не вся таблица как в Б + + +-- Задание 9 +-- Вариант коллег +SELECT * +FROM users +WHERE EXTRACT(YEAR FROM created_at) = 2026 + AND LOWER(country) = 'россия' + AND age + 10 > 35; + +-- Мой вариант +SELECT + id, name, age, is_active +FROM + mnoskov.users +WHERE + created_at BETWEEN '2026-01-01' AND '2027-01-02' AND + country = 'RU' AND + age > 45; +-- Мой вариант лучше потому что: +-- нет функций и мат. вычислений в where, все приведено к самому легкочитаемому формату +-- нет звездочки в select, а только то, что нужно + + +-- Задание 10 +INSERT INTO users (id, name, age, email, country, created_at) +SELECT + g AS id, + 'user_' || g AS name, + 27 AS age, + 'user_' || g || '@example.com' AS email, + 'RU' AS country, + '2026-02-03' AS created_at +FROM generate_series(20001, 50000) AS g; -- GPT сделал запрос мне + +SELECT * FROM mnoskov.users WHERE age IS NOT NULL; + +-- Запрос 1 +EXPLAIN ANALYZE +SELECT * FROM users WHERE age > 25; + +-- Запрос 2 +EXPLAIN ANALYZE +SELECT name, email FROM users WHERE age > 25; +-- Ну чето все равно не видна разница во времени выполнения, но по логике должен быть быстрее второй запрос \ No newline at end of file From 50bb355a95b3b5bf951dad9612f5acfe405a4ef6 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 14 Mar 2026 14:09:08 +0300 Subject: [PATCH 033/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'EXPLAIN=20=D0=B8=20EXPLAIN=20ANALYZE'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_explain_analyze_practice.sql | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql b/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql new file mode 100644 index 0000000..9db5b58 --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql @@ -0,0 +1,203 @@ +-- Задание 1 +EXPLAIN +SELECT + name, email +FROM + users +WHERE + id = 1; +-- Использует index scan +-- Index Cond: (id = 1) - это значит что условие id = 1, и оно выполняется по индексу, и что id - индекс +-- База использует индекс, потому что у нас по полю id создан индекс, и база выбирает наиболее быстрый пусть ввыполнения запроса - через индекс + + +-- Задание 2 +-- Запрос А +EXPLAIN +SELECT * FROM users WHERE id = 2; + +-- Запрос Б +EXPLAIN +SELECT * FROM users WHERE age > 25; +-- В первом запрос index scan, потому что у нас уловие по индексу - id, во втором запросы seq scan, потому что у поля age не индексов, и таблица сканируется медленее + + +-- Задание 3 +EXPLAIN ANALYZE +SELECT + name, price +FROM + products +WHERE + category = 'Электроника'; +-- Execution Time: 0.020 ms +-- Тип сканирования: Seq Scan +-- Ожидалось вернуть 1, а вернул 3 + + +-- Задание 4 +EXPLAIN ANALYZE +SELECT * FROM users WHERE country = 'RU'; +-- Без индекса - Execution Time: 6.769 ms +CREATE INDEX idx_country ON users(country); +-- С индексом - Execution Time: 2.076 ms +-- Сначала идет Bitmap Heap Scan on users - как я понял на этом этапе 'создается' таблица где country = 'RU', а потом Bitmap INdex Scan on idx_country - тут уже мы читам информацию из таблицы где все country = 'RU'. +-- Запрос стал быстрее примерно в три раза +-- Индекс помог, потому что база искала не среди всех данных, а среди 'проиндексированных' + + +-- Задание 5 +EXPLAIN ANALYZE +SELECT + u.name, o.order_date, o.status +FROM + users u +JOIN + orders o ON u.id = o.user_id +WHERE + u.country = 'RU'; +-- База исполщовала Nested Loop +-- Чтение началось с orders +-- Execution Time: 0.075 ms + + +-- Задание 6 +CREATE INDEX idx_email ON users(email); +EXPLAIN ANALYZE +SELECT + * +FROM + users +WHERE + lower(email) = 'user_28@example.com'; +-- База не использует индекс пока в where есть функция lower +-- Потому что в условии функция, а с ними индексы не в ладах как я понял +-- Чтобы индекс заработал, надо убрать функцию lower, и сравнивать email как есть +-- Если убрать lower, то Execution Time намного быстрее, прям очень намного быстрее + + +-- Задание 7 +CREATE INDEX idx_age ON users(age); +-- Запрос А +EXPLAIN ANALYZE +SELECT * FROM users WHERE age + 5 > 30; + +-- Запрос Б +EXPLAIN ANALYZE +SELECT * FROM users WHERE age > 25; +-- Быстрее работает запрос Б, но че то он тоже не хочет индекс использовать, хотя я его создал +-- Первый запрос не может использовать индекс, потому что там в условии математическое вычисление, а почему второй запрос не использует индек - я хз +-- Чтобы исправить запрос надо убрать сложение, и просто написать age > 25, как в запросе Б + + +-- Задание 8 +CREATE INDEX idx_created_at ON users(created_at); +EXPLAIN ANALYZE +SELECT u.name, COUNT(o.id) AS orders_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE EXTRACT(YEAR FROM u.created_at) = 2026 +GROUP BY u.name; +-- Функция EXTRACT - это плохая идея, потому что это функция которой быть в where по возможности не должно +-- WHERE i.crated_at BETWEEN '2026-01-01' AND '2027-01-01' +-- Индекс не используется в текущеа виде + + +-- Задание 9 +-- Запрос А +EXPLAIN ANALYZE +SELECT * FROM products WHERE price > 50000; + +-- Запрос Б +EXPLAIN ANALYZE +SELECT name, price FROM products WHERE price > 50000; +-- Если будет много данных в таблице, то быстрее будет работать второй запрос +-- Второй эффективнее тк в селекте указаны определнные поля, а не все(*) +-- Разница будет особо замета при больших объемах данных + + +-- Задание 10 +EXPLAIN ANALYZE +SELECT u.name, p.name AS product_name, oi.quantity +FROM users u +JOIN orders o ON u.id = o.user_id +JOIN order_items oi ON o.id = oi.order_id +JOIN products p ON oi.product_id = p.id +WHERE u.country = 'RU' AND p.category = 'Электроника'; +-- Порядок чтения таблиц: order_items -> products -> orders -> users +-- Количество строк: order_items - 12, products - 3, orders - 1, users - 0 +-- Если у country и category будут индексы, то запрос ьудет выполняться побыстре, потому что будет Index Scan, а не Seq Scan + + +-- Задание 11 +-- До +EXPLAIN ANALYZE +SELECT * +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE LOWER(u.name) LIKE '%иван%' + AND EXTRACT(MONTH FROM o.order_date) = 1 + AND u.age + 10 > 35; +-- Execution Time: 0.219 ms + +-- После +EXPLAIN ANALYZE +SELECT * +FROM users u +JOIN orders o ON u.id = o.user_id +WHERE u.name = 'иван' AND + o.order_date BETWEEN '2024-01-01' AND '2024-02-01' AND + O.order_date BETWEEN '2026-01-01' AND '2026-02-01' AND + u.age > 25; +-- Execution Time: 0.032 ms +-- Мой вариант лучще, потому что у меня нет функций и вычисление мешаюзих использовать индексы в условии, и оптимизатор может успешно использовать индексы + + +-- Задание 12 +-- Запрос 1 +EXPLAIN ANALYZE +SELECT + o.user_id, + o.id AS order_id +FROM + users u +JOIN + orders o ON u.id = o.user_id AND u.country = 'RU'; + +-- Запрос 2 +EXPLAIN ANALYZE +SELECT + o.user_id, + o.id AS order_id +FROM + orders o +WHERE EXISTS ( + SELECT + 1 + FROM + users u + WHERE + o.user_id = u.id AND + u.country = 'RU' +); + +-- Запрос 3 +EXPLAIN ANALYZE +SELECT + o.user_id, + o.id AS order_id +FROM + orders o +WHERE o.user_id IN ( + SELECT + u.id + FROM + users u + WHERE + u.country = 'RU' +); + +-- Запрос 1: Execution Time: 0.055 ms +-- Запрос 2: Execution Time: 0.051 ms +-- Запрос 3: Execution Time: 0.073 ms +-- Они все дют примерно одинаковый разультат, потому что мало данных в таблице. А планы выполнения у них у всех одинаковые, вот прям 1 в 1. Ну по логие 3 запрос самый медленный, а первые два шустрые \ No newline at end of file From fec0f8e103c170999e90e55ff351229cbbf4116f Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 15 Mar 2026 17:23:18 +0300 Subject: [PATCH 034/123] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D1=83?= =?UTF-8?q?=20'EXPLAIN=20=D0=B8=20EXPLAIN=20ANALYZE'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql b/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql index 9db5b58..d3c362e 100644 --- a/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql +++ b/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql @@ -124,8 +124,8 @@ JOIN orders o ON u.id = o.user_id JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.product_id = p.id WHERE u.country = 'RU' AND p.category = 'Электроника'; --- Порядок чтения таблиц: order_items -> products -> orders -> users --- Количество строк: order_items - 12, products - 3, orders - 1, users - 0 +-- Порядок чтения таблиц: products -> order_items -> orders -> users +-- Количество строк: products - 3, order_items - 12, orders - 1, users - 0 -- Если у country и category будут индексы, то запрос ьудет выполняться побыстре, потому что будет Index Scan, а не Seq Scan From ed2b990a3653b45f57b3c9f33865ccf6c1500587 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 15 Mar 2026 20:26:54 +0300 Subject: [PATCH 035/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=9F=D0=BE=D1=80=D1=8F=D0=B4=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_command_execution_order_practice.sql | 217 ++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql b/mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql new file mode 100644 index 0000000..55377f9 --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql @@ -0,0 +1,217 @@ +-- Задание 1 +EXPLAIN ANALYZE +SELECT country, count(*) AS users_count +FROM users +WHERE is_active = TRUE +GROUP BY country +HAVING count(*) > 1 +ORDER BY users_count DESC; +-- WHERE применится до GROUP BY +-- HAVING применится после GROUP BY +-- Использовать users_count в HAVING нельзя, а в ORDER BY можно + + +-- Задание 2 +EXPLAIN ANALYZE +SELECT u.name, o.order_date, o.status +FROM users u +JOIN orders o ON u.id = o.user_id +WHERE u.country = 'RU' AND o.status = 'paid' +ORDER BY o.order_date DESC; +-- Порядок выполнения по QUERY PLAN: +-- 1) сначала сканирутся таблица orders и выполняется фильтрация o.status = 'paid', +-- 2) потом сканируется таблица users по условию u.id = o.user_id, и выполняется вильтрация u.country = 'RU', +-- 3) далее Nested Loop соединяет orders и users, которые уже прошли фильтры, +-- 4) теперь выводится выполняется сортирвка o.order_date DESC + +-- Порядок выполнения по логике: +-- 1) сначала сканируем таблицу users и присоединяем orders по условию u.id = o.user_id +-- 2) потом фильтруем соединенные таблицы по u.country = 'RU' и o.status = 'paid' +-- 3) в SELECT выбираем поля которые хотим вывести - u.name, o.order_date, o.status +-- 4) сортируем выбранные поля по o.order_date по убыванию + + +-- Задание 3 +-- Запрос А +SELECT country, AVG(age) AS avg_age +FROM users +GROUP BY country +HAVING country = 'Россия'; +-- Работает медленно потому having фильтрет после группировки а не до + +-- Запрос Б +SELECT country, avg(age) AS avg_age +FROM users +WHERE country = 'RU' +GROUP BY country; +-- Этот вариант быстрее, тк он фильтрует строки до группировки, те группируется меньше строк а не все + + +-- Задание 4 +-- Запрос А +EXPLAIN ANALYZE +SELECT category, AVG(price) AS avg_price +FROM products +GROUP BY category +HAVING category = 'Электроника' AND AVG(price) > 20000; + +-- Запрос Б +EXPLAIN ANALYZE +SELECT category, AVG(price) AS avg_price +FROM products +WHERE category = 'Электроника' +GROUP BY category +HAVING AVG(price) > 20000; +-- Количество обработанных строк у запроса А: 4 +-- Количество обработанных строк у запроса Б: 4 +-- PostgreSQL походу оптимизирует даже хуйню написанную в запросе А, планы запросов А и Б одинаковые - 1 в 1, хотя по сути у запроса А должно быть больше строк + + +-- Задание 5 +-- Изначальный запрос +SELECT + country, + COUNT(*) AS users_count +FROM users +WHERE users_count > 1 +GROUP BY country +ORDER BY country; + +-- Исправленный запрос +SELECT + country, + COUNT(*) AS users_count +FROM users +GROUP BY country +HAVING COUNT(*) > 1 +ORDER BY country; + +-- Задание 6 +SELECT + u.country, + COUNT(o.id) AS orders_count, + AVG(o.id) AS avg_order_id +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.is_active = true +GROUP BY u.country +HAVING COUNT(o.id) > 1 +ORDER BY orders_count DESC +LIMIT 3; +-- Полный порядок выполнения: +-- 1) FROM/JOIN - сканируется таблица users, и к ней присоединяется таблица orders по условию u.id = o.user_id +-- 2) WHERE - из двух соединенных таблиц фильтруются и остаются только строки где u.is_active = true +-- 3) GROUP BY - происходится группировка по u.country, те все строки с одной и той же страной(записью в столбце) сжимаются в одну +-- 4) HAVING - уже по сгруппированным строкам проводится фильтрация по COUNT(o.id) > 1, остются только те страны(строки) где кол-во пользователей больше одного +-- 5) SELECT - выбираются столбцы которые будут выведены: u.country, COUNT(o.id) AS orders_count, AVG(o.id) AS avg_order_id +-- 6) ORDER BY - выбранные в SELECT строки сортируются по orders_count по убыванию +-- 7) LIMIT - из отсортированных строк остаются только первые 3 + + +-- Задание 7 +-- Запрос А +EXPLAIN ANALYZE +SELECT category, COUNT(*) +FROM products +GROUP BY category +HAVING category = 'Электроника'; + +-- Запрос Б +EXPLAIN ANALYZE +SELECT category, COUNT(*) +FROM products +WHERE category = 'Электроника' +GROUP BY category; +-- по логике запрос Б должен быть быстрее тк он сначала филтрует строки а потом группирует уже отфильтрованное, +-- НО PostgreSql может оптимизровать и запрос А, и получается что выполняются оба запроса одинаково, сначала фильтрация а потом группировка +-- В обоих запросах на шаге 'Seq Scan on products' обрабатываются: 3 строки, а на 'GroupAggregate': 1 строка +-- Execution Time одно и тоже + + +-- Задание 8 +SELECT + u.name, + COUNT(o.id) AS orders_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.country = 'Россия' + AND u.age > 25 + AND o.status = 'completed' +GROUP BY u.name +HAVING COUNT(o.id) >= 2 +ORDER BY orders_count DESC; +-- WHERE применяется между FROM/JOIN и GROUP BY +-- HAVING применяется между GROUP BY и SELECT +-- фильтрация по o.status в WHERE происходит группировки строк, а фильтрация по COUNT(o.id) в HAVING происходит уже после группирациюю, по самим сгруппированным строкам + + +-- Задание 9 +SELECT + u.name, + p.name AS product_name, + p.price +FROM users u +JOIN orders o ON u.id = o.user_id +JOIN order_items oi ON o.id = oi.order_id +JOIN products p ON oi.product_id = p.id +WHERE u.country = 'Россия' +ORDER BY p.price DESC +LIMIT 5; +-- ORDER BY применяется после SELECT, перед LIMIT/OFFSET(если есть) +-- LIMIT идет после GROUP BY, тк сначала нужно отфильтровать строки чтобы взять их кусок(5 строк) +-- если LIMIT(5) выполнить до сортировки, то это будет бесполезно, возьмутся просто первые 5 строк, и уже они будут сортироваться, и по итогу выйдет хуйня + + +-- Задание 10 +EXPLAIN ANALYZE +SELECT + category, + count(id) AS count_product_in_category, + avg(price) AS avg_category_price +FROM + products +GROUP BY + category +HAVING + count(id) >= 2 OR avg(price) > 15000 +ORDER BY + avg_category_price DESC +LIMIT 3; +-- 1) FROM - сканируем poducts +-- 2) GROUP BY - группируем по category +-- 3) HAVING - фильтруем по агрегированной функции count(id) +-- 4) SELECT - делаем выборку по столбцам category, count_product_in_category, avg_category_price +-- 5) ORDER BY - сортируем по avg_category_price по убыванию +-- 6) LIMIT - выбираем первые 3 строки из отсортированных +-- Ипсользовал HAVING тк по условию все фильтрации по агрегированным функциям +-- Выполнил с EXPLAIN ANALYZE, сначала читаетя porducts, потом делается фильтрации по count(id) >= 2 и avg(price) > 15000, далее sort, а потом Limit + + +-- Задание 11 +-- Запрос А +EXPLAIN ANALYZE +SELECT + u.name, + u.age, + (SELECT COUNT(*) + FROM orders o + WHERE o.user_id = u.id) AS orders_count +FROM users u +WHERE u.country = 'RU' +ORDER BY orders_count DESC; +-- Подзапрос в SELECT выполняется самым первым, до начала остального запроса, и называется в планировщие - SubPlan 1 +-- orders_count нельзя использовать в WHERE +-- orders_count можно использовать в ORDER BY + +-- Запрос Б(исправленный) +EXPLAIN ANALYZE +SELECT + u.name, + u.age, + count(o.id) AS orders_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.country = 'RU' +GROUP BY u.name, u.age +ORDER BY orders_count DESC; +-- Запрос Б(исправленный) - быстрее, тк подзапрос в SELECT делается для каждого пользователя заново, а JOIN всего один раз \ No newline at end of file From 240ccccba3e11e8175157310d2b54232d7643c2f Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 15 Mar 2026 21:56:20 +0300 Subject: [PATCH 036/123] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=20=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86,=20JOIN,=20?= =?UTF-8?q?UNION,=20=D0=90=D0=B3=D1=80=D0=B5=D1=86=D0=B8=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B8=D1=80=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8,=20=D0=A4=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8,=20CTE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DDL_create_first_tables.sql | 4 +--- .../postgresql/2. JOIN/DQL_join_practice.sql | 4 ++-- .../postgresql/3. UNION/DQL_union_practice.sql | 2 +- .../DQL_aggregations&groupings_practice.sql | 4 ++-- .../DQL_functions_practice.sql | 4 ++-- .../6. Подзапросы и CTE/DQL_cte_practice.sql | 18 +++++++++--------- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql index db89114..676ee2f 100644 --- a/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql +++ b/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql @@ -28,12 +28,10 @@ DROP TABLE IF EXISTS mnoskov.orders; CREATE TABLE mnoskov.orders ( id bigint PRIMARY KEY, user_id bigint, - order_date date NOT NULL, -- в названии date, и тип думаю date + order_date timestamptz NOT NULL, -- в названии date, и тип думаю date status varchar(20), -- думал лучше int, но судя по заданию DML лучше varchar FOREIGN KEY (user_id) REFERENCES users(id) ); -ALTER TABLE mnoskov.orders -ALTER COLUMN order_date TYPE timestamptz; -- Задание 4 diff --git a/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql b/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql index 8ba0e40..639da44 100644 --- a/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql +++ b/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql @@ -16,7 +16,7 @@ LEFT JOIN -- Задание 2 SELECT oi.order_id, - p.name AS prouct_name, + p.name AS product_name, oi.quantity, oi.price_at_purchase FROM @@ -75,7 +75,7 @@ WHERE -- Задание 6 SELECT oi.order_id, - u.id AS user_name, + u.id AS user_id, p.name AS product_name, oi.quantity FROM diff --git a/mnoskov/postgresql/3. UNION/DQL_union_practice.sql b/mnoskov/postgresql/3. UNION/DQL_union_practice.sql index 7ff2b1c..b284f5e 100644 --- a/mnoskov/postgresql/3. UNION/DQL_union_practice.sql +++ b/mnoskov/postgresql/3. UNION/DQL_union_practice.sql @@ -70,6 +70,6 @@ FROM mnoskov.products p LEFT JOIN mnoskov.order_items oi ON p.id = oi.product_id -WHERE oi.product_id = NULL; +WHERE oi.product_id is NULL; -- Проверка IS NULL по полю из order_items, потому что это права таблица, и null будут добавляться в неё -- 1) Понизить цену товара 2) лучше прорекламировать 3) снять с продажи \ No newline at end of file diff --git a/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql b/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql index 28ee152..1058267 100644 --- a/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql +++ b/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql @@ -93,7 +93,7 @@ HAVING count(*) >= 2 ORDER BY active_user_count DESC; --- is_active = true логичнее писать в HAVING, потому что is_active - не агрегат +-- is_active = true логичнее писать в WHERE, потому что is_active - не агрегат -- Если нет страны где больше двух активных юзеров, то ничего не выведется @@ -126,5 +126,5 @@ HAVING sum(quantity * price_at_purchase) < 10000 ORDER BY order_revenue; --- Не понял вопрос: Какие реальные причины могут быть у очень маленьких заказов (1–2 гипотезы)? +-- Не понял вопрос: Какие реальные причины могут быть у очень маленьких заказов (1–2 гипотезы)? Ответ: «доставка пробников/тестовой партии», «использование купонов/бонусов», «технический тест витрины» -- Проще увидеть глазами отфильтрованные микро-заказы \ No newline at end of file diff --git a/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql b/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql index f37e1ef..30d30fb 100644 --- a/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql +++ b/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql @@ -40,7 +40,7 @@ FROM mnoskov.users ORDER BY age; --- Почему удобнее держать логику сегментации в SQL (CASE), а не только в коде приложения? Я не понял этот вопрос. +-- Почему удобнее держать логику сегментации в SQL (CASE), а не только в коде приложения? В каком коде? Если в 'коде приложения' - это какая-то отдельная программа, то могут предположить, чтобы передавать в приложение уже отфильтрованные данные, чтобы задачи базы данных не выносить из базы. -- Если забыть в ветку ELSE, то будет NULL у тех кто никуда не попал @@ -50,7 +50,7 @@ SELECT created_at::date, current_date - created_at::date AS days_since_registration, CASE - WHEN created_at >= current_date - INTERVAL '30 days' THEN 'hot' ELSE 'regular' + WHEN created_at >= current_date - INTERVAL '30 days' THEN 'hot' ELSE NULL END AS hot_flag FROM mnoskov.users diff --git a/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql b/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql index 3682f94..cb6ad44 100644 --- a/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql +++ b/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql @@ -26,7 +26,7 @@ WHERE NOT EXISTS ( WHERE u.id = o.user_id ) --- Работает быстрее чем NOT IN +-- NOT EXISTS безопаснее в плане NULL, и часто планируется лучше чем NOT IN -- Определить какое количество людей не делают заказы, и кинуть им например купон на скидку на первый заказ @@ -50,7 +50,7 @@ ORDER BY -- Задание 4 -WITH revenue_by_user AS ( +WITH revenue_user AS ( SELECT o.user_id, sum(oi.quantity * oi.price_at_purchase) AS total_revenue @@ -63,19 +63,19 @@ WITH revenue_by_user AS ( ) SELECT u.name, - rbu.total_revenue + ru.total_revenue FROM mnoskov.users u JOIN - revenue_by_user rbu ON u.id = rbu.user_id + revenue_user ru ON u.id = ru.user_id ORDER BY - rbu.total_revenue desc; + ru.total_revenue desc; -- Проще читать когда шаг «посчитать выручку по пользователям» вынесен отдельно -- Чтобы избежать дублирования импользуется CTE, а не простой подзапрос -- Задание 5 -WITH revenue_by_product AS ( +WITH revenue_product AS ( SELECT product_id, sum(quantity * price_at_purchase) AS product_revenue @@ -87,13 +87,13 @@ WITH revenue_by_product AS ( SELECT p.name, p.category, - rbp.product_revenue + rp.product_revenue FROM mnoskov.products p JOIN - revenue_by_product rbp ON p.id = rbp.product_id + revenue_product rp ON p.id = rp.product_id ORDER BY - rbp.product_revenue + rp.product_revenue LIMIT 3; -- Сначала удобнее считать агрегаты, чтобы код не выгляжел грооздким и чтобы в нем было легче разобраться -- Отличае в удобстве чтения и написания. И этот агрегат можно потом ещё и переиспользовать From 3f307b95acf233d76a8e11b7efee6c7d724ef304 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 16 Mar 2026 22:40:53 +0300 Subject: [PATCH 037/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'JOIN=20(=D1=84=D0=B8=D0=B7=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=D0=B5)'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../6. JOIN (физические)/DQL_physical_JOIN_practice.sql | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql b/mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql new file mode 100644 index 0000000..79fc169 --- /dev/null +++ b/mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql @@ -0,0 +1,260 @@ +-- Задание 1 +-- Ситуация -- Какой JOIN выберет база? -- Почему? +-------------------------------------------------------------------------------------- +-- 10 пользователей, 1 млн заказов,| Nested | Маленькая таблица и большая с индексом. | +-- есть индекс на orders.user_id | Loop | База будет искать совпадения из | +-- | | маленькой по индексу из большой. | +-------------------------------------------------------------------------------------- +-- 100 000 пользователей, | Hash | Таблицы среднего размера, нет индексов. | +-- 100 000 заказов, нет индексов | Join | База построит хэш-таблицу одной из них, | +-- | | и будет искать совпадения в другой. | +-------------------------------------------------------------------------------------- +-- Обе таблицы отсортированы по | Merge | Потому что таблицы отсортированы по | +-- ключу, есть индексы | Join | ключу. Будет происходить соединение - | +-- | | 'Одновременное&последовательное'. | +-------------------------------------------------------------------------------------- +-- Маленькая таблица (5 строк) | Nested | База будет искать совпадения из | +-- соединяется с большой (1 млн), | Loop | маленькой в большой. Но тк нет индексов | +-- нет индекса | | - это будет медленно. | +-- |--------| Я засомневался и разобрал с ГПТ: | +-- | Hash | Nested Loop будет дорого(без индексов - | +-- | Join | 5.000.000 проврок), поэтому скорее всего| +-- | | Hash Join, просто построится хэш-таблица| +-- | | по маленькой и 5 раз отсканирует большую| +-------------------------------------------------------------------------------------- +-- Я заебался строить эту таблицу из пробелов. Хуй когда решусь на это снова. | +-------------------------------------------------------------------------------------- + + +-- Задание 2 +-- Маленькая таблица (10 строк) +CREATE TABLE mnoskov.small_users AS +SELECT generate_series(1, 10) AS id, 'User ' || generate_series(1, 10) AS name; + +-- Большая таблица (100 000 строк) +CREATE TABLE mnoskov.big_orders AS +SELECT + generate_series(1, 100000) AS id, + (random() * 9 + 1)::int AS user_id, + CURRENT_DATE - (random() * 365)::int AS order_date; + +-- Вариант 1: Без индекса +EXPLAIN ANALYZE +SELECT u.name, COUNT(o.id) +FROM small_users u +JOIN big_orders o ON u.id = o.user_id +GROUP BY u.name; + +-- Вариант 2: С индексом +CREATE INDEX idx_big_orders_user_id ON big_orders(user_id); +--DROP INDEX idx_big_orders_user_id; + +EXPLAIN ANALYZE +SELECT u.name, COUNT(o.id) +FROM small_users u +JOIN big_orders o ON u.id = o.user_id +GROUP BY u.name; +-- Вариант 1: Merge Join - Потому что обе таблицы отсортированы с самого создания +-- Варинат 2: Тоже Merge Join +-- База могла бы выбрать Nested Loop, потому что у нас одна таблица маленькая а другая большая и с индексом, но выбрала Merge Join, потому что таблицы отсортированы +-- С индексом Execution Time: 31.579 ms, а без примерно 80.000 ms + + +-- Задание 3 +-- Вариант 1: Без ORDER BY +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +JOIN orders o ON u.id = o.user_id; + +-- Вариант 2: С ORDER BY по ключу соединения +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +JOIN orders o ON u.id = o.user_id +ORDER BY u.id, o.user_id; + +-- Вариант 3: С ORDER BY по другому полю +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +JOIN orders o ON u.id = o.user_id +ORDER BY o.order_date; +-- Во втором варианте также как и в первом - Nested Loop +-- Merge Join не появился +-- ORDER BY может повлиять на выбор JOIN, тк если есть стортировка, то база может выбрать Merge Join + + +-- Задание 4 +EXPLAIN ANALYZE +SELECT + u.name, + p.name AS product_name, + oi.quantity, + o.order_date +FROM users u +JOIN orders o ON u.id = o.user_id +JOIN order_items oi ON o.id = oi.order_id +JOIN products p ON oi.product_id = p.id +WHERE u.country = 'RU'; +-- (u.id = o.user_id) - Nested Loop; (o.id = oi.order_id) - Nested Loop; (oi.product_id = p.id) - Hash Join +-- Seq Scan на этапах сканирования orders и products +-- Думаю стоит создать индекс на orders.user_id; и надо было бы на users.countyr, но я создавал уже в предидущих укроках; подумал, что надо ещё products.id, но там id и так первичный ключ, значит и индекс есть +CREATE INDEX idx_orders_user_id ON orders(user_id); -- DROP INDEX idx_orders_user_id; +-- После создания индекса типы JOIN не поменялись, план остался тем же, скорость выполнения запроса та же + + +-- Задание 5 +EXPLAIN ANALYZE +SELECT + u.name, + COUNT(DISTINCT o.id) AS orders_count, + COUNT(DISTINCT oi.product_id) AS products_count +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +LEFT JOIN order_items oi ON o.id = oi.order_id +WHERE u.country = 'RU' +GROUP BY u.name; +-- на этапе LEFT JOIN order_items oi ON o.id = oi.order_id - оптимизатор выполнил Hash Right Join по условию oi.order_id = o.id, то есть Hash Join +-- на этапе LEFT JOIN orders o ON u.id = o.user_id - оптимизатор выполнил Hash Left Join по условию u.id = o.user_id, то есть тоже Hash Join +-- Метод по сути один и тот же - Hash Join, но отличаются Right и Left - я думаю это выбрано для лучшей оптимизации +-- Через Hash Right Join проходит - 12 строк +-- Через Hash Left Join проходит - 11281 строк + + +-- Задание 6 +-- Вариант A: INNER JOIN +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +INNER JOIN orders o ON u.id = o.user_id; + +-- Вариант B: LEFT JOIN +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +LEFT JOIN orders o ON u.id = o.user_id; +-- Разный тип используется, в варианте А - Nested Loop, в варианте B - Hash Left Join +-- Да, у варанта B Execution Time в очень много раз больше +-- LEFT JOIN медленне, потому что он не использует индекс, а делает Seq Scan, те читает таблицы полностью, потому что подругому не получится выполнить LEFT JOIN + + +-- Задание 7 +-- Вариант 1: users JOIN orders +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +JOIN orders o ON u.id = o.user_id; + +-- Вариант 2: orders JOIN users +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM orders o +JOIN users u ON o.user_id = u.id; +-- Тип JOIN не изменился +-- Порядок чтения таблиц не изменился +-- Execution Time одинаковое +-- Оптимизатор выбрал один и тот же порядок потому что по сути это два одинаковых запроса, с одной и той же логикой, но написаны чуть чуть по разному, и оптимизатор в обоих случаях выполняет так, как быстрее. Почему порядок может быть разным, я не предполагаю. + + +-- Задание 8 +-- Запрос 1 +EXPLAIN ANALYZE +SELECT + * +FROM + orders o +JOIN + order_items oi ON o.id = oi.order_id +ORDER BY + o.id, oi.order_id; +-- Уверен такой запрос привел бы к Merge Join, если бы данных в таблицах было побольше + +-- Запрос 2(из задания 2) +EXPLAIN ANALYZE +SELECT u.name, COUNT(o.id) +FROM small_users u +JOIN big_orders o ON u.id = o.user_id +GROUP BY u.name; +-- Потому что обе таблицы уже отсортированы в момент создания + +-- Задание 9 +-- Nested Loop лучше Hash Join, когда есть индексы по полям фильтрации, когда одна таблица небольшая, а у второй есть индекс. Подглядел: когда нужно найти немного совпадений. +-- Hash Join лучше чем Nested Loop, когда нет индексов, когда таблицы среднего размера и больше, и когдау условие фильтрации '=' + +-- Оба запроса из задания 6: +-- Вариант A: INNER JOIN +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +INNER JOIN orders o ON u.id = o.user_id; +-- тут nested loop быстрее, тк соеденияется все по индексам + +-- Вариант B: LEFT JOIN +EXPLAIN ANALYZE +SELECT u.name, o.order_date +FROM users u +LEFT JOIN orders o ON u.id = o.user_id; +-- а тут быстрее merge join, тк по индексам соединить не получится, тут нужны обе полные таблицы, и создается по одной из них хэш-таблица + + +-- Задание 10 +-- Запрос A +EXPLAIN ANALYZE +--CREATE MATERIALIZED VIEW mv_category_revenue AS +SELECT + p.category, + sum(oi.quantity * oi.price_at_purchase) AS total_revenue +FROM + mnoskov.products p +JOIN + mnoskov.order_items oi ON p.id = oi.product_id +JOIN + mnoskov.orders o ON oi.order_id = o.id +WHERE o.status = 'paid' +GROUP BY + p.category +ORDER BY + total_revenue desc; +-- До: Execution Time: 0.309 ms +CREATE INDEX idx_orders_status ON orders(status); +-- После добавления индекса ничего не поменялось, план запроса остался тот же, скорее всего потому что мало данных, думаю будь их больше с индексом запрос был бы быстрее + +-- Запрос B +EXPLAIN ANALYZE +--CREATE MATERIALIZED VIEW mv_user_activity AS +SELECT + u.id AS user_id, + u.name AS user_name, + count(*) AS orders_count +FROM + mnoskov.users u +JOIN + mnoskov.orders o ON u.id = o.user_id +WHERE + o.status = 'paid' +GROUP BY + u.id, u.name +HAVING + count(*) > 0 +ORDER BY + orders_count desc; +-- И тут тоже несмотря на то что индекс на orders статус я уже создал, таблица orders скаринруется как Seq Scan, я думаю это потому, что мало данных, будь их больше с индексом запрос был бы быстрее + +-- Запрос C +EXPLAIN ANALYZE +--CREATE MATERIALIZED VIEW mv_daily_revenue AS +SELECT + o.order_date::date AS sales_date, + sum(oi.quantity * oi.price_at_purchase) AS daily_revenue +FROM + mnoskov.orders o +JOIN + mnoskov.order_items oi ON o.id = oi.order_id +WHERE + o.status = 'paid' +GROUP BY + o.order_date +ORDER BY + sales_date; +-- Тут ситуация аналогичная и для первый двух запросов, нужные индексы уже созданы, в связи выполнения всех предидущих занятий и тем, и единственное что я мог сделать для улучшения времени выполнения - это добавить индекс на orders(status) \ No newline at end of file From 69d49c501d1d1898b53969c6505e7843c799b3aa Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 17 Mar 2026 17:29:45 +0300 Subject: [PATCH 038/123] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?DDL=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DDL_create_first_tables.sql | 0 .../DML_first_inserts_in_tables.sql | 0 .../2. Базовый_SQL/DQL_basic_SQL_practice.sql | 0 .../3. Фильтрация/DQL_filtration_practice.sql | 0 .../2. JOIN/DQL_join_practice.sql | 0 .../3. UNION/DQL_union_practice.sql | 0 .../DQL_aggregations&groupings_practice.sql | 0 .../DQL_functions_practice.sql | 0 .../6. Подзапросы и CTE/DQL_cte_practice.sql | 0 .../DQL_window_functions_practice.sql | 0 .../DQL_views_practice.sql | 0 .../1. Транзакции/DQL_transactions_practice.sql | 0 .../2. Блокировки/DQL_blocking_prectice.sql | 0 .../3. Оптимизатор запросов/DQL_optimizer_pracitce.sql | 0 .../DQL_explain_analyze_practice.sql | 0 .../DQL_command_execution_order_practice.sql | 0 .../6. JOIN (физические)/DQL_physical_JOIN_practice.sql | 0 .../DDL_create_tables_for_pet_project.sql | 45 +++++++++++++++++++ 18 files changed, 45 insertions(+) rename mnoskov/postgresql/{ => SQL с нуля}/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/1. Основы/3. Фильтрация/DQL_filtration_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/2. JOIN/DQL_join_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/3. UNION/DQL_union_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/5. Функции: даты, строки, логика/DQL_functions_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/6. Подзапросы и CTE/DQL_cte_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/7. Оконные функции/DQL_window_functions_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/8. Представления и витрины данных/DQL_views_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql (100%) rename mnoskov/postgresql/{ => SQL с нуля}/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql (100%) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql diff --git a/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql b/mnoskov/postgresql/SQL с нуля/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql rename to mnoskov/postgresql/SQL с нуля/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DDL_create_first_tables.sql diff --git a/mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql b/mnoskov/postgresql/SQL с нуля/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql rename to mnoskov/postgresql/SQL с нуля/1. Основы/1. Создание_и_изменение_таблиц_и_данных/DML_first_inserts_in_tables.sql diff --git a/mnoskov/postgresql/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql b/mnoskov/postgresql/SQL с нуля/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql rename to mnoskov/postgresql/SQL с нуля/1. Основы/2. Базовый_SQL/DQL_basic_SQL_practice.sql diff --git a/mnoskov/postgresql/1. Основы/3. Фильтрация/DQL_filtration_practice.sql b/mnoskov/postgresql/SQL с нуля/1. Основы/3. Фильтрация/DQL_filtration_practice.sql similarity index 100% rename from mnoskov/postgresql/1. Основы/3. Фильтрация/DQL_filtration_practice.sql rename to mnoskov/postgresql/SQL с нуля/1. Основы/3. Фильтрация/DQL_filtration_practice.sql diff --git a/mnoskov/postgresql/2. JOIN/DQL_join_practice.sql b/mnoskov/postgresql/SQL с нуля/2. JOIN/DQL_join_practice.sql similarity index 100% rename from mnoskov/postgresql/2. JOIN/DQL_join_practice.sql rename to mnoskov/postgresql/SQL с нуля/2. JOIN/DQL_join_practice.sql diff --git a/mnoskov/postgresql/3. UNION/DQL_union_practice.sql b/mnoskov/postgresql/SQL с нуля/3. UNION/DQL_union_practice.sql similarity index 100% rename from mnoskov/postgresql/3. UNION/DQL_union_practice.sql rename to mnoskov/postgresql/SQL с нуля/3. UNION/DQL_union_practice.sql diff --git a/mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql b/mnoskov/postgresql/SQL с нуля/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql similarity index 100% rename from mnoskov/postgresql/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql rename to mnoskov/postgresql/SQL с нуля/4. Агрегации и группировки/DQL_aggregations&groupings_practice.sql diff --git a/mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql b/mnoskov/postgresql/SQL с нуля/5. Функции: даты, строки, логика/DQL_functions_practice.sql similarity index 100% rename from mnoskov/postgresql/5. Функции: даты, строки, логика/DQL_functions_practice.sql rename to mnoskov/postgresql/SQL с нуля/5. Функции: даты, строки, логика/DQL_functions_practice.sql diff --git a/mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql b/mnoskov/postgresql/SQL с нуля/6. Подзапросы и CTE/DQL_cte_practice.sql similarity index 100% rename from mnoskov/postgresql/6. Подзапросы и CTE/DQL_cte_practice.sql rename to mnoskov/postgresql/SQL с нуля/6. Подзапросы и CTE/DQL_cte_practice.sql diff --git a/mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql b/mnoskov/postgresql/SQL с нуля/7. Оконные функции/DQL_window_functions_practice.sql similarity index 100% rename from mnoskov/postgresql/7. Оконные функции/DQL_window_functions_practice.sql rename to mnoskov/postgresql/SQL с нуля/7. Оконные функции/DQL_window_functions_practice.sql diff --git a/mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql b/mnoskov/postgresql/SQL с нуля/8. Представления и витрины данных/DQL_views_practice.sql similarity index 100% rename from mnoskov/postgresql/8. Представления и витрины данных/DQL_views_practice.sql rename to mnoskov/postgresql/SQL с нуля/8. Представления и витрины данных/DQL_views_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/1. Транзакции/DQL_transactions_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/2. Блокировки/DQL_blocking_prectice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/3. Оптимизатор запросов/DQL_optimizer_pracitce.sql diff --git a/mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/4. EXPLAIN и EXPLAIN ANALYZE/DQL_explain_analyze_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/5. Порядок выполнения команд/DQL_command_execution_order_practice.sql diff --git a/mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql b/mnoskov/postgresql/SQL с нуля/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql similarity index 100% rename from mnoskov/postgresql/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql rename to mnoskov/postgresql/SQL с нуля/9. Оптимизация/6. JOIN (физические)/DQL_physical_JOIN_practice.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql new file mode 100644 index 0000000..68c5a40 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql @@ -0,0 +1,45 @@ +--DROP TABLE IF EXISTS users; +CREATE TABLE users ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name varchar(100) NOT NULL, + age SMALLINT, + email varchar(100), + registration_date timestamptz NOT NULL +); + +--DROP TABLE IF EXISTS courses; +CREATE TABLE courses ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + title varchar(150) NOT NULL, + category varchar(50), + created_at timestamptz NOT NULL +); + +--DROP TABLE IF EXISTS lessons; +CREATE TABLE lessons ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + course_id bigint NOT NULL, + title varchar(150) NOT NULL, + duration_min SMALLINT NOT NULL, + FOREIGN KEY (course_id) REFERENCES courses(id) +); + +--DROP TABLE IF EXISTS enrollments; +CREATE TABLE enrollments ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id bigint NOT NULL, + course_id bigint NOT NULL, + enrolled_at timestamptz NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (course_id) REFERENCES courses(id) +); + +--DROP TABLE IF EXISTS lesson_views; +CREATE TABLE lesson_views ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + user_id bigint NOT NULL, + lesson_id bigint NOT NULL, + viewed_at timestamptz NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (lesson_id) REFERENCES lessons(id) +); \ No newline at end of file From f9db7e0b8475cce90a451052e9ba5a5c8c9bd97b Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 17 Mar 2026 18:04:44 +0300 Subject: [PATCH 039/123] =?UTF-8?q?DML=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=82=20'=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82:=20=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BA=D1=83?= =?UTF-8?q?=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DML_insert_in_tables_for_pet_project.sql | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql new file mode 100644 index 0000000..7438241 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql @@ -0,0 +1,155 @@ +INSERT INTO users (name, age, email, registration_date) VALUES +('Alice',25,'alice@mail.com','2023-01-10'), +('Bob',30,'bob@gmail.com','2023-02-05'), +('Charlie',22,'charlie@mail.com','2023-02-20'), +('Diana',28,'diana@mail.com','2023-03-01'), +('Ethan',35,'ethan@gmail.com','2023-03-10'), +('Frank',29,'frank@mail.com','2023-01-05'), +('Grace',26,'grace@gmail.com','2023-01-12'), +('Henry',32,'henry@mail.com','2023-01-20'), +('Iris',24,'iris@gmail.com','2023-02-10'), +('Jack',27,'jack@mail.com','2023-04-15'), +('Kelly',23,'kelly@gmail.com','2023-04-20'), +('Leo',31,'leo@mail.com','2023-01-15'), +('Mia',26,'mia@gmail.com','2023-01-18'), +('Nina',28,'nina@mail.com','2023-01-08'), +('Oscar',33,'oscar@gmail.com','2023-01-10'), +('Paul',29,'paul@mail.com','2023-02-15'), +('Quinn',24,'quinn@gmail.com','2023-03-20'), +('Rita',27,'rita@mail.com','2023-04-01'), +('Sam',30,'sam@gmail.com','2023-04-10'), +('Tina',25,'tina@mail.com','2023-04-18'); + +INSERT INTO courses (title, category, created_at) VALUES +('SQL для начинающих','data','2023-01-01'), +('Python для анализа данных','programming','2023-01-15'), +('BI с нуля','business','2023-03-01'), +('Excel для аналитиков','business','2023-02-01'), +('Основы машинного обучения','data','2023-03-15'); + +INSERT INTO lessons (course_id, title, duration_min) VALUES +(1,'SELECT и FROM',10), +(1,'JOIN',15), +(1,'WHERE и фильтрация',12), +(1,'Агрегации и GROUP BY',18), +(1,'Подзапросы',25), +(2,'Pandas основы',20), +(2,'NumPy',15), +(2,'Визуализация данных',22), +(3,'Основы BI',12), +(3,'Метрики и дашборды',18), +(3,'Power BI введение',20), +(4,'Формулы в Excel',15), +(4,'Сводные таблицы',18), +(4,'Графики и диаграммы',14), +(5,'Введение в ML',20), +(5,'Линейная регрессия',30), +(5,'Классификация',28), +(5,'Кластеризация',25); + +INSERT INTO enrollments (user_id, course_id, enrolled_at) VALUES +(1,1,'2023-01-15'), +(1,2,'2023-02-01'), +(2,1,'2023-01-20'), +(3,2,'2023-03-05'), +(4,3,'2023-04-01'), +(6,1,'2023-01-06'), +(6,2,'2023-01-10'), +(6,4,'2023-02-05'), +(7,1,'2023-01-13'), +(7,3,'2023-03-10'), +(8,1,'2023-01-22'), +(8,2,'2023-01-22'), +(8,3,'2023-01-22'), +(9,4,'2023-02-12'), +(10,1,'2023-04-16'), +(11,2,'2023-04-21'), +(12,1,'2023-01-16'), +(13,2,'2023-01-19'), +(13,4,'2023-02-20'), +(14,1,'2023-01-09'), +(14,2,'2023-01-09'), +(14,3,'2023-01-09'), +(14,4,'2023-02-01'), +(14,5,'2023-03-20'), +(15,1,'2023-01-11'), +(15,3,'2023-03-05'), +(16,2,'2023-02-16'), +(16,4,'2023-02-16'), +(17,3,'2023-03-22'), +(17,5,'2023-03-25'), +(18,1,'2023-04-02'), +(19,1,'2023-04-11'), +(19,2,'2023-04-11'), +(20,2,'2023-04-19'), +(5,5,'2023-03-20'), +(1,4,'2023-02-10'), +(2,3,'2023-03-15'), +(3,1,'2023-02-25'), +(4,2,'2023-03-10'), +(6,3,'2023-03-08'), +(7,2,'2023-02-01'), +(9,1,'2023-02-15'), +(12,3,'2023-03-10'), +(15,2,'2023-02-01'), +(18,4,'2023-04-05'); + +INSERT INTO lesson_views (user_id, lesson_id, viewed_at) VALUES +(1,1,'2023-01-16 10:00:00'), +(1,2,'2023-01-16 10:15:00'), +(1,3,'2023-01-17 09:00:00'), +(1,4,'2023-01-17 09:30:00'), +(1,4,'2023-01-17 11:00:00'), +(1,4,'2023-01-18 10:00:00'), +(1,6,'2023-02-02 14:00:00'), +(1,12,'2023-02-15 10:00:00'), +(1,13,'2023-02-15 10:30:00'), +(2,1,'2023-01-21 09:00:00'), +(2,9,'2023-03-16 10:00:00'), +(2,10,'2023-03-16 10:30:00'), +(3,6,'2023-03-06 12:00:00'), +(3,1,'2023-02-26 11:00:00'), +(3,2,'2023-02-26 11:20:00'), +(4,9,'2023-04-02 10:00:00'), +(4,10,'2023-04-03 10:00:00'), +(4,6,'2023-03-12 14:00:00'), +(4,7,'2023-03-13 09:00:00'), +(5,15,'2023-03-21 10:00:00'), +(5,16,'2023-03-22 11:00:00'), +(6,1,'2023-01-07 10:00:00'), +(6,2,'2023-01-07 10:15:00'), +(6,3,'2023-01-08 09:00:00'), +(6,4,'2023-01-08 09:30:00'), +(6,5,'2023-01-09 10:00:00'), +(6,6,'2023-01-11 14:00:00'), +(6,7,'2023-01-12 10:00:00'), +(6,8,'2023-01-13 11:00:00'), +(6,12,'2023-02-06 09:00:00'), +(6,13,'2023-02-06 09:30:00'), +(6,14,'2023-02-07 10:00:00'), +(6,9,'2023-03-09 11:00:00'), +(7,1,'2023-01-14 10:00:00'), +(7,2,'2023-01-14 10:20:00'), +(7,3,'2023-01-15 09:00:00'), +(7,9,'2023-03-11 14:00:00'), +(7,10,'2023-03-11 14:30:00'), +(9,12,'2023-02-13 10:00:00'), +(9,13,'2023-02-13 10:30:00'), +(9,1,'2023-02-16 11:00:00'), +(12,1,'2023-01-17 10:00:00'), +(12,2,'2023-01-17 10:20:00'), +(12,1,'2023-04-10 11:00:00'), +(12,3,'2023-04-10 11:30:00'), +(12,9,'2023-03-12 10:00:00'), +(13,6,'2023-01-20 14:00:00'), +(13,7,'2023-01-21 10:00:00'), +(13,12,'2023-02-21 09:00:00'), +(14,1,'2023-01-09 08:00:00'), +(14,2,'2023-01-09 08:15:00'), +(14,3,'2023-01-10 10:00:00'), +(14,4,'2023-01-10 10:30:00'), +(14,5,'2023-01-11 09:00:00'), +(14,6,'2023-01-10 14:00:00'), +(14,7,'2023-01-11 10:00:00'), +(14,8,'2023-01-12 11:00:00'), +(14,9,'2023-01-10 08:00:00'); \ No newline at end of file From 9a3e660595319995e72a97c949fd8ffc54d21f29 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 17 Mar 2026 22:52:04 +0300 Subject: [PATCH 040/123] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20'=D0=90=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_user_activity_summary.sql | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql new file mode 100644 index 0000000..46cfb4d --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql @@ -0,0 +1,24 @@ +CREATE MATERIALIZED VIEW user_activity_summary AS +SELECT + u.id AS user_id, + u.name, + count(DISTINCT e.course_id) AS enrollment_count, + count(DISTINCT lv.lesson_id) AS viewed_lessons_count, + max(lv.viewed_at)::date AS last_view, + min(lv.viewed_at)::date AS first_view, + ((count(DISTINCT lv.lesson_id)::numeric(5,2) / count(DISTINCT l.id)) * 100)::numeric(5, 2) AS engagement_rate +FROM + users u +JOIN + enrollments e ON u.id = e.user_id +JOIN + courses c ON e.course_id = c.id +JOIN + lessons l ON c.id = l.course_id +LEFT JOIN + lesson_views lv ON u.id = lv.user_id -- сюда - 'AND l.id = lv.lesson_id' +GROUP BY + u.id, u.name +ORDER BY + u.id; +-- Кинул его в нейронку на проверку, она сказала что надо добавить 'AND l.id = lv.lesson_id' при присоединении таблицы lesson_views(сделал пометку в коде), это нужно чтобы точно проверить, что урок который посмотрел пользователь точно принадлежит его курсу, чтобы избежать ошибок из-за некорректных данные если такие появятся в результате какого либо сбоя или ошибки. \ No newline at end of file From 3b9c6393df33abc30fbbfcc009770a18cea394c9 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 18 Mar 2026 13:14:45 +0300 Subject: [PATCH 041/123] =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=20'=D0=AD=D1=84=D1=84=D0=B5=D0=BA=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BA=D1=83=D1=80=D1=81=D0=BE?= =?UTF-8?q?=D0=B2'=20=D0=B4=D0=BB=D1=8F=20'=D0=9F=D0=B5=D1=82-=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD-=D0=BA=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_courses_effectivity.sql | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql new file mode 100644 index 0000000..4e670fc --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql @@ -0,0 +1,41 @@ +CREATE MATERIALIZED VIEW course_summary AS +WITH courses_unique_users AS ( + SELECT + e.course_id, + count(e.user_id) AS unique_users + FROM + enrollments e + GROUP BY + e.course_id +), +lessons_results AS ( + SELECT + l.course_id, + count(distinct l.id) AS lessons_count, + count(lv.lesson_id) AS total_views, + min(lv.viewed_at)::date AS first_viewed_at, + max(lv.viewed_at)::date AS last_viewed_at + FROM + lessons l + LEFT JOIN + lesson_views lv ON l.id = lv.lesson_id + GROUP BY + l.course_id +) +SELECT + c.id AS course_id, + c.title AS course_title, + cuu.unique_users, + lr.lessons_count, + lr.total_views, + round(lr.total_views / cuu.unique_users::numeric, 2) AS avg_views_per_user, + lr.first_viewed_at, + lr.last_viewed_at +FROM + courses c +JOIN + courses_unique_users cuu ON c.id = cuu.course_id +JOIN + lessons_results lr ON c.id = lr.course_id +ORDER BY + c.id; \ No newline at end of file From c5381da2481e094f9ad1dd821d0b2c2d5e0f5bcd Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 20 Mar 2026 12:56:57 +0300 Subject: [PATCH 042/123] =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=20'=D0=9E=D0=B1=D1=89=D0=B0=D1=8F=20=D0=BE=D1=82=D1=87?= =?UTF-8?q?=D0=B5=D1=82=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D1=8E'=20=D0=B4=D0=BB=D1=8F=20'=D0=9F=D0=B5?= =?UTF-8?q?=D1=82-=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20=D0=9E=D0=BD?= =?UTF-8?q?=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BA=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_reporting_on_interactions.sql | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql new file mode 100644 index 0000000..32fbe79 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql @@ -0,0 +1,48 @@ +CREATE MATERIALIZED VIEW platform_summary as +WITH users_count AS ( + SELECT + count(*) AS total_users + FROM + users +), +courses_count AS ( + SELECT + count(*) AS total_courses + FROM + courses +), +lessons_count AS ( + SELECT + count(*) AS total_lessons + FROM + lessons +), +users_with_views AS ( + SELECT + count(distinct user_id) AS users_with_views + FROM + lesson_views +), +views_count AS ( + SELECT + count(*) AS total_views + FROM + lesson_views lv +) +SELECT + uc.total_users, + cc.total_courses, + lc.total_lessons, + uwv.users_with_views, + round(lc.total_lessons / cc.total_courses::NUMERIC, 2) AS avg_lessons_per_course, + round(vc.total_views / lc.total_lessons::NUMERIC, 2) AS avg_views_per_lesson +FROM + users_count uc +CROSS JOIN + courses_count cc +CROSS JOIN + lessons_count lc +CROSS JOIN + users_with_views uwv +CROSS JOIN + views_count AS vc; \ No newline at end of file From 3e09a2cbc769d5e80a64f750cb482848722337cb Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 21 Mar 2026 19:49:00 +0300 Subject: [PATCH 043/123] =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=20'=D0=9A=D0=BE=D0=B3=D0=BE=D1=80=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9'=20=D0=B4=D0=BB=D1=8F=20'=D0=9F=D0=B5=D1=82-=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD-=D0=BA=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_cohort_analysis_of_users.sql | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql new file mode 100644 index 0000000..19b8668 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql @@ -0,0 +1,40 @@ +CREATE MATERIALIZED VIEW user_cohort_analysis AS +WITH users_and_enrollments_results AS ( + SELECT + date_trunc('month', u.registration_date)::date AS cohort_month, + count(DISTINCT u.id) AS users_in_cohort, + count(DISTINCT e.user_id) AS enrolled_users, + count(DISTINCT e.course_id) courses_in_cohort + FROM + users u + JOIN + enrollments e ON u.id = e.user_id + GROUP BY + cohort_month +), +lesson_views_results AS ( + SELECT + date_trunc('month', u.registration_date)::date AS cohort_month, + count(DISTINCT lv.user_id) AS active_users, + round(avg(lv.viewed_at::date - u.registration_date::date), 0) AS avg_time_to_first_view + FROM + users u + LEFT JOIN + lesson_views lv ON u.id = lv.user_id + GROUP BY + cohort_month +) +SELECT + uer.cohort_month, + uer.users_in_cohort, + uer.enrolled_users, + lvr.active_users, + round((lvr.active_users / uer.users_in_cohort::numeric) * 100, 2) || '%' AS retention_rate, + round(uer.courses_in_cohort / uer.users_in_cohort::numeric, 2) AS avg_courses_per_user, + lvr.avg_time_to_first_view +FROM + users_and_enrollments_results uer +JOIN + lesson_views_results lvr ON uer.cohort_month = lvr.cohort_month +ORDER BY + uer.cohort_month; \ No newline at end of file From f7e6b757dfe3b7e664e61ff168dba4b0ddd7bd6e Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 21 Mar 2026 20:49:34 +0300 Subject: [PATCH 044/123] =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=20'=D0=9F=D0=BE=D0=BF=D1=83=D0=BB=D1=8F=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=BA=D1=83=D1=80=D1=81=D0=BE=D0=B2?= =?UTF-8?q?'=20=D0=B4=D0=BB=D1=8F=20'=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-?= =?UTF-8?q?=D0=BA=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_courses_popularity.sql | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql new file mode 100644 index 0000000..3377eb5 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql @@ -0,0 +1,39 @@ +CREATE MATERIALIZED VIEW lesson_popularity_ranking as +WITH lesson_views_results AS ( + SELECT + lv.lesson_id, + count(lv.user_id) AS total_views, + count(DISTINCT lv.user_id) AS unique_viewers + FROM + lesson_views lv + GROUP BY + lv.lesson_id +), +enrollments_results AS ( + SELECT + e.course_id, + count(e.user_id) AS enrolled_to_course + FROM + enrollments e + GROUP BY + e.course_id +) +SELECT + l.id AS lesson_id, + l.title AS lesson_title, + c.title AS course_title, + lvr.total_views, + lvr.unique_viewers, + round(lvr.total_views / lvr.unique_viewers::numeric, 2) AS avg_views_per_viewer, + dense_rank() OVER (ORDER BY lvr.total_views desc) AS popularity_rank, + round((lvr.unique_viewers / er.enrolled_to_course::numeric) * 100, 0) || '%' AS completion_rate +FROM + lessons l +JOIN + courses c ON l.course_id = c.id +JOIN + lesson_views_results lvr ON l.id = lvr.lesson_id +JOIN + enrollments_results er ON c.id = er.course_id +ORDER BY + popularity_rank; \ No newline at end of file From ce82641efbca80929de1f86beaf85a7f7452ad46 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 22 Mar 2026 15:08:45 +0300 Subject: [PATCH 045/123] =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=20'=D0=92=D0=BE=D1=80=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8'=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20'=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82:=20=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BA?= =?UTF-8?q?=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_materialized_view_conversion_funnel.sql | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql new file mode 100644 index 0000000..6b208c0 --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql @@ -0,0 +1,114 @@ +WITH stage_courses AS ( + -- registered + (SELECT + 0 AS course_id, + 1 AS stage_id, + 'registered' AS stage_name, + count(DISTINCT u.id) AS users_count + FROM + users u) + UNION ALL + -- enrolled + (SELECT + e.course_id, + 2 AS stage_id, + 'enrolled' AS stage_name, + count(DISTINCT e.user_id) AS users_count + FROM + enrollments e + GROUP BY + e.course_id + ORDER BY + e.course_id) + UNION ALL + -- started + (SELECT + l.course_id, + 3 AS stage_id, + 'started' AS stage_name, + count(DISTINCT lv.user_id) AS users_count + FROM + lessons l + JOIN + lesson_views lv ON l.id = lv.lesson_id + GROUP BY + l.course_id + ORDER BY + l.course_id) + UNION ALL + -- mid_progress + (WITH lessons_mid AS ( + SELECT + l.course_id, + ceil(percentile_cont(0.5) WITHIN GROUP (ORDER BY l.id)) AS mid_in_lesson_id + FROM + lessons l + GROUP BY + l.course_id + ) + SELECT + lm.course_id, + 4 AS stage_id, + 'mid_progress' AS stage_name, + count(lv.user_id) AS users_count + FROM + lessons_mid lm + LEFT JOIN + lesson_views lv ON lm.mid_in_lesson_id = lv.lesson_id + GROUP BY + lm.course_id, lm.mid_in_lesson_id + ORDER BY + lm.course_id) + UNION ALL + -- completed + (WITH courses_lessons_id AS ( + SELECT + l.course_id, + l.id AS lesson_id + FROM + lessons l + GROUP BY + l.course_id, l.id + ), + lessons_numbers AS ( + SELECT + cli.course_id, + cli.lesson_id, + ROW_NUMBER() OVER(PARTITION BY cli.course_id ORDER BY cli.lesson_id DESC) AS lesson_number + FROM + courses_lessons_id cli + ) + SELECT + ln.course_id, + 5 AS stage_id, + 'completed' AS stage_name, + count(lv.user_id) AS users_count + FROM + lessons_numbers ln + LEFT JOIN + lesson_views lv ON ln.lesson_id = lv.lesson_id + WHERE + ln.lesson_number = 1 + GROUP BY + ln.course_id, ln.lesson_id + ORDER BY + ln.course_id) +) +SELECT + sc.course_id, + c.title AS course_title, + sc.stage_id, + sc.stage_name, + sc.users_count, + CASE + WHEN lag(sc.users_count) OVER (PARTITION BY sc.course_id ORDER BY sc.stage_id) IS NULL THEN 'first stage' + WHEN lag(sc.users_count) OVER (PARTITION BY sc.course_id ORDER BY sc.stage_id) = 0 THEN 0::text || '%' + WHEN lag(sc.users_count) OVER (PARTITION BY sc.course_id ORDER BY sc.stage_id) IS NOT NULL THEN round((sc.users_count / lag(sc.users_count) OVER (PARTITION BY sc.course_id ORDER BY sc.stage_id)::NUMERIC) * 100, 0)::text || '%' + END AS conversion_from_previous, + round((sc.users_count / FIRST_VALUE(sc.users_count) OVER (ORDER BY sc.stage_id)::NUMERIC) * 100, 0)::text || '%' AS conversion_from_start +FROM + courses c +RIGHT JOIN + stage_courses sc ON c.id = sc.course_id +ORDER BY + sc.course_id \ No newline at end of file From d9c76de8833bf31b2569c8ef5a1121488de71c04 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 22 Mar 2026 15:49:29 +0300 Subject: [PATCH 046/123] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20'=D0=92?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BD=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=81=D0=B8=D0=B8'=20=D0=B4=D0=BB=D1=8F=20'=D0=9F?= =?UTF-8?q?=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20=D0=9E?= =?UTF-8?q?=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BA=D1=83=D1=80=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql index 6b208c0..bade9b5 100644 --- a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql @@ -97,7 +97,6 @@ WITH stage_courses AS ( SELECT sc.course_id, c.title AS course_title, - sc.stage_id, sc.stage_name, sc.users_count, CASE From ac156fae0f70c384e32465cbd21f8feb3a5ab480 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 22 Mar 2026 18:02:49 +0300 Subject: [PATCH 047/123] =?UTF-8?q?=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20'?= =?UTF-8?q?=D0=92=D0=BE=D1=80=D0=BE=D0=BD=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8'=20=D0=B4=D0=BB=D1=8F=20'?= =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20?= =?UTF-8?q?=D0=9E=D0=BD=D0=BB=D0=B0=D0=B9=D0=BD-=D0=BA=D1=83=D1=80=D1=81?= =?UTF-8?q?=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql index bade9b5..9800c9f 100644 --- a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql @@ -1,3 +1,4 @@ +CREATE MATERIALIZED VIEW conversion_funnel AS WITH stage_courses AS ( -- registered (SELECT @@ -110,4 +111,4 @@ FROM RIGHT JOIN stage_courses sc ON c.id = sc.course_id ORDER BY - sc.course_id \ No newline at end of file + sc.course_id; \ No newline at end of file From cbaf0b1f4ad093eac6eb0e81f565b375066db777 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 22 Mar 2026 19:59:09 +0300 Subject: [PATCH 048/123] =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B2=D1=81=D0=B5=20=D0=94=D0=BE=D0=BF.=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3. Доп. задания/DQL_additional_tasks.sql | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql new file mode 100644 index 0000000..49c296d --- /dev/null +++ b/mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql @@ -0,0 +1,51 @@ +-- Задание 1 +SELECT + * +FROM + conversion_funnel +WHERE + stage_name = 'started' +ORDER BY + conversion_from_previous; +-- Первые 2 курса, гже отавлилось больше всего учеников между этапами enrolled → started + + +-- Задание 2 +SELECT + * +FROM + lesson_popularity_ranking lpr +WHERE + lpr.avg_views_per_viewer > 1 +LIMIT 3; + + +-- Задание 3 +SELECT + * +FROM + user_cohort_analysis uca +ORDER BY + retention_rate DESC; +-- Не разобрался как можжно соеденить с другими таблицам +-- А retention_rate такой, наверное потому что при запуске курсов была пиар компания большая, и делали рассылки с напоминанием позаниматься, а потом перестали, вот и удержание похерилось + + +-- Задание 4 +-- Витрина - user_activity_summary +-- Всяко разно попробовал подобавлять индексы, итог: без индексов быстрее. Просмотрел весь запрос, вроде ничего не нужно менять - лишнего ничего нет. + +-- Витрина - course_summary +-- Тут также как и в предидущем - и так и сяк попробовал, план запроса не меняется, время не улучшается + +-- Витрина - platform_summary +-- Тут тоже нечего менять - всё из CROSS JOIN. Попробовал вместо count(*) писать count(id) думал мол в ширину будет меньше читать, в итоге выяснил что и так и так одинаково читает + +-- Витрина - user_cohort_analysis +-- И тут че не делай, все одна фигня. План вроде хороший. + +-- Витрина - lesson_popularity_ranking +-- Тут тоже ничего не вышло - я начниаю чувствовать себя тупым от того что не могу улучшить нигде время запроса. + +-- Витрина - conversion_funnel +-- Здесь тоже ничего не вышло - со стороны план запроса выглядит хорошо. Ускорить время запроса не вышло. \ No newline at end of file From 783c87592556d0f84dc23b8a3f2f82768abf0b67 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 27 Mar 2026 15:47:16 +0300 Subject: [PATCH 049/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1. Индексы/practice_indexes.sql | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 mnoskov/Теория БД/1. Ускорение и масштабирование/1. Индексы/practice_indexes.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/1. Индексы/practice_indexes.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/1. Индексы/practice_indexes.sql new file mode 100644 index 0000000..d29f3e7 --- /dev/null +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/1. Индексы/practice_indexes.sql @@ -0,0 +1,111 @@ +/*Задание 1: По каким полям делать индексы(тут говорится смотреть на таблицы из пет проекта 'Онлайн магазины', надеюсь смотреть на таблицы а не на витрины): + 1) По каким полям, как тебе кажется, реальный продукт‑менеджер или аналитик будет чаще всего: + where: + users: age, email, registration_date, + enrollments: enrolled_at + courses: title, category + lessons: title, duration_min + lesson_views: viewed_at + order by: + users: id, age, registration_date + enrollments: user_id, enrolled_at + courses: id + lessons: id, duration_min + lesson_views: user_id, viewed_at + group by: + users: age, registration_date + enrollments: user_id, course_id, enrolled_at + courses: category + lessons: course_id + lesson_views: user_id, lesson_id + join: + users: id + enrollments: user_id, course_id + courses: id + lessons: id, course_id + lesson_views: user_id, lesson_id + 2) Для каждого поля, которое ты назвал, укажи: «индекс нужен / индекс не нужен» и коротко объясни почему (часто фильтруем, редко фильтруем, мало разных значений и т.п.). + users: + id(есть автоматически), + age(не нужен, мало разнах значений), + email(нужен, чтобы найти пользователя через where) + registration_date(нужен, часто будет сравниваться в BETWEEN наприме, и сортироваться) + enrollments: + user_id(нужен, будет часто джоиниться), + course_id(нужен, будет часто джоиниться), + enrolled_at(нужен, часто будет сравниваться в BETWEEN например, и сортироваться) + courses: + id(есть автоматически), + title(не нужен, мало разных значений), + category(не нужен, мало разных значений) + lessons: + id(есть автоматически), + course_id(нужен, будет часто джоиниться), + title(не нужен, мало разных значений), + duration_min(мне кажется не нужен, тк не очень часто будет использоваться) + lesson_views: + user_id(нужен, будет часто джоиниться), + lesson_id(нужен, будет часто джоиниться) + viewed_at(нужен, часто будет сравниваться в BETWEEN например, и сортироваться)*/ + +-- Задание 2: Свяжи запрос и индекс: + -- 1) + SELECT * FROM users WHERE email = 'какой-то email'; -- индекс на email ускорит where + -- 2) + SELECT * FROM users WHERE is_active = true AND created_at >= CURRENT_DATE - INTERVAL '1 year'; -- индекс на created_at ускоряет where + -- 3) + SELECT * FROM products WHERE category = 'Электроника' ORDER BY price; -- индекс по price ускорит order by + -- 4) + SELECT * FROM orders WHERE user_id = 1 AND order_date >= CURRENT_DATE - INTERVAL '30 days'; -- индекс на user_id и order_date ускорят where + -- 5) + SELECT order_id, product_id FROM order_items; -- на у тут по идее составной создается сразу, так как тут pkey составной. Ну и они ускоряют select + +/* Задание 3. Порядок столбцов в индексе + 1) Я бы сделал orders(order_date, user_id). + 2) Для первых двух будет работать четко, а для третьего хуже. Тк у третьего нет фильрации по order date, и он стоит вторым в индексе + 3) + * Когда один индекс - хорошо с фильтрацией по нескольким полям, и когда первое поле указанное в индексе используется часто, а второе нет. + * Когда индекса два - хорошо когда оба поля фильтруются по отдельности*/ + +/* Задание 4. Лишние индексы в наших таблицах + !!! Я тут не буду писать индексы которые создаются автоматически из за pkey + users: + Скорее нужен индекс: + email, created_at(нужны для where, и order by) + Скорее не нужен индекс: + name, age, country, is_active(либо мало рахных значений, либо не очень востребованы эти поля) + products: + Скорее нужен индекс: + price (для order by и where) + Скорее не нужен индекс: + name, ctegory, in_stock(либо мало разных значений, либо не очень востребованы эти поля) + orders: + Скорее нужен индекс: + user_id(для join), price(для order by и where) + Скорее не нужен индекс: + name, category, in_stock(либо мало разных значений, либо не очень востребованы эти поля) + order_items: + Скорее нужен индекс: + price_at_purchase (для order by и where) + Скорее не нужен индекс: + quantity(мало разных значения) + + Много индексов не есть хорошо, потому что они тоже занимают помять и их тоже нужно считать. Если их бездумно везде понатыкать то запрос может выполняться дольше.*/ + +/* Задание 5. Что должно быть уникальным + 1) id, email + 2) id, article_number(придумал вот такое) + 3) + * Если не будет уникаьности по id, то возникнут проблемы бд, что у разных пользователей будет один id и это жопа, ничего невозможно фильтровать и тд. + * Если email будет повторяться, то как вообще пользователи будут входить в аккаунт, и как делать персонализированную рассылку? Никак. + * Если article_number будет повторяться, то те же проблемы что и с id, будет нарушение логики работы бд. + + * Уникальный индекс обезопасит бд от сбоев и некорректной работы.*/ + +-- Задание 6. Где индексы помогут, а где нет + SELECT * FROM users WHERE id = 100; -- индекс уже создан тк это pkey + SELECT * FROM users WHERE email = 'user@example.com'; -- нужен, поможет с where, быстрее будет рабоать фильтрация + SELECT * FROM products WHERE price BETWEEN 100 AND 500; -- нужн, поможет с where, быстрее будте фильтрация + SELECT * FROM products WHERE name LIKE '%phone%'; -- не нужен, индекс не рвботает с like + SELECT * FROM orders WHERE status = 'DELIVERED'; -- не нужен, в status мало разных значений + SELECT * FROM order_items WHERE order_id = 123; -- нужен, поможет с where, быстрее будет рабоать фильтрация \ No newline at end of file From 6b1c388c710bec95dca1b33ae76b2eb37c7085df Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 29 Mar 2026 21:15:04 +0300 Subject: [PATCH 050/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=9F=D0=B0=D1=80=D1=82=D0=B8=D1=86=D0=B8?= =?UTF-8?q?=D0=B8'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Партиции/practice_partitions.sql | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql new file mode 100644 index 0000000..5445669 --- /dev/null +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql @@ -0,0 +1,190 @@ +/* Задание 1. Какой вид партиционирования выбрать + 1) + * range по полю order_date + * запросы с between в год + 1. where order_date between '2022-01-01' and '2023-01-01' + 2. where extract('year' from order_date) = '2025' + 3. where order_date < '2026-01-01' and order_date > '2025-01-01' + 2) + * тут два типа партиционирования подходят. range по created_at или list по country. + * если выбирать list по country то плюс - это при фильтрах по стране будут отсекаться лишние партиции, а минус, что год/месяц регистрации не будет разделен по партициям. + 3) + * логично range по created_at - чтобы разделить по месяцам/годам, а hash по user_id - почему так, точно сказать не могу, но в теории сказано что hash используется когда нет диапазона или списка и это частый сценарий для логов + * с range выиграют запросы с фильтрами по created_at, а с hash выигают запросы с фильтрами по user_id, а все что не указаны как поле для партиций, не почувствуют разницы.*/ + + +/* Задание 2. Размер партиций + 2) количество партициый за 10 лет по: + * годам - 10, тут вообще все четко, и удалять не сложно, и поддержка проще, и запросы будут хорошо делаться + * месяцам - 120, тут с удалением тарых данных труднее, поддержка тяжелая, но скорость запросов будет лучше, если фильтрация будет по месяцам + * дням - примерно 3650, а это я чувствую пиздец, с удалением и поддержкой жопа, и со скоростью запросов тоже жопа - данные будут читаться из кучи партиций что не есть хорошо + 3) + Я думаю прям для очень большого магазина оптимальнее будет партиционирование по месяцам - тк очень много покупателей и большинство статистик будет по месяцам, по годам тоэе конечно, но думаю приоритетнее по месяцам*/ + + +-- Задание 3. RANGE‑партиции по дате заказов +DROP TABLE IF EXISTS orders_part; +CREATE TABLE orders_part ( + id bigint, + user_id bigint, + order_date timestamptz NOT NULL, + status varchar(20), + PRIMARY KEY (id, order_date), + FOREIGN KEY (user_id) REFERENCES users_1(id) +) PARTITION BY RANGE (order_date); + +CREATE TABLE orders_2024 PARTITION OF orders_part +FOR VALUES FROM ('2024_01-01') TO ('2025-01-01'); + +CREATE TABLE orders_2025 PARTITION OF orders_part +FOR VALUES FROM ('2025_01-01') TO ('2026-01-01'); + +CREATE TABLE orders_2026 PARTITION OF orders_part +FOR VALUES FROM ('2026_01-01') TO ('2027-01-01'); + +-- сделал insert который попросил сгенерировать нейронку + +EXPLAIN ANALYZE +SELECT + * +FROM + orders_part +WHERE + order_date BETWEEN '2025-01-01' AND '2025-12-31 23:59:59'; -- пришлось сделать так, тк дата 2026-01-01 тоже есть в таблице +-- в этом запросе за год - читатется только одна партиция - orders_2025, НО если в условии все же написать AND '2026-01-01', то читаются две партиции orders_2025 и orders_2026 и время выполнения запроса намного больше тк выполняется full scan обеих таблиц + +EXPLAIN ANALYZE +SELECT + * +FROM + orders_part +WHERE + EXTRACT('year' FROM order_date) IN ('2024', '2025'); -- так читаются все три партиции, 2026 почему-то тоже захватывается, но по итогу после сканирование у таблицы orders_2026 - rows = 0 + +EXPLAIN ANALYZE +SELECT + * +FROM + orders_part; +-- так просто сканируются все три партиции, но без фильтров, как было в предидущем варианте + + +-- Задание 4. LIST‑партиции по стране пользователей +DROP TABLE IF EXISTS users_part; +CREATE TABLE users_part ( + id int8 NOT NULL, + "name" varchar(100) NOT NULL, + age int2 NULL, + email varchar(100) NULL, + country bpchar(2) NULL, + is_active bool NULL, + created_at timestamptz NOT NULL, + PRIMARY KEY (id, country) +) PARTITION BY LIST (country); + +CREATE TABLE users_ru PARTITION OF users_part +FOR VALUES IN ('RU'); +CREATE TABLE users_us PARTITION OF users_part +FOR VALUES IN ('US'); +CREATE TABLE users_gb PARTITION OF users_part +FOR VALUES IN ('GB'); +CREATE TABLE users_de PARTITION OF users_part +FOR VALUES IN ('DE'); +CREATE TABLE users_fr PARTITION OF users_part +FOR VALUES IN ('FR'); +CREATE TABLE users_jp PARTITION OF users_part +FOR VALUES IN ('JP'); +CREATE TABLE users_uk PARTITION OF users_part +FOR VALUES IN ('UK'); +CREATE TABLE users_kz PARTITION OF users_part +FOR VALUES IN ('KZ'); + +-- сделал insert который попросил сгенерировать нейронку + +EXPLAIN ANALYZE +SELECT + * +FROM + users_part +WHERE + country = 'RU' +-- запрос скаинровал только партицию users_ru + +EXPLAIN ANALYZE +SELECT + * +FROM + users_part +WHERE + country IN ('RU', 'KZ') +-- запрос сканировал только партиции users_ru и users_kz + +EXPLAIN ANALYZE +SELECT + * +FROM + users_part +WHERE + age BETWEEN 44 AND 66; +-- сканировал не просто users_part, а каждую партицию по отдельности + + +-- Задание 5. HASH‑партиции по user_id в логах +DROP TABLE IF EXISTS user_logs_part; +CREATE TABLE user_logs_part ( + id int8, + user_id bigint NOT NULL, + created_at timestamp NOT null, + event_type text NOT NULL, + payload jsonb, + PRIMARY KEY (id, user_id) +) PARTITION BY hash (user_id) + +CREATE TABLE user_logs_p1 PARTITION OF user_logs_part +FOR VALUES WITH (modulus 4, remainder 0); + +CREATE TABLE user_logs_p2 PARTITION OF user_logs_part +FOR VALUES WITH (modulus 4, remainder 1); + +CREATE TABLE user_logs_p3 PARTITION OF user_logs_part +FOR VALUES WITH (modulus 4, remainder 2); + +CREATE TABLE user_logs_p4 PARTITION OF user_logs_part +FOR VALUES WITH (modulus 4, remainder 3); + +-- сделал insert который попросил сгенерировать нейронку + +EXPLAIN ANALYZE +SELECT * FROM user_logs_part WHERE user_id = 228; +-- читает только user_logs_p2 + +EXPLAIN ANALYZE +SELECT * FROM user_logs_part WHERE created_at >= current_date - INTERVAL '7 days'; +-- читает все партиции + +-- Задание 6 +-- email, чтобы искать пользователя быстрее, created_at для сортировки и where +CREATE UNIQUE INDEX idx_users_ru_email ON users_ru(email); +CREATE INDEX idx_users_ru_created_at ON users_ru(created_at); + +CREATE UNIQUE INDEX idx_users_us_email ON users_us(email); +CREATE INDEX idx_users_us_created_at ON users_us(created_at); + +CREATE UNIQUE INDEX idx_users_gb_email ON users_gb(email); +CREATE INDEX idx_users_gb_created_at ON users_gb(created_at); + +CREATE UNIQUE INDEX idx_users_de_email ON users_de(email); +CREATE INDEX idx_users_de_created_at ON users_de(created_at); + +CREATE UNIQUE INDEX idx_users_fr_email ON users_fr(email); +CREATE INDEX idx_users_fr_created_at ON users_fr(created_at); + +CREATE UNIQUE INDEX idx_users_jp_email ON users_jp(email); +CREATE INDEX idx_users_jp_created_at ON users_jp(created_at); + +CREATE UNIQUE INDEX idx_users_uk_email ON users_uk(email); +CREATE INDEX idx_users_uk_created_at ON users_uk(created_at); + +CREATE UNIQUE INDEX idx_users_kz_email ON users_kz(email); +CREATE INDEX idx_users_kz_created_at ON users_kz(created_at); +-- должны ускориться запросы с поиском пользователей по email, и запросы с сортировкой по дате или с фильтрацией по интервалам \ No newline at end of file From ef0ab61829cc422e990b684cd70abcc0ec2ff183 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 29 Mar 2026 22:31:46 +0300 Subject: [PATCH 051/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A8=D0=B0=D1=80=D0=B4=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3. Шарды/practice_sharding.sql | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 mnoskov/Теория БД/1. Ускорение и масштабирование/3. Шарды/practice_sharding.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/3. Шарды/practice_sharding.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/3. Шарды/practice_sharding.sql new file mode 100644 index 0000000..c3f5e12 --- /dev/null +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/3. Шарды/practice_sharding.sql @@ -0,0 +1,19 @@ +-- Задание 1. Выбор ключа шардирования +-- Ну я тут чето нихера не понял +-- Вот users лучше всего шардировать по id, а с orders непонятно. Я не до конца понял логику шардирования, если orders и users на разных шардах, то наверное лучше шардировать по user_id. Но в тоже время логично было бы сделать по order_date. +-- Короче я очень плохо понял шардирование, сейчас поищу объяснение на ютубчике и напишу новый ответ. +-- В нашем случае мы получается обсуждаем горизонтальное шардирование +-- Как я понял, если шардируются две таблицы, то они шардирются по одному полю у нас это users(id) и orders(user_id). +-- Я выбрал users(id), тк это уникальное поле и есть join по нему в orders - user_id +-- Шардирование будет происходить по хэшу. Быстрее будут выполняться запросы в пределах одного шарда, а запросы где данные берутся из разных шардов да ещё и join - явно станут сложнее. + +-- Задание 2. Добавление нового шарда +-- Проблемы: мне кажется временная приостановка записи и чтения таблиц на время 'перешардирования'. +-- План: 1. остановить запись и чтение данных; 2. создать 8 новых шардов; 3. перераспределить данные из старых четырех в восемь новых шардов; 4. удалить старые 4 шарда; 5. поменять логику изменения и чтение в бэк энде например 6. разрешить запись и чтение. конец + +-- Задание 3. Шардинг против партиционирования +-- При партицировании таблицы данные хранятся на одной бд, внутри одной таблицы, но эта таблица внутри себя разделена на партиции для ускорения запросов к ней. +-- При шардировании таблицы данные хранятся на разных серверах, то есть таблица делится на части(при горизонтальном шардировании), и каждая часть таблицы отправляется на разные сервер. + +-- Пример партицирования без шардирования: когда сервер не переполнен, но таблица слишком большая, и и запросы к ней замедляются, тогда таблица делится на партиции, для более быстрого выполнения запросов к ней. +-- Пример когда без шардирования не обойтись: это когда сервер перегружен, и физически невозможно добавлять новые данные и чтение таблиц очень медленное, тк для этого чтения сервер не может выделять должное количество ресурсов. \ No newline at end of file From eb89783316eba12bcf0170eb4a0fc614fc54eade Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 30 Mar 2026 11:37:31 +0300 Subject: [PATCH 052/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A0=D0=B5=D0=BF=D0=BB=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../4. Репликация/practice_replication.sql | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 mnoskov/Теория БД/1. Ускорение и масштабирование/4. Репликация/practice_replication.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/4. Репликация/practice_replication.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/4. Репликация/practice_replication.sql new file mode 100644 index 0000000..d9adc7e --- /dev/null +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/4. Репликация/practice_replication.sql @@ -0,0 +1,40 @@ +-- Задание 1. Где и зачем понадобится репликация +-- На примере онлайн-магазина +-- Пример 1: мастер сервер перестал работать(по любым причинам), и чтобы не останавливать работу магазина - делаем реплику мастером. Если бы небыло репликации работа магазина приостановилась бы на какое время. +-- Пример 2: на мастер сервере даже без чтения высокая нагрузка с инсертами, апдейтами, а чтение происходит с реплики, например отчеты для аналитиков читаются с реплики чтобы не перегружать мастер. Если убрать репликацию, то сервер будет один и может перегрузиться и упасть. +-- Пример 3: я сдеал первыве два примера настоллько обобщенными, что они покрываются все более конкретные примеры, и я не могу придумать пример 3. + + +-- Задание 2. Сравнение синхронной и асинхронной репликации +-- * как ведёт себя запись при нормальной работе; +-- * синхронная репликация - мастер ждет пока реплика завершит копирование, наверное блокирует строку в таблице как в тарнзакции строка блокируется пока транзакция не завершится. +-- * асинхронная репликация - мастер ничего не ждет - его главная задача, что он отправил даные в реплику, и реплика уже пусть сама разбирается и сохраняетс всё. +-- * что происходит при падении мастера; +-- * я думаю при обоих видах репликаций - реплика становится мастером. +-- * какие риски потери/отставания данных есть. +-- * с синхронной - рисков с отставанием и потерями вроде нет. +-- * в случае с асинхронной - реплика может не успеть сохранить что-то одно, как придет уже что-то второе в туже строку например, или может произойти какой-то сбой, и данные прийдут некорректно - получается транзакция просто не откатится, а будет как будет. + +-- платежному сервису нужен синхронный вид репликации, тк ошибки и неточность недопустимы. +-- аналитической витрины нужен асинхронный вид репликации, тк он быстрее, и небольшая неточность не критична. + + +-- Задание 3. Логическая и физическая репликация +-- * думаю логическая репликация это больше про то, что мы видим и делаем сами, по типу sql запрос написать, а физическая репликация - это про то, что происходит на уровне субд/сервера, что данные перетекают байтах, WAL-журналов(я погуглил что-это - это грубо говоря план изменений, но пока не сами изменения) +-- Оказывается про логические физические репликации я тоже ничего не понял, сейчас прогуглю, и напишу ответ заново. +-- * у логической репликации изменения передаются как sql запросы, для отдельных талиц например, а физическаая репликация изменяется побайтово создавая полную копию мастера +-- * у логической репликации высокая гибкость, можно выбирать что обновлять, а что нет, а у физической репликации нулевая гибкость - создается просто копия мастера 1 в 1 +-- * как я понял для миграция лучше всего логическая репликация - тк высокая гибкость, а физическая подходит для резервной копии + +-- * для физической репликации - банковская система, тк физичская быстрее и копия 1 в 1 делается, а для логическрй репликации я не могу ничего придумать. + + +-- Задание 4. Схема один мастер — несколько реплик для чтения +-- * на мастер отправлять строго unsrt update delete только с изменениями, а на реплики select, только с чтением +-- * проблемы из-за отсавания: 1. пользователь обновил фото профиля, а изменилось оно на странице толькко через 1-2 минуты; 2. один пользователь купил последний товар, а реплика не успела обновиться, и другой пользователь тоже попытался заказать этот товар, и у него вылезла ошибка, получается что товар отображается в наличии а купить нельзя; 3. в банках если будут отставать то там вообще жопа будет со счетами, но там получается синхронная репликация. +-- * наверное можно закрыть доступ на изменения для строк которые ещё не до конца реплицировались + +-- Задание 5. Мульти‑мастер: когда это оправдано +-- * главные плюсы: 1. распределение нагрузки между серверами 2. каждая мастер репликация отвечает только за свою зону ответсвенности +-- * сложности: 1. конфликты, что на разных мульти репликах могут быть разные записи по одному и тому же значению и будет не ясно что важнее оставить; 2. согласованность - кадая мульти реплика должна работать только в пределах своей зоны ответственности; 3. сложность настройки - я думаю имеется ввиду защита от конфликтов и поддержка согласованности. +-- * у меня в голове только мысль о разделении отвественности по хэшу, что одна мастер реплика отвечает за один кусок, другая за другой, по типу шардирования, только без деления таблиц, они как бы и дальше между собой реплицируются, но каждая изменят обновляет и делает записи только на своем куске. Наверное такой способ поможет при больших таблицах с частыми изменениями(хотя по суть шардирование лучше испольщовать тогда, а не мульти-мастер). Неэффективно будет на таблицах с редкими изменениями, и не очень больших таблицах. \ No newline at end of file From 74237af6c6005ed8b0da6f47fa755b31a0d0f20b Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 30 Mar 2026 13:47:47 +0300 Subject: [PATCH 053/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D1=8B:=20?= =?UTF-8?q?=D0=90=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=D1=8B'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../5. Индексы: Алгоритмы/practice_indexes_algorithms.sql | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql new file mode 100644 index 0000000..989b738 --- /dev/null +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql @@ -0,0 +1,32 @@ +-- Задание 1 +-- 1. будет +-- 2. не будет. можно изменить запрос - убрать lower +-- 3. не будет. нужно убрать like +-- 4. не будет. нужно убрать like +-- 5. будет +-- 6. не будет, тк фильтрация по полю без индекса + + +-- Задание 2 +-- не понял задание, сначала нужно каждому запросу сказать что индекс не нужен/бсполезен, а потом создать индекс? +-- 1. по идее есть смысл создать два индекса - по user_id и created_at, или состваной. + CREATE INDEX idx_orders_user_id ON orders(user_id); + CREATE INDEX idx_orders_created_at ON orders(created_at); +-- 2. тут никакой индекс не нужен +-- 3. тут тоже индекс не нужен, тк у status всего 4 значения + + +-- Задание 3 +-- 1. тут hash, тк равенство +-- 2. здесь индекс и не нужен, тк у status всего 4 значения +-- 3. тут B-tree, тк интервал(<,>) +-- 4. конкретно в этом запросе лучше hash, тк равенство, но в целом думаю на user_id лучше B-tree, тк часто сортируют по нему + +-- * hash иммет смысл где просто равенство +-- * B-tree обязательно там где интервалы, > или <, и где order by +-- * в варианте 2, где у столбца всего 4 уникальных значения + + +-- Задание 4 +-- * первый составной нужен для запросов с фильтрацией по нескольким полям +-- * второй не нужен, тк user_id будет рабоать эффективно и в первом случае \ No newline at end of file From e9159267063f114ae323a596e5c36c9b1b10db4d Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 30 Mar 2026 15:16:10 +0300 Subject: [PATCH 054/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'ACID=20=D0=B8=20BASE'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1. ACID и BASE/practice_ASID&BASE.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 mnoskov/Теория БД/2. Гарантии работы БД/1. ACID и BASE/practice_ASID&BASE.sql diff --git a/mnoskov/Теория БД/2. Гарантии работы БД/1. ACID и BASE/practice_ASID&BASE.sql b/mnoskov/Теория БД/2. Гарантии работы БД/1. ACID и BASE/practice_ASID&BASE.sql new file mode 100644 index 0000000..d9fa2a2 --- /dev/null +++ b/mnoskov/Теория БД/2. Гарантии работы БД/1. ACID и BASE/practice_ASID&BASE.sql @@ -0,0 +1,12 @@ +-- Задание 1. Интернет‑банк +-- * в этом случае важна атомарность, и первый запрос должен был откатиться обратно, и не дублироваться как я понимаю. +-- * если не будеь атомарноти, то после сбоев могут произойте несостыковки, напрмер у пользователя деньги списались, а заказ не оформился, или наоборот + + +-- Задание 2. Лента новостей и лайки +-- * эта система ближе к BASE, тк согласование узлов происходит не сразу, и у кого то 22 лайка, а у кого-то 24. + + +-- Задание 3. Заказы в интернет‑магазине +-- * у первого случая принципы ACID - это нужно чтбы критическа важная информация обновлялась без ошибок, и чтобы небыло несостыковок +-- * во втором случае принципы BASE - можно немного потерпеть неточность, тк это некритично \ No newline at end of file From 176001d33c43604e4c2f7ebe5e71d2e6f96533ed Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 30 Mar 2026 16:08:24 +0300 Subject: [PATCH 055/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A3=D1=80=D0=BE=D0=B2=D0=BD=D0=B8=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=BB=D1=8F=D1=86=D0=B8=D0=B8'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Уровни изоляции/practice_isolation_levels.sql | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 mnoskov/Теория БД/2. Гарантии работы БД/2. Уровни изоляции/practice_isolation_levels.sql diff --git a/mnoskov/Теория БД/2. Гарантии работы БД/2. Уровни изоляции/practice_isolation_levels.sql b/mnoskov/Теория БД/2. Гарантии работы БД/2. Уровни изоляции/practice_isolation_levels.sql new file mode 100644 index 0000000..33e81b8 --- /dev/null +++ b/mnoskov/Теория БД/2. Гарантии работы БД/2. Уровни изоляции/practice_isolation_levels.sql @@ -0,0 +1,13 @@ +-- Задание 1: Разбери ситуацию +-- 1) 200 +-- 2) 100 +-- 3) Результаты разные тк на уровне Read Committed разрешено неповторяемое чтение, а на уровне Repeatable Read неповторяемое чтенине запрщено, и мы видим то, что было на момент начала транщакции. + +-- Задание 2: Практика +-- При Repeatable Read, я увидел во втором select значение - 100, потому что изменение произошли до того как завершилась транзакция с чтением, и зафиксировалось то значение которые было на момент начала транзакции с чтением +-- При Read Committed, во втором select было значение - 200, тк этот уровень изолированности подразумевает чтение закоммиченных(другой транзакцией) данных даже если текущая транзакция ещё не завершена + +-- Задание 3: Найди ошибку +-- 1) Так произошло потому, что выбран некорректный уровень изоляции. +-- 2) Тут подойдет serializavle. +-- 3) Решить эту проблему без смены изоляции можно с помощью проверки баланса перед списанием, и если денег на балансе будет недостаточно, то откатить транзакцию. Такое решение подойдет только с уровнем изоляции Read Commited, для Repeatable Read такой способ уже не подойдет. \ No newline at end of file From 9765503642c96b6d184635044183d6e6ffab7bcc Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 31 Mar 2026 12:26:02 +0300 Subject: [PATCH 056/123] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20"=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=BA=D0=B0=20'?= =?UTF-8?q?=D0=9F=D0=B0=D1=80=D1=82=D0=B8=D1=86=D0=B8=D0=B8'"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Партиции/practice_partitions.sql | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql index 5445669..fa096dc 100644 --- a/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql +++ b/mnoskov/Теория БД/1. Ускорение и масштабирование/2. Партиции/practice_partitions.sql @@ -2,9 +2,8 @@ 1) * range по полю order_date * запросы с between в год - 1. where order_date between '2022-01-01' and '2023-01-01' + 1. where order_date >= '2022-01-01' and order_date < '2023-01-01' 2. where extract('year' from order_date) = '2025' - 3. where order_date < '2026-01-01' and order_date > '2025-01-01' 2) * тут два типа партиционирования подходят. range по created_at или list по country. * если выбирать list по country то плюс - это при фильтрах по стране будут отсекаться лишние партиции, а минус, что год/месяц регистрации не будет разделен по партициям. @@ -34,13 +33,13 @@ CREATE TABLE orders_part ( ) PARTITION BY RANGE (order_date); CREATE TABLE orders_2024 PARTITION OF orders_part -FOR VALUES FROM ('2024_01-01') TO ('2025-01-01'); +FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); CREATE TABLE orders_2025 PARTITION OF orders_part -FOR VALUES FROM ('2025_01-01') TO ('2026-01-01'); +FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); CREATE TABLE orders_2026 PARTITION OF orders_part -FOR VALUES FROM ('2026_01-01') TO ('2027-01-01'); +FOR VALUES FROM ('2026-01-01') TO ('2027-01-01'); -- сделал insert который попросил сгенерировать нейронку @@ -107,7 +106,7 @@ SELECT FROM users_part WHERE - country = 'RU' + country = 'RU'; -- запрос скаинровал только партицию users_ru EXPLAIN ANALYZE @@ -116,7 +115,7 @@ SELECT FROM users_part WHERE - country IN ('RU', 'KZ') + country IN ('RU', 'KZ'); -- запрос сканировал только партиции users_ru и users_kz EXPLAIN ANALYZE @@ -138,7 +137,7 @@ CREATE TABLE user_logs_part ( event_type text NOT NULL, payload jsonb, PRIMARY KEY (id, user_id) -) PARTITION BY hash (user_id) +) PARTITION BY hash (user_id); CREATE TABLE user_logs_p1 PARTITION OF user_logs_part FOR VALUES WITH (modulus 4, remainder 0); From 9234995fe26d0c185d6259af1d7eeee5a26f28f3 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 31 Mar 2026 14:23:24 +0300 Subject: [PATCH 057/123] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'CAP-=D1=82=D0=B5=D0=BE=D1=80=D0=B5=D0=BC=D0=B0'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3. CAP-теорема/practice_CAP_theorem.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 mnoskov/Теория БД/2. Гарантии работы БД/3. CAP-теорема/practice_CAP_theorem.sql diff --git a/mnoskov/Теория БД/2. Гарантии работы БД/3. CAP-теорема/practice_CAP_theorem.sql b/mnoskov/Теория БД/2. Гарантии работы БД/3. CAP-теорема/practice_CAP_theorem.sql new file mode 100644 index 0000000..d66aec9 --- /dev/null +++ b/mnoskov/Теория БД/2. Гарантии работы БД/3. CAP-теорема/practice_CAP_theorem.sql @@ -0,0 +1,11 @@ +-- Задание 1: Определи тип базы +-- 1) CP +-- 2) AP +-- 3) CA +-- 4) AP + + +-- Задание 2: Что выбрать? +-- CP - должна быть согласованность, чтобы последний билет достался только одному, у двух людей не может быть одинаковый билет +-- Если выбрать CP, то все будет четко, и билет достентся только одному пользователю +-- Если выбрать AP, то билет может достаться и тому и тому, что недопустимо \ No newline at end of file From 1bd6b63cfa1362edb9fda53492651ab3e7087075 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 16 Apr 2026 22:44:37 +0300 Subject: [PATCH 058/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=97=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=8B'=20-?= =?UTF-8?q?=20GreenPlum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/greenplum/practice_requests.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 mnoskov/greenplum/practice_requests.txt diff --git a/mnoskov/greenplum/practice_requests.txt b/mnoskov/greenplum/practice_requests.txt new file mode 100644 index 0000000..4c4fbde --- /dev/null +++ b/mnoskov/greenplum/practice_requests.txt @@ -0,0 +1,17 @@ +Задание 1: +- Будут собираться все 10млн строк с разных сегментов, а тольок потом сработает LIMIT. Это очень дорого стоит, и медленно тк очень много данных в пустую соединятся в мастер. +- Я думаю можно к текущему запросу добавить DENSE_RANK по total и написать условие where -тут имя dense_rank- <= 10. +!!! В теории не сказано про работу order by - пришлось загуглить !!! + +Задание 2: +- PARTITION BY по. region_id, а должен быть по order_id, чтобы работало без задержек, а при текущем варианте запроса будет происходить data motion как я понял, а это лишние вычисления. +- Тогда надо распределять таблицу по region_id полагаю. + +Задание 3: +- Ответ до подсказки: думаю все будет нормально, данные просто сгруппируются и вставятся, никаких зарежек быть не должно. +- Ответ после подсказки: greenplum будет пересылать(data motion) user_id между сегментами, чтобы собрать их в группы, а только потом собирать в мастер и инсертить, инсерт уже поидее будет происходить хорошо, тк данные после data motion будут лежать нормально. +- Orders надо дестрибьютить по user_id, тогда все четко и быстро будет - и group by, и join. + +Задание 4: +- Я хочу увидеть Motion, Broadcast, Gather +- Что означает Broadcast Motion, Redistribute Motion, Gather Motion? - в душе ни чаю, там вопрос с ответом сразу, взял оттуда: Broadcast Motion - разослать копии каждой строки на все сегменты; Redistribute Motion - перераспределить по новому ключу; Gather Motion - собрать на мастер. \ No newline at end of file From 7dcea750a3aae04ed7a687db9a7a13aa87149931 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 20 Apr 2026 00:14:25 +0400 Subject: [PATCH 059/123] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=8B=20-=20=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D1=81=20(Task=201=20and=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Запросы - Синтаксис.sql | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql diff --git a/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql new file mode 100644 index 0000000..47d7629 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql @@ -0,0 +1,224 @@ +/* Запросы - Синтаксис*/ + +--! Задача 1 !-- + +DROP TABLE IF EXISTS users ON CLUSTER clickhouse_cluster; + +CREATE TABLE users +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + name String, + age UInt8, + country String, + is_active UInt8 +) ENGINE = MergeTree() +ORDER BY user_id; + +INSERT INTO users VALUES +(1, 'Ivan', 30, 'RU', 1), +(2, 'Anna', 25, 'RU', 1), +(3, 'Petr', 45, 'RU', 0), +(4, 'Olga', 35, 'RU', 1), +(5, 'John', 55, 'US', 1), +(6, 'Alice', 42, 'US', 0), +(7, 'Tom', 38, 'US', 1), +(8, 'Lucas', 28, 'BR', 1), +(9, 'Maria', 60, 'BR', 1), +(10, 'João', 27, 'BR', 0), +(11, 'Kenji', 33, 'JP', 1), +(12, 'Yuki', 29, 'JP', 0), +(13, 'Chen', 40, 'CN', 1), +(14, 'Li', 48, 'CN', 1), +(15, 'Fatima', 31, 'AE', 1), +(16, 'Ahmed', 36, 'AE', 1), +(17, 'George', 24, 'US', 1), +(18, 'Nina', 34, 'RU', 1), +(19, 'Max', 50, 'DE', 1), +(20, 'Sophie', 22, 'DE', 0); + +--Получи 10 самых старших активных пользователей. +SELECT + user_id, + name, + age, + is_active +FROM users +WHERE is_active = true +ORDER BY age DESC +LIMIT 10; + +--Выведи пользователей из country = 'RU', отсортированных по имени. +SELECT + user_id, + name, + country +FROM users +WHERE country = 'RU' +ORDER BY name; + +--Что произойдёт, если написать LIMIT 10 без ORDER BY? Просто выведет 10 случайных активных прользователей без какой либо сортировки. +SELECT + user_id, + name, + age +FROM users +WHERE is_active = true +LIMIT 10; + +--Посчитай количество пользователей по странам. +SELECT + country, + COUNT(*) as user_country +FROM users +GROUP BY country; + +--Для каждой страны выведи минимальный и максимальный возраст. +SELECT + country, + MIN(age) as min_age, + MAX(age) as max_age +FROM users +GROUP BY country +ORDER BY country; + +--Отсортируй страны по убыванию количества пользователей. +SELECT + country, + COUNT(*) as user_country +FROM users +GROUP BY country +ORDER BY user_country DESC; + +--! Задача 2 !-- + +DROP TABLE IF EXISTS dsolovianov.events ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.events ON CLUSTER clickhouse_cluster +( + user_id UInt32, + event_time DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/events_v2', + '{replica}' +) +ORDER BY user_id; + +INSERT INTO dsolovianov.events (user_id, event_time, event_type) VALUES +(1, '2025-07-17 09:00:00', 'click'), +(1, '2025-07-17 09:05:00', 'view'), +(1, '2025-07-17 09:10:00', 'purchase'), +(2, '2025-07-17 10:00:00', 'click'), +(2, '2025-07-17 10:05:00', 'click'), +(2, '2025-07-17 11:00:00', 'view'), +(3, '2025-07-16 12:00:00', 'view'), +(3, '2025-07-16 12:30:00', 'click'), +(3, '2025-07-17 08:00:00', 'purchase'), +(4, '2025-07-17 13:00:00', 'click'), +(4, '2025-07-17 14:00:00', 'view'), +(4, '2025-07-17 15:00:00', 'view'), +(5, '2025-07-16 20:00:00', 'view'), +(5, '2025-07-17 01:00:00', 'click'), +(5, '2025-07-17 06:00:00', 'purchase'), +(6, '2025-07-17 07:30:00', 'view'), +(6, '2025-07-17 08:45:00', 'click'), +(7, '2025-07-16 23:59:59', 'click'), +(7, '2025-07-17 00:01:00', 'purchase'), +(7, '2025-07-17 10:10:00', 'view'); + +--1. Добавь колонку с кумулятивным количеством событий по `user_id` (используй `count() OVER ...`). +SELECT + user_id, + event_time, + event_type, + COUNT(*) OVER (PARTITION BY user_id ORDER BY event_time) AS cumulative_events +FROM dsolovianov.events +ORDER BY user_id, event_time; + +--2. Сделай аналог ранжирования событий внутри пользователя: `row_number() OVER (PARTITION BY user_id ORDER BY event_time)` +SELECT + user_id, + event_time, + event_type, + ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) AS event_rank +FROM dsolovianov.events +ORDER BY user_id, event_rank; + +--3. Что будет, если ты явно задашь `ROWS BETWEEN`? Поддерживается ли? +--Да, `ROWS BETWEEN` поддерживается: +SELECT + user_id, + event_time, + event_type, + -- кумулятивное окно (все предыдущие строки) + COUNT(*) OVER (PARTITION BY user_id ORDER BY event_time + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_events, + -- скользящее окно: текущая + 2 предыдущих события + COUNT(*) OVER (PARTITION BY user_id ORDER BY event_time + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS last_3_events, + -- окно: предыдущая + текущая + следующая + COUNT(*) OVER (PARTITION BY user_id ORDER BY event_time + ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS prev_curr_next +FROM dsolovianov.events +ORDER BY user_id, event_time; + +--4. Сделай `WITH recent_events AS (...)` — все события за последние 24 часа. +WITH recent_events AS ( + SELECT * + FROM dsolovianov.events + WHERE event_time >= '2025-07-16 15:00:00' +) +SELECT * FROM recent_events +ORDER BY event_time DESC; + +--5. Используй CTE, чтобы посчитать топ-5 пользователей по числу этих событий. +WITH recent_events AS ( + SELECT * + FROM dsolovianov.events + WHERE event_time >= '2025-07-16 15:00:00' +) +SELECT + user_id, + COUNT(*) AS event_count +FROM recent_events +GROUP BY user_id +ORDER BY event_count DESC +LIMIT 5; + +--6. Добавь второй `WITH` — для маппинга `event_type → category` и используй его в основном запросе. +WITH +event_categories AS ( + SELECT + event_type, + CASE + WHEN event_type = 'click' THEN 'engagement' + WHEN event_type = 'view' THEN 'engagement' + WHEN event_type = 'purchase' THEN 'conversion' + ELSE 'other' + END AS category + FROM (SELECT DISTINCT event_type FROM dsolovianov.events) +), +recent_events AS ( + SELECT * + FROM dsolovianov.events + WHERE event_time >= '2025-07-16 15:00:00' +) +SELECT + re.user_id, + re.event_time, + re.event_type, + ec.category, + ROW_NUMBER() OVER (PARTITION BY re.user_id ORDER BY re.event_time) AS event_rank, + COUNT(*) OVER (PARTITION BY re.user_id ORDER BY re.event_time) AS cumulative_events +FROM recent_events re +LEFT JOIN event_categories ec ON re.event_type = ec.event_type +ORDER BY re.user_id, re.event_time; + +/* +Вопрос: поддерживает ли ClickHouse многоуровневые или рекурсивные CTE? +многоуровневые CTE - да +рекурсивные CTE - нет в классическом понимании, а скорее в итеративном смысле. +*/ + From 8c6daac203ecdde1ad934919d6a763f36e85e6a9 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 20 Apr 2026 03:32:36 +0400 Subject: [PATCH 060/123] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=8B=20-=20=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D1=81=20(Task=201=20and=202)=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Запросы - Синтаксис.sql | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql index 47d7629..abd02d7 100644 --- a/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql +++ b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql @@ -2,7 +2,7 @@ --! Задача 1 !-- -DROP TABLE IF EXISTS users ON CLUSTER clickhouse_cluster; +DROP TABLE IF EXISTS dsolovianov.users ON CLUSTER clickhouse_cluster SYNC; CREATE TABLE users ON CLUSTER clickhouse_cluster @@ -11,8 +11,11 @@ ON CLUSTER clickhouse_cluster name String, age UInt8, country String, - is_active UInt8 -) ENGINE = MergeTree() + is_active Boolean +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/users_v2', + '{replica}' +) ORDER BY user_id; INSERT INTO users VALUES @@ -73,6 +76,12 @@ SELECT FROM users GROUP BY country; +SELECT + country, + uniqExact(country) as unique_countries --возвращает точное количество уникальных значений в столбце country +FROM users +GROUP BY country; + --Для каждой страны выведи минимальный и максимальный возраст. SELECT country, @@ -90,6 +99,18 @@ FROM users GROUP BY country ORDER BY user_country DESC; +--Всё вместе +SELECT + country, + COUNT(*) as user_country, + SUM(is_active) as active_users, + MIN(age) as min_age, + MAX(age) as max_age, + uniqExact(country) as unique_countries +FROM users +GROUP BY country +ORDER BY country; + --! Задача 2 !-- DROP TABLE IF EXISTS dsolovianov.events ON CLUSTER clickhouse_cluster SYNC; @@ -104,7 +125,7 @@ ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dsolovianov/events_v2', '{replica}' ) -ORDER BY user_id; +ORDER BY (user_id, event_time); INSERT INTO dsolovianov.events (user_id, event_time, event_type) VALUES (1, '2025-07-17 09:00:00', 'click'), @@ -220,5 +241,4 @@ ORDER BY re.user_id, re.event_time; Вопрос: поддерживает ли ClickHouse многоуровневые или рекурсивные CTE? многоуровневые CTE - да рекурсивные CTE - нет в классическом понимании, а скорее в итеративном смысле. -*/ - +*/ \ No newline at end of file From a997d586da7ffc4ebe563b31c0737545bfa29484 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 20 Apr 2026 03:36:52 +0400 Subject: [PATCH 061/123] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=8B=20-=20=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D1=81=20(Task=201=20and=202)=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql index abd02d7..120b341 100644 --- a/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql +++ b/dsolovianov/clickhouse/Homework/Запросы - Синтаксис.sql @@ -11,7 +11,7 @@ ON CLUSTER clickhouse_cluster name String, age UInt8, country String, - is_active Boolean + is_active Bool ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/dsolovianov/users_v2', '{replica}' From 9b0186715459a6db96bbb4c368e9e192d818d78b Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 20 Apr 2026 18:08:46 +0300 Subject: [PATCH 062/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'DISTRIBUTED=20BY'=20-=20GreenPlum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/greenplum/practice_distributed_by.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 mnoskov/greenplum/practice_distributed_by.txt diff --git a/mnoskov/greenplum/practice_distributed_by.txt b/mnoskov/greenplum/practice_distributed_by.txt new file mode 100644 index 0000000..b9f5fc6 --- /dev/null +++ b/mnoskov/greenplum/practice_distributed_by.txt @@ -0,0 +1,9 @@ +Задание 1 + - запос не будет локальным + - гринплам будет делать data motion + +Задание 2 + - distributed by не подходит, тк таблица не будет учавствовать в join + +Задание 3 + - можно добавить event_id и распределять по нему, или же сделать destributed randomly \ No newline at end of file From 9520bd8bbc6532bfe6f138bfe9e09f690ada9fab Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 21 Apr 2026 18:04:37 +0300 Subject: [PATCH 063/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'EXPLAIN'=20-=20GreenPlum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/greenplum/practice_explain.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 mnoskov/greenplum/practice_explain.txt diff --git a/mnoskov/greenplum/practice_explain.txt b/mnoskov/greenplum/practice_explain.txt new file mode 100644 index 0000000..9393ecd --- /dev/null +++ b/mnoskov/greenplum/practice_explain.txt @@ -0,0 +1,6 @@ +Задание 1 + - если orders DISTRIBUTED BY user_id, то будет 'Seq Scan on orders' -> 'HashAggregate'. Redistribute Motion(по user_id) пропадет, потому что все уже распределено по user_id; + +Задание 2 + - Это называется Skew. + - Чтобы избежать skew, нужно выбрать столбец для распределения с нормальной кардинальностью. Или делать destributed randomly если не нужно join. \ No newline at end of file From 863b58480e3425a24a8d738671e608215611071b Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 21 Apr 2026 19:54:45 +0300 Subject: [PATCH 064/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=92=D0=BD=D0=B5=D1=88=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B8=20=D0=B8?= =?UTF-8?q?=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D1=8F?= =?UTF-8?q?'=20-=20GreenPlum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice_external_sources_and_integration.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 mnoskov/greenplum/practice_external_sources_and_integration.txt diff --git a/mnoskov/greenplum/practice_external_sources_and_integration.txt b/mnoskov/greenplum/practice_external_sources_and_integration.txt new file mode 100644 index 0000000..5270ff7 --- /dev/null +++ b/mnoskov/greenplum/practice_external_sources_and_integration.txt @@ -0,0 +1,10 @@ +Задание 1 + - данные физически находятся в Hive; + - писать обратно в Hive нельзя, тк таблица не writable; + - не знаю, в теорри это не сказано - думаю будет ошибка. После того как загуглил: + 1) если в hive добавились новые столбцы, то при чтении все будет отображаться так, как было во время создание внешней таблицы в greenplum, без ошибок; + 2) если в hive удалась часть столбцов, то возникнет ошибка тк, в greenplum по сути будут лишние столбцы. + +Задание 2 + - данные не копируются в greenplum, а только читаются из s3; + - я думаю в момент создания таблицы - быстрее external, тк ничего не копируется фищически, но при чтении должен быть быстрее CREATE TABLE AS SELECT, тк данные физически хранятся на нашем сервере, а не где-то в облаке. \ No newline at end of file From 8913ccb25ea37abfca51a1cdea457035974af59b Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 21 Apr 2026 21:05:39 +0300 Subject: [PATCH 065/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=201.=20=D0=98?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0.=20?= =?UTF-8?q?=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20DW?= =?UTF-8?q?H?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Step_1_project_Initialization.sql | 46 +++++++++++++++++++ .../practice_distributed_by.txt | 0 .../practice_explain.txt | 0 .../practice_external_sources_and_integration.txt | 0 .../practice_requests.txt | 0 5 files changed, 46 insertions(+) create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql rename mnoskov/greenplum/{ => Практика greenplum}/practice_distributed_by.txt (100%) rename mnoskov/greenplum/{ => Практика greenplum}/practice_explain.txt (100%) rename mnoskov/greenplum/{ => Практика greenplum}/practice_external_sources_and_integration.txt (100%) rename mnoskov/greenplum/{ => Практика greenplum}/practice_requests.txt (100%) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql b/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql new file mode 100644 index 0000000..f76258c --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql @@ -0,0 +1,46 @@ +-- 1 +CREATE SCHEMA raw; +CREATE SCHEMA core; +CREATE SCHEMA dm; + +-- 2 +-- DROP TABLE IF EXISTS raw.users; +CREATE TABLE raw.users ( + id bigint, + name varchar(100), + age SMALLINT, + email varchar(100), + registration_date timestamptz +); + +--DROP TABLE IF EXISTS raw.courses; +CREATE TABLE raw.courses ( + id bigint, + title varchar(150), + category varchar(50), + created_at timestamptz +); + +--DROP TABLE IF EXISTS raw.lessons; +CREATE TABLE raw.lessons ( + id bigint, + course_id bigint, + title varchar(150), + duration_min SMALLINT +); + +--DROP TABLE IF EXISTS raw.enrollments; +CREATE TABLE raw.enrollments ( + id bigint, + user_id bigint, + course_id bigint, + enrolled_at timestamptz +); + +--DROP TABLE IF EXISTS raw.lesson_views; +CREATE TABLE raw.lesson_views ( + id bigint, + user_id bigint, + lesson_id bigint, + viewed_at timestamptz +); \ No newline at end of file diff --git a/mnoskov/greenplum/practice_distributed_by.txt b/mnoskov/greenplum/Практика greenplum/practice_distributed_by.txt similarity index 100% rename from mnoskov/greenplum/practice_distributed_by.txt rename to mnoskov/greenplum/Практика greenplum/practice_distributed_by.txt diff --git a/mnoskov/greenplum/practice_explain.txt b/mnoskov/greenplum/Практика greenplum/practice_explain.txt similarity index 100% rename from mnoskov/greenplum/practice_explain.txt rename to mnoskov/greenplum/Практика greenplum/practice_explain.txt diff --git a/mnoskov/greenplum/practice_external_sources_and_integration.txt b/mnoskov/greenplum/Практика greenplum/practice_external_sources_and_integration.txt similarity index 100% rename from mnoskov/greenplum/practice_external_sources_and_integration.txt rename to mnoskov/greenplum/Практика greenplum/practice_external_sources_and_integration.txt diff --git a/mnoskov/greenplum/practice_requests.txt b/mnoskov/greenplum/Практика greenplum/practice_requests.txt similarity index 100% rename from mnoskov/greenplum/practice_requests.txt rename to mnoskov/greenplum/Практика greenplum/practice_requests.txt From c456045950f6a6a69f18931eebfb1bbf8a494edd Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 21 Apr 2026 21:44:45 +0300 Subject: [PATCH 066/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=202.=20=D0=97?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20(=D1=8D=D0=BC=D1=83=D0=BB=D1=8F=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20Airflow).=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Step 2: Loading Data/courses_202604212117.csv | 6 ++ .../Step 2: Loading Data/enrollments_202604212130.csv | 46 +++++++++++++++ .../Step 2: Loading Data/lesson_views_202604212133.csv | 59 +++++++++++++++++++ .../Step 2: Loading Data/lessons_202604212131.csv | 19 ++++++ .../Step 2: Loading Data/users_202604212132.csv | 21 +++++++ 5 files changed, 151 insertions(+) create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv new file mode 100644 index 0000000..d348d4a --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv @@ -0,0 +1,6 @@ +"id","title","category","created_at" +1,SQL для начинающих,data,2023-01-01 00:00:00.000 +0300 +2,Python для анализа данных,programming,2023-01-15 00:00:00.000 +0300 +3,BI с нуля,business,2023-03-01 00:00:00.000 +0300 +4,Excel для аналитиков,business,2023-02-01 00:00:00.000 +0300 +5,Основы машинного обучения,data,2023-03-15 00:00:00.000 +0300 diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv new file mode 100644 index 0000000..e5c5610 --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv @@ -0,0 +1,46 @@ +"id","user_id","course_id","enrolled_at" +1,1,1,2023-01-15 00:00:00.000 +0300 +2,1,2,2023-02-01 00:00:00.000 +0300 +3,2,1,2023-01-20 00:00:00.000 +0300 +4,3,2,2023-03-05 00:00:00.000 +0300 +5,4,3,2023-04-01 00:00:00.000 +0300 +6,6,1,2023-01-06 00:00:00.000 +0300 +7,6,2,2023-01-10 00:00:00.000 +0300 +8,6,4,2023-02-05 00:00:00.000 +0300 +9,7,1,2023-01-13 00:00:00.000 +0300 +10,7,3,2023-03-10 00:00:00.000 +0300 +11,8,1,2023-01-22 00:00:00.000 +0300 +12,8,2,2023-01-22 00:00:00.000 +0300 +13,8,3,2023-01-22 00:00:00.000 +0300 +14,9,4,2023-02-12 00:00:00.000 +0300 +15,10,1,2023-04-16 00:00:00.000 +0300 +16,11,2,2023-04-21 00:00:00.000 +0300 +17,12,1,2023-01-16 00:00:00.000 +0300 +18,13,2,2023-01-19 00:00:00.000 +0300 +19,13,4,2023-02-20 00:00:00.000 +0300 +20,14,1,2023-01-09 00:00:00.000 +0300 +21,14,2,2023-01-09 00:00:00.000 +0300 +22,14,3,2023-01-09 00:00:00.000 +0300 +23,14,4,2023-02-01 00:00:00.000 +0300 +24,14,5,2023-03-20 00:00:00.000 +0300 +25,15,1,2023-01-11 00:00:00.000 +0300 +26,15,3,2023-03-05 00:00:00.000 +0300 +27,16,2,2023-02-16 00:00:00.000 +0300 +28,16,4,2023-02-16 00:00:00.000 +0300 +29,17,3,2023-03-22 00:00:00.000 +0300 +30,17,5,2023-03-25 00:00:00.000 +0300 +31,18,1,2023-04-02 00:00:00.000 +0300 +32,19,1,2023-04-11 00:00:00.000 +0300 +33,19,2,2023-04-11 00:00:00.000 +0300 +34,20,2,2023-04-19 00:00:00.000 +0300 +35,5,5,2023-03-20 00:00:00.000 +0300 +36,1,4,2023-02-10 00:00:00.000 +0300 +37,2,3,2023-03-15 00:00:00.000 +0300 +38,3,1,2023-02-25 00:00:00.000 +0300 +39,4,2,2023-03-10 00:00:00.000 +0300 +40,6,3,2023-03-08 00:00:00.000 +0300 +41,7,2,2023-02-01 00:00:00.000 +0300 +42,9,1,2023-02-15 00:00:00.000 +0300 +43,12,3,2023-03-10 00:00:00.000 +0300 +44,15,2,2023-02-01 00:00:00.000 +0300 +45,18,4,2023-04-05 00:00:00.000 +0300 diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv new file mode 100644 index 0000000..b09d250 --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv @@ -0,0 +1,59 @@ +"id","user_id","lesson_id","viewed_at" +1,1,1,2023-01-16 10:00:00.000 +0300 +2,1,2,2023-01-16 10:15:00.000 +0300 +3,1,3,2023-01-17 09:00:00.000 +0300 +4,1,4,2023-01-17 09:30:00.000 +0300 +5,1,4,2023-01-17 11:00:00.000 +0300 +6,1,4,2023-01-18 10:00:00.000 +0300 +7,1,6,2023-02-02 14:00:00.000 +0300 +8,1,12,2023-02-15 10:00:00.000 +0300 +9,1,13,2023-02-15 10:30:00.000 +0300 +10,2,1,2023-01-21 09:00:00.000 +0300 +11,2,9,2023-03-16 10:00:00.000 +0300 +12,2,10,2023-03-16 10:30:00.000 +0300 +13,3,6,2023-03-06 12:00:00.000 +0300 +14,3,1,2023-02-26 11:00:00.000 +0300 +15,3,2,2023-02-26 11:20:00.000 +0300 +16,4,9,2023-04-02 10:00:00.000 +0300 +17,4,10,2023-04-03 10:00:00.000 +0300 +18,4,6,2023-03-12 14:00:00.000 +0300 +19,4,7,2023-03-13 09:00:00.000 +0300 +20,5,15,2023-03-21 10:00:00.000 +0300 +21,5,16,2023-03-22 11:00:00.000 +0300 +22,6,1,2023-01-07 10:00:00.000 +0300 +23,6,2,2023-01-07 10:15:00.000 +0300 +24,6,3,2023-01-08 09:00:00.000 +0300 +25,6,4,2023-01-08 09:30:00.000 +0300 +26,6,5,2023-01-09 10:00:00.000 +0300 +27,6,6,2023-01-11 14:00:00.000 +0300 +28,6,7,2023-01-12 10:00:00.000 +0300 +29,6,8,2023-01-13 11:00:00.000 +0300 +30,6,12,2023-02-06 09:00:00.000 +0300 +31,6,13,2023-02-06 09:30:00.000 +0300 +32,6,14,2023-02-07 10:00:00.000 +0300 +33,6,9,2023-03-09 11:00:00.000 +0300 +34,7,1,2023-01-14 10:00:00.000 +0300 +35,7,2,2023-01-14 10:20:00.000 +0300 +36,7,3,2023-01-15 09:00:00.000 +0300 +37,7,9,2023-03-11 14:00:00.000 +0300 +38,7,10,2023-03-11 14:30:00.000 +0300 +39,9,12,2023-02-13 10:00:00.000 +0300 +40,9,13,2023-02-13 10:30:00.000 +0300 +41,9,1,2023-02-16 11:00:00.000 +0300 +42,12,1,2023-01-17 10:00:00.000 +0300 +43,12,2,2023-01-17 10:20:00.000 +0300 +44,12,1,2023-04-10 11:00:00.000 +0300 +45,12,3,2023-04-10 11:30:00.000 +0300 +46,12,9,2023-03-12 10:00:00.000 +0300 +47,13,6,2023-01-20 14:00:00.000 +0300 +48,13,7,2023-01-21 10:00:00.000 +0300 +49,13,12,2023-02-21 09:00:00.000 +0300 +50,14,1,2023-01-09 08:00:00.000 +0300 +51,14,2,2023-01-09 08:15:00.000 +0300 +52,14,3,2023-01-10 10:00:00.000 +0300 +53,14,4,2023-01-10 10:30:00.000 +0300 +54,14,5,2023-01-11 09:00:00.000 +0300 +55,14,6,2023-01-10 14:00:00.000 +0300 +56,14,7,2023-01-11 10:00:00.000 +0300 +57,14,8,2023-01-12 11:00:00.000 +0300 +58,14,9,2023-01-10 08:00:00.000 +0300 diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv new file mode 100644 index 0000000..54dbb22 --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv @@ -0,0 +1,19 @@ +"id","course_id","title","duration_min" +1,1,SELECT и FROM,10 +2,1,JOIN,15 +3,1,WHERE и фильтрация,12 +4,1,Агрегации и GROUP BY,18 +5,1,Подзапросы,25 +6,2,Pandas основы,20 +7,2,NumPy,15 +8,2,Визуализация данных,22 +9,3,Основы BI,12 +10,3,Метрики и дашборды,18 +11,3,Power BI введение,20 +12,4,Формулы в Excel,15 +13,4,Сводные таблицы,18 +14,4,Графики и диаграммы,14 +15,5,Введение в ML,20 +16,5,Линейная регрессия,30 +17,5,Классификация,28 +18,5,Кластеризация,25 diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv new file mode 100644 index 0000000..d6aa0da --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv @@ -0,0 +1,21 @@ +"id","name","age","email","registration_date" +1,Alice,25,alice@mail.com,2023-01-10 00:00:00.000 +0300 +2,Bob,30,bob@gmail.com,2023-02-05 00:00:00.000 +0300 +3,Charlie,22,charlie@mail.com,2023-02-20 00:00:00.000 +0300 +4,Diana,28,diana@mail.com,2023-03-01 00:00:00.000 +0300 +5,Ethan,35,ethan@gmail.com,2023-03-10 00:00:00.000 +0300 +6,Frank,29,frank@mail.com,2023-01-05 00:00:00.000 +0300 +7,Grace,26,grace@gmail.com,2023-01-12 00:00:00.000 +0300 +8,Henry,32,henry@mail.com,2023-01-20 00:00:00.000 +0300 +9,Iris,24,iris@gmail.com,2023-02-10 00:00:00.000 +0300 +10,Jack,27,jack@mail.com,2023-04-15 00:00:00.000 +0300 +11,Kelly,23,kelly@gmail.com,2023-04-20 00:00:00.000 +0300 +12,Leo,31,leo@mail.com,2023-01-15 00:00:00.000 +0300 +13,Mia,26,mia@gmail.com,2023-01-18 00:00:00.000 +0300 +14,Nina,28,nina@mail.com,2023-01-08 00:00:00.000 +0300 +15,Oscar,33,oscar@gmail.com,2023-01-10 00:00:00.000 +0300 +16,Paul,29,paul@mail.com,2023-02-15 00:00:00.000 +0300 +17,Quinn,24,quinn@gmail.com,2023-03-20 00:00:00.000 +0300 +18,Rita,27,rita@mail.com,2023-04-01 00:00:00.000 +0300 +19,Sam,30,sam@gmail.com,2023-04-10 00:00:00.000 +0300 +20,Tina,25,tina@mail.com,2023-04-18 00:00:00.000 +0300 From 6c06823b68d718ddcfe49663bb997513dd3ea3de Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 22 Apr 2026 14:06:46 +0300 Subject: [PATCH 067/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203.=20=D0=A1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql new file mode 100644 index 0000000..c21d4fa --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -0,0 +1,112 @@ +-- 4 + +--DROP TABLE IF EXISTS core.dim_user; +CREATE TABLE core.dim_user ( + user_id int PRIMARY KEY, + name text, + age int, + email text, + registration_date date +) DISTRIBUTED BY (user_id); + +--DROP TABLE IF EXISTS core.dim_course; +CREATE TABLE core.dim_course ( + course_id int PRIMARY KEY, + title text, + category text +) DISTRIBUTED BY (course_id); + +--DROP TABLE IF EXISTS core.dim_lesson; +CREATE TABLE core.dim_lesson ( + lesson_id int PRIMARY KEY, + title text, + duration_min int, + course_id int REFERENCES core.dim_course(course_id) +) DISTRIBUTED BY (lesson_id); + +--DROP TABLE IF EXISTS core.dim_date; +CREATE TABLE core.dim_date ( + date date PRIMARY KEY, + year int, + month int, + day int, + weekday text +) DISTRIBUTED BY (date); + + +-- 5 +--DROP TABLE IF EXISTS core.fact_lesson_views; +CREATE TABLE core.fact_lesson_views ( + user_id int REFERENCES core.dim_user(user_id), + lesson_id int REFERENCES core.dim_lesson(lesson_id), + course_id int REFERENCES core.dim_course(course_id), + viewed_at timestamp +) DISTRIBUTED BY (user_id); + +--DROP TABLE IF EXISTS core.enrollments; +CREATE TABLE core.enrollments ( + user_id int REFERENCES core.dim_user(user_id), + course_id int REFERENCES core.dim_course(course_id), + enrolled_at timestamp +) DISTRIBUTED BY (user_id); + + +--6 +INSERT INTO core.dim_user (user_id, name, age, email, registration_date) +SELECT DISTINCT + id::int AS user_id, + name::text, + age::int, + email::text, + registration_date::date +FROM raw.users +WHERE id IS NOT NULL AND email IS NOT NULL; + +INSERT INTO core.dim_course (course_id, title, category) +SELECT DISTINCT + id::int AS course_id, + title::text, + category::text +FROM + raw.courses +WHERE id IS NOT NULL AND title IS NOT NULL; + +INSERT INTO core.dim_lesson (lesson_id, title, duration_min, course_id) +SELECT DISTINCT + id::int AS lesson_id, + title::text, + duration_min::int, + course_id::int +FROM + raw.lessons +WHERE id IS NOT NULL; + +-- загуглил как заполнять: +INSERT INTO core.dim_date (date, year, month, day, weekday) +SELECT + d::date, + EXTRACT(YEAR FROM d)::int, + EXTRACT(MONTH FROM d)::int, + EXTRACT(DAY FROM d)::int, + to_char(d, 'day') +FROM + generate_series('2023-01-01'::date, '2025-12-31'::date, INTERVAL '1 day') AS d; + +INSERT INTO core.fact_lesson_views (user_id, lesson_id, course_id, viewed_at) +SELECT + lv.user_id::int, + lv.lesson_id::int, + l.course_id::int, + lv.viewed_at::timestamp +FROM + raw.lesson_views lv +JOIN + raw.lessons l ON lv.lesson_id = l.id + +INSERT INTO core.enrollments (user_id, course_id, enrolled_at) +SELECT + user_id::int, + course_id::int, + enrolled_at::timestamp +FROM + raw.enrollments; \ No newline at end of file From b6341a52edcddd0a6948178b7f51db5d5714b62a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 23 Apr 2026 21:58:27 +0300 Subject: [PATCH 068/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=201.=20=D0=98?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0.=20?= =?UTF-8?q?=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20DW?= =?UTF-8?q?H.=20=D0=A0=D0=90=D0=91=D0=9E=D0=A2=D0=90=20=D0=9D=D0=90=D0=94?= =?UTF-8?q?=20=D0=9E=D0=A8=D0=98=D0=91=D0=9A=D0=90=D0=9C=D0=98=20-=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20NOT=20NULL=20?= =?UTF-8?q?=D0=BD=D0=B0=20id.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Step_1_project_Initialization.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql b/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql index f76258c..f542678 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql @@ -4,9 +4,9 @@ CREATE SCHEMA core; CREATE SCHEMA dm; -- 2 --- DROP TABLE IF EXISTS raw.users; +--DROP TABLE IF EXISTS raw.users; CREATE TABLE raw.users ( - id bigint, + id bigint NOT NULL, name varchar(100), age SMALLINT, email varchar(100), @@ -15,7 +15,7 @@ CREATE TABLE raw.users ( --DROP TABLE IF EXISTS raw.courses; CREATE TABLE raw.courses ( - id bigint, + id bigint NOT NULL, title varchar(150), category varchar(50), created_at timestamptz @@ -23,7 +23,7 @@ CREATE TABLE raw.courses ( --DROP TABLE IF EXISTS raw.lessons; CREATE TABLE raw.lessons ( - id bigint, + id bigint NOT NULL, course_id bigint, title varchar(150), duration_min SMALLINT @@ -31,7 +31,7 @@ CREATE TABLE raw.lessons ( --DROP TABLE IF EXISTS raw.enrollments; CREATE TABLE raw.enrollments ( - id bigint, + id bigint NOT NULL, user_id bigint, course_id bigint, enrolled_at timestamptz @@ -39,7 +39,7 @@ CREATE TABLE raw.enrollments ( --DROP TABLE IF EXISTS raw.lesson_views; CREATE TABLE raw.lesson_views ( - id bigint, + id bigint NOT NULL, user_id bigint, lesson_id bigint, viewed_at timestamptz From 243e79401b9379d8b57fcd2c9ed49038e54fada4 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 23 Apr 2026 22:25:25 +0300 Subject: [PATCH 069/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203.=20=D0=A1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=A0=D0=90=D0=91=D0=9E?= =?UTF-8?q?=D0=A2=D0=90=20=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91?= =?UTF-8?q?=D0=9A=D0=90=D0=9C=D0=98=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20NULL=20=D0=B2=20=D0=B8=D0=B7=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index c21d4fa..c2b6c59 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -5,23 +5,23 @@ CREATE TABLE core.dim_user ( user_id int PRIMARY KEY, name text, age int, - email text, - registration_date date + email text NOT NULL, + registration_date date NOT NULL ) DISTRIBUTED BY (user_id); --DROP TABLE IF EXISTS core.dim_course; CREATE TABLE core.dim_course ( course_id int PRIMARY KEY, - title text, + title text NOT NULL, category text ) DISTRIBUTED BY (course_id); --DROP TABLE IF EXISTS core.dim_lesson; CREATE TABLE core.dim_lesson ( lesson_id int PRIMARY KEY, - title text, + title text NOT NULL, duration_min int, - course_id int REFERENCES core.dim_course(course_id) + course_id int NOT NULL REFERENCES core.dim_course(course_id) ) DISTRIBUTED BY (lesson_id); --DROP TABLE IF EXISTS core.dim_date; @@ -37,17 +37,17 @@ CREATE TABLE core.dim_date ( -- 5 --DROP TABLE IF EXISTS core.fact_lesson_views; CREATE TABLE core.fact_lesson_views ( - user_id int REFERENCES core.dim_user(user_id), - lesson_id int REFERENCES core.dim_lesson(lesson_id), - course_id int REFERENCES core.dim_course(course_id), - viewed_at timestamp + user_id int NOT NULL REFERENCES core.dim_user(user_id), + lesson_id int NOT NULL REFERENCES core.dim_lesson(lesson_id), + course_id int NOT NULL REFERENCES core.dim_course(course_id), + viewed_at timestamp NOT NULL ) DISTRIBUTED BY (user_id); --DROP TABLE IF EXISTS core.enrollments; CREATE TABLE core.enrollments ( - user_id int REFERENCES core.dim_user(user_id), - course_id int REFERENCES core.dim_course(course_id), - enrolled_at timestamp + user_id int NOT NULL REFERENCES core.dim_user(user_id), + course_id int NOT NULL REFERENCES core.dim_course(course_id), + enrolled_at timestamp NOT NULL ) DISTRIBUTED BY (user_id); @@ -101,7 +101,7 @@ SELECT FROM raw.lesson_views lv JOIN - raw.lessons l ON lv.lesson_id = l.id + raw.lessons l ON lv.lesson_id = l.id; INSERT INTO core.enrollments (user_id, course_id, enrolled_at) SELECT From 5c2037a49affb52129cc7961368a92bd61e2adbf Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 23 Apr 2026 23:28:03 +0300 Subject: [PATCH 070/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203.=20=D0=A1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=A0=D0=90=D0=91=D0=9E?= =?UTF-8?q?=D0=A2=D0=90=20=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91?= =?UTF-8?q?=D0=9A=D0=90=D0=9C=D0=98=20-=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=20=D1=82=D0=B8=D0=BF=D1=8B=20=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=BB=D0=B1=D1=86=D0=BE=D0=B2=20=D0=B2=20core=20=D1=82=D0=B0?= =?UTF-8?q?=D0=BA=D0=B8=D0=BC=D0=B8=20=D0=B6=D0=B5=20=D0=BA=D0=B0=D0=BA=20?= =?UTF-8?q?=D0=B2=20raw.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index c2b6c59..eed6ab3 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -1,27 +1,27 @@ -- 4 - --DROP TABLE IF EXISTS core.dim_user; CREATE TABLE core.dim_user ( - user_id int PRIMARY KEY, - name text, - age int, - email text NOT NULL, - registration_date date NOT NULL + user_id bigint PRIMARY KEY, + name varchar(100), + age smallint, + email varchar(100) NOT NULL, + registration_date timestamptz NOT NULL ) DISTRIBUTED BY (user_id); --DROP TABLE IF EXISTS core.dim_course; CREATE TABLE core.dim_course ( - course_id int PRIMARY KEY, - title text NOT NULL, - category text + course_id bigint PRIMARY KEY, + title varchar(150) NOT NULL, + category varchar(50), + created_at timestamptz NOT NULL ) DISTRIBUTED BY (course_id); --DROP TABLE IF EXISTS core.dim_lesson; CREATE TABLE core.dim_lesson ( - lesson_id int PRIMARY KEY, - title text NOT NULL, - duration_min int, - course_id int NOT NULL REFERENCES core.dim_course(course_id) + lesson_id bigint PRIMARY KEY, + title varchar(150) NOT NULL, + duration_min smallint, + course_id bigint NOT NULL REFERENCES core.dim_course(course_id) ) DISTRIBUTED BY (lesson_id); --DROP TABLE IF EXISTS core.dim_date; @@ -37,17 +37,17 @@ CREATE TABLE core.dim_date ( -- 5 --DROP TABLE IF EXISTS core.fact_lesson_views; CREATE TABLE core.fact_lesson_views ( - user_id int NOT NULL REFERENCES core.dim_user(user_id), - lesson_id int NOT NULL REFERENCES core.dim_lesson(lesson_id), - course_id int NOT NULL REFERENCES core.dim_course(course_id), - viewed_at timestamp NOT NULL + user_id bigint NOT NULL REFERENCES core.dim_user(user_id), + lesson_id bigint NOT NULL REFERENCES core.dim_lesson(lesson_id), + course_id bigint NOT NULL REFERENCES core.dim_course(course_id), + viewed_at timestamptz NOT NULL ) DISTRIBUTED BY (user_id); --DROP TABLE IF EXISTS core.enrollments; CREATE TABLE core.enrollments ( - user_id int NOT NULL REFERENCES core.dim_user(user_id), - course_id int NOT NULL REFERENCES core.dim_course(course_id), - enrolled_at timestamp NOT NULL + user_id bigint NOT NULL REFERENCES core.dim_user(user_id), + course_id bigint NOT NULL REFERENCES core.dim_course(course_id), + enrolled_at timestamptz NOT NULL ) DISTRIBUTED BY (user_id); From 95456999b390eef71527212ec5ed7f61af9e4f60 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Fri, 24 Apr 2026 18:10:49 +0300 Subject: [PATCH 071/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203.=20=D0=A1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=A0=D0=90=D0=91=D0=9E?= =?UTF-8?q?=D0=A2=D0=90=20=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91?= =?UTF-8?q?=D0=9A=D0=90=D0=9C=D0=98=20-=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D1=83=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8=20=D0=B8=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=B5=D0=BD=D0=B8=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 108 ++++++++++++++---- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index eed6ab3..7af360d 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -23,6 +23,7 @@ CREATE TABLE core.dim_lesson ( duration_min smallint, course_id bigint NOT NULL REFERENCES core.dim_course(course_id) ) DISTRIBUTED BY (lesson_id); +CREATE INDEX idx_dim_lesson_course_id ON core.dim_lesson(course_id); --DROP TABLE IF EXISTS core.dim_date; CREATE TABLE core.dim_date ( @@ -52,34 +53,92 @@ CREATE TABLE core.enrollments ( --6 +-- dim_user INSERT INTO core.dim_user (user_id, name, age, email, registration_date) -SELECT DISTINCT - id::int AS user_id, - name::text, - age::int, - email::text, - registration_date::date -FROM raw.users -WHERE id IS NOT NULL AND email IS NOT NULL; - -INSERT INTO core.dim_course (course_id, title, category) -SELECT DISTINCT - id::int AS course_id, - title::text, - category::text +SELECT + raw_u.id, + raw_u.name, + raw_u.age, + raw_u.email, + raw_u.registration_date FROM - raw.courses -WHERE id IS NOT NULL AND title IS NOT NULL; + raw.users raw_u +WHERE NOT EXISTS + (SELECT 1 FROM core.dim_user core_u WHERE core_u.user_id = raw_u.id) + AND raw_u.email IS NOT NULL AND raw_u.registration_date IS NOT NULL; +UPDATE core.dim_user core_u +SET + name = raw_u.name, + age = raw_u.age, + email = raw_u.email, + registration_date = raw_u.registration_date +FROM + raw.users raw_u +WHERE + core_u.user_id = raw_u.id + AND ( + core_u.name != raw_u.name OR + core_u.age != raw_u.age OR + core_u.email != raw_u.email OR + core_u.registration_date != raw_u.registration_date + ); + +-- dim_course +INSERT INTO core.dim_course (course_id, title, category, created_at) +SELECT + raw_c.id, + raw_c.title, + raw_c.category, + raw_c.created_at +FROM + raw.courses raw_c +WHERE NOT EXISTS + (SELECT 1 FROM core.dim_course core_c WHERE core_c.course_id = raw_c.id) + AND raw_c.title IS NOT NULL AND raw_c; + +UPDATE core.dim_course core_c +SET + title = raw_c.title, + category = raw_c.category, + created_at = raw_c.created_at +FROM + raw.courses raw_c +WHERE + core_c.course_id = raw_c.id + AND ( + core_c.title != raw_c.title OR + core_c.category != raw_c.category OR + core_c.created_at != raw_c.created_at + ); + +-- dim_lesson INSERT INTO core.dim_lesson (lesson_id, title, duration_min, course_id) -SELECT DISTINCT - id::int AS lesson_id, - title::text, - duration_min::int, - course_id::int +SELECT + raw_l.id, + raw_l.title, + raw_l.duration_min, + raw_l.course_id FROM - raw.lessons -WHERE id IS NOT NULL; + raw.lessons raw_l +WHERE NOT EXISTS + (SELECT 1 FROM core.dim_lesson core_l WHERE core_l.lesson_id = raw_l.id) + AND raw_l.title IS NOT NULL AND raw_l.course_id IS NOT NULL; + +UPDATE core.dim_lesson core_l +SET + title = raw_l.title, + duration_min = raw_l.duration_min, + course_id = raw_l.course_id -- решил оставить, тк чисто теоретические же урок может начать принадлежать другому курсу +FROM + raw.lessons raw_l +WHERE + core_l.lesson_id = raw_l.id + AND ( + core_l.title != raw_l.title OR + core_l.duration_min != raw_l.duration_min OR + core_l.course_id != raw_l.course_id + ); -- загуглил как заполнять: INSERT INTO core.dim_date (date, year, month, day, weekday) @@ -90,7 +149,8 @@ SELECT EXTRACT(DAY FROM d)::int, to_char(d, 'day') FROM - generate_series('2023-01-01'::date, '2025-12-31'::date, INTERVAL '1 day') AS d; + generate_series('2023-01-01'::date, '2030-12-31'::date, INTERVAL '1 day') AS d; +-- получается dim_date загружается один раз, и должно храниться где-то отдельно? INSERT INTO core.fact_lesson_views (user_id, lesson_id, course_id, viewed_at) SELECT From add8d776e5d4c6c0ed39cd642b5edbd18dea6b0c Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sat, 25 Apr 2026 18:11:21 +0300 Subject: [PATCH 072/123] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=203.=20=D0=A1?= =?UTF-8?q?=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1=D0=BE?= =?UTF-8?q?=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=A0=D0=90=D0=91=D0=9E?= =?UTF-8?q?=D0=A2=D0=90=20=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91?= =?UTF-8?q?=D0=9A=D0=90=D0=9C=D0=98=20-=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=20insert/update=20=D0=B2=20core,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index 7af360d..c44753d 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -43,6 +43,7 @@ CREATE TABLE core.fact_lesson_views ( course_id bigint NOT NULL REFERENCES core.dim_course(course_id), viewed_at timestamptz NOT NULL ) DISTRIBUTED BY (user_id); +CREATE INDEX idx_fact_lesson_views_viewed_at ON core.fact_lesson_views(viewed_at); --DROP TABLE IF EXISTS core.enrollments; CREATE TABLE core.enrollments ( @@ -63,8 +64,8 @@ SELECT raw_u.registration_date FROM raw.users raw_u -WHERE NOT EXISTS - (SELECT 1 FROM core.dim_user core_u WHERE core_u.user_id = raw_u.id) +WHERE + NOT EXISTS (SELECT 1 FROM core.dim_user core_u WHERE core_u.user_id = raw_u.id) AND raw_u.email IS NOT NULL AND raw_u.registration_date IS NOT NULL; UPDATE core.dim_user core_u @@ -93,9 +94,9 @@ SELECT raw_c.created_at FROM raw.courses raw_c -WHERE NOT EXISTS - (SELECT 1 FROM core.dim_course core_c WHERE core_c.course_id = raw_c.id) - AND raw_c.title IS NOT NULL AND raw_c; +WHERE + NOT EXISTS (SELECT 1 FROM core.dim_course core_c WHERE core_c.course_id = raw_c.id) + AND raw_c.title IS NOT NULL AND raw_c.created_at IS NOT NULL; UPDATE core.dim_course core_c SET @@ -121,8 +122,8 @@ SELECT raw_l.course_id FROM raw.lessons raw_l -WHERE NOT EXISTS - (SELECT 1 FROM core.dim_lesson core_l WHERE core_l.lesson_id = raw_l.id) +WHERE + NOT EXISTS (SELECT 1 FROM core.dim_lesson core_l WHERE core_l.lesson_id = raw_l.id) AND raw_l.title IS NOT NULL AND raw_l.course_id IS NOT NULL; UPDATE core.dim_lesson core_l @@ -140,7 +141,7 @@ WHERE core_l.course_id != raw_l.course_id ); --- загуглил как заполнять: +-- загуглил как заполнять dim_date: INSERT INTO core.dim_date (date, year, month, day, weekday) SELECT d::date, @@ -150,23 +151,32 @@ SELECT to_char(d, 'day') FROM generate_series('2023-01-01'::date, '2030-12-31'::date, INTERVAL '1 day') AS d; --- получается dim_date загружается один раз, и должно храниться где-то отдельно? INSERT INTO core.fact_lesson_views (user_id, lesson_id, course_id, viewed_at) SELECT - lv.user_id::int, - lv.lesson_id::int, - l.course_id::int, - lv.viewed_at::timestamp + raw_lv.user_id, + raw_lv.lesson_id, + raw_l.course_id, + raw_lv.viewed_at FROM - raw.lesson_views lv + raw.lesson_views raw_lv JOIN - raw.lessons l ON lv.lesson_id = l.id; + raw.lessons raw_l ON raw_lv.lesson_id = raw_l.id +WHERE + raw_lv.viewed_at > (SELECT max(viewed_at) FROM core.fact_lesson_views) -- но при первой загрузке это не сработало, тк max(vied_at) = NULL, как это исправить - я не знаю + AND raw_lv.user_id IS NOT NULL AND raw_lv.lesson_id IS NOT NULL + AND raw_l.course_id IS NOT NULL AND raw_lv.viewed_at IS NOT NULL; INSERT INTO core.enrollments (user_id, course_id, enrolled_at) SELECT - user_id::int, - course_id::int, - enrolled_at::timestamp + raw_e.user_id, + raw_e.course_id, + raw_e.enrolled_at FROM - raw.enrollments; \ No newline at end of file + raw.enrollments raw_e +WHERE + NOT EXISTS (SELECT 1 FROM core.enrollments core_e WHERE + core_e.user_id = raw_e.user_id + AND core_e.course_id = raw_e.course_id + AND core_e.enrolled_at = raw_e.enrolled_at) + AND raw_e.user_id IS NOT NULL AND raw_e.course_id IS NOT NULL AND raw_e.enrolled_at IS NOT NULL; \ No newline at end of file From 66aa47ef26ed1287e3929ae8faab8adabc29fb41 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 26 Apr 2026 13:44:42 +0300 Subject: [PATCH 073/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=AD=D1=82=D0=B0=D0=BF=203.=20?= =?UTF-8?q?=D0=A1=D0=BB=D0=BE=D0=B9=20Core=20=E2=80=94=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BE=D0=B3=D0=B0=D1=89=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5.=20=D0=A0=D0=90=D0=91=D0=9E?= =?UTF-8?q?=D0=A2=D0=90=20=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91?= =?UTF-8?q?=D0=9A=D0=90=D0=9C=D0=98=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B5=D1=89=D1=91=20=D0=B8=D0=BD=D0=B4=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D0=BE=D0=B2=20=D0=B2=20fact=5Flesson=5Fviews=20?= =?UTF-8?q?=D0=B8=20=D0=B2=20enrollments.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index c44753d..e03b401 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -44,6 +44,9 @@ CREATE TABLE core.fact_lesson_views ( viewed_at timestamptz NOT NULL ) DISTRIBUTED BY (user_id); CREATE INDEX idx_fact_lesson_views_viewed_at ON core.fact_lesson_views(viewed_at); +CREATE INDEX idx_fact_lesson_views_user_id ON core.fact_lesson_views(user_id); +CREATE INDEX idx_fact_lesson_views_lesson_id ON core.fact_lesson_views(lesson_id); +CREATE INDEX idx_fact_lesson_views_course_id ON core.fact_lesson_views(course_id); --DROP TABLE IF EXISTS core.enrollments; CREATE TABLE core.enrollments ( @@ -51,6 +54,8 @@ CREATE TABLE core.enrollments ( course_id bigint NOT NULL REFERENCES core.dim_course(course_id), enrolled_at timestamptz NOT NULL ) DISTRIBUTED BY (user_id); +CREATE INDEX idx_enrollments_user_id ON core.enrollments(user_id); +CREATE INDEX idx_enrollments_course_id ON core.enrollments(course_id); --6 From 8fd5510097ef80dcbc8e0d33d4cb37ff2f48769d Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 26 Apr 2026 18:14:40 +0300 Subject: [PATCH 074/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=AD=D1=82=D0=B0=D0=BF=204:=20?= =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=20=D0=B2=20Greenplum.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Step_4_preparing_display_cases_at_greenplum.sql | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql new file mode 100644 index 0000000..de2dac6 --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql @@ -0,0 +1,90 @@ +-- 7 +--DROP MATERIALIZED VIEW dm.lesson_popularity_summary; +CREATE MATERIALIZED VIEW dm.lesson_popularity_summary AS +WITH results_fact_lesson_views AS ( + SELECT + flv.lesson_id, + count(flv.user_id) AS total_views, + count(DISTINCT flv.user_id) AS unique_users, + min(flv.viewed_at)::date AS first_view, + max(flv.viewed_at)::date AS last_view + FROM + core.fact_lesson_views flv + GROUP BY + flv.lesson_id +) +SELECT + dl.lesson_id, + dl.title AS lesson_title, + dl.course_id, + dc.title AS course_title, + rflv.total_views, + rflv.unique_users, + rflv.first_view, + rflv.last_view +FROM + core.dim_lesson dl +JOIN + core.dim_course dc ON dl.course_id = dc.course_id +LEFT JOIN + results_fact_lesson_views rflv ON dl.lesson_id = rflv.lesson_id; + + +-- 8 +--DROP MATERIALIZED VIEW dm.inactive_users_summary; +CREATE MATERIALIZED VIEW dm.inactive_users_summary AS +SELECT + du.user_id, + du.name AS user_name, + du.email, + du.age, + 0 AS enrollments_count, + du.registration_date +FROM + core.dim_user du +WHERE + NOT EXISTS (SELECT 1 FROM core.fact_lesson_views flv WHERE flv.user_id = du.user_id); +-- тут странно, в условии сказано зачем-то enrollment присоединять, но делается все и без него отлично + + +-- 9 +--DROP MATERIALIZED VIEW dm.course_completion_rate; +CREATE MATERIALIZED VIEW dm.course_completion_rate AS +WITH results_dim_course_lesson AS ( + SELECT + dc.course_id, + dc.title, + count(lesson_id) AS lessons_in_course + FROM + core.dim_course dc + JOIN + core.dim_lesson dl ON dc.course_id = dl.course_id + GROUP BY + dc.course_id +), +results_fact_lesson_views AS ( + SELECT + flv.user_id, + flv.course_id, + count(DISTINCT lesson_id) AS lessons_viewed + FROM + core.fact_lesson_views flv + GROUP BY + flv.user_id, flv.course_id +) +SELECT + du.user_id, + du.name AS user_name, + rdcl.course_id, + rdcl.title AS course_title, + rdcl.lessons_in_course, + COALESCE(rflv.lessons_viewed, 0) AS lessons_viewed, + round(COALESCE(rflv.lessons_viewed, 0) / NULLIF(rdcl.lessons_in_course, 0)::numeric, 2) AS completion_rate +FROM + core.dim_user du +JOIN + core.enrollments e ON du.user_id = e.user_id +JOIN + results_dim_course_lesson rdcl ON e.course_id = rdcl.course_id +LEFT JOIN + results_fact_lesson_views rflv ON e.user_id = rflv.user_id AND e.course_id = rflv.course_id; \ No newline at end of file From 4b51ffb724c8fdb0eddf1defc1ea0d929918bcbe Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 26 Apr 2026 18:21:34 +0300 Subject: [PATCH 075/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20DWH.=20=D0=AD=D1=82=D0=B0=D0=BF=205.=20?= =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql b/mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql new file mode 100644 index 0000000..f2d8cb9 --- /dev/null +++ b/mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql @@ -0,0 +1,4 @@ +SELECT * FROM dm.lesson_popularity_summary ORDER BY lesson_id LIMIT 10; +SELECT * FROM dm.inactive_users_summary ORDER BY user_id LIMIT 10; +SELECT * FROM dm.course_completion_rate ORDER BY user_id, course_id; +--пришлось добавить order_by для красивого вывода, потому что создать витрины отсортированными не получается \ No newline at end of file From 347334dc26e23ad8121e35e62554986021306004 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 28 Apr 2026 04:30:01 +0300 Subject: [PATCH 076/123] =?UTF-8?q?=D0=A0=D0=90=D0=91=D0=9E=D0=A2=D0=90=20?= =?UTF-8?q?=D0=9D=D0=90=D0=94=20=D0=9E=D0=A8=D0=98=D0=91=D0=9A=D0=90=D0=9C?= =?UTF-8?q?=D0=98.=20=D0=9F=D0=B5=D1=82=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA?= =?UTF-8?q?=D1=82:=20DWH.=20=D0=AD=D1=82=D0=B0=D0=BF=204:=20=D0=9F=D0=BE?= =?UTF-8?q?=D0=B4=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BD=20=D0=B2=20Greenplum.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Пет проект: DWH/Step_3_core_layer.sql | 2 ++ .../Step_4_preparing_display_cases_at_greenplum.sql | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql index e03b401..a804a5c 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql @@ -157,6 +157,7 @@ SELECT FROM generate_series('2023-01-01'::date, '2030-12-31'::date, INTERVAL '1 day') AS d; +-- fact_lesson_views INSERT INTO core.fact_lesson_views (user_id, lesson_id, course_id, viewed_at) SELECT raw_lv.user_id, @@ -172,6 +173,7 @@ WHERE AND raw_lv.user_id IS NOT NULL AND raw_lv.lesson_id IS NOT NULL AND raw_l.course_id IS NOT NULL AND raw_lv.viewed_at IS NOT NULL; +-- enrollments INSERT INTO core.enrollments (user_id, course_id, enrolled_at) SELECT raw_e.user_id, diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql index de2dac6..be37917 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql @@ -38,13 +38,22 @@ SELECT du.name AS user_name, du.email, du.age, - 0 AS enrollments_count, + e.enrollments_count, du.registration_date FROM core.dim_user du +LEFT JOIN ( + SELECT + user_id, + count(course_id) AS enrollments_count + FROM + core.enrollments + GROUP BY + user_id +) e ON du.user_id = e.user_id WHERE NOT EXISTS (SELECT 1 FROM core.fact_lesson_views flv WHERE flv.user_id = du.user_id); --- тут странно, в условии сказано зачем-то enrollment присоединять, но делается все и без него отлично +-- интересно, что при проверки плана запроса, когда кусок с enrollments_count был в CTE, выдавал какую-то хуйню, а когда я засунул его в подзапрос, сразу инлексы заработали, и строки начали эффективней читаться -- 9 @@ -60,7 +69,7 @@ WITH results_dim_course_lesson AS ( JOIN core.dim_lesson dl ON dc.course_id = dl.course_id GROUP BY - dc.course_id + dc.course_id, dc.title ), results_fact_lesson_views AS ( SELECT @@ -79,7 +88,7 @@ SELECT rdcl.title AS course_title, rdcl.lessons_in_course, COALESCE(rflv.lessons_viewed, 0) AS lessons_viewed, - round(COALESCE(rflv.lessons_viewed, 0) / NULLIF(rdcl.lessons_in_course, 0)::numeric, 2) AS completion_rate + round(COALESCE(rflv.lessons_viewed, 0)::numeric / NULLIF(rdcl.lessons_in_course, 0), 2) AS completion_rate FROM core.dim_user du JOIN From 2ced7cf273b03896b1e2c1d2a3c63024b83dafe2 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 30 Apr 2026 22:58:25 +0400 Subject: [PATCH 077/123] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D1=87=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BE=D1=82=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D1=83=20=D0=97=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=8B=20-=20JOIN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clickhouse/Homework/Запросы - JOIN.sql | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Запросы - JOIN.sql diff --git a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql new file mode 100644 index 0000000..29496d9 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql @@ -0,0 +1,70 @@ + +-- Задачи на отработку Запросы - JOIN -- + +DROP TABLE IF EXISTS dsolovianov.products ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.prices ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE products +ON CLUSTER clickhouse_cluster +( + product_id UInt32, + name String +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/products', + '{replica}' +) +ORDER BY product_id; + + +INSERT INTO products VALUES +(101, 'iPhone 14'), +(102, 'Galaxy S22'), +(103, 'Pixel 7'); + +CREATE TABLE prices +ON CLUSTER clickhouse_cluster +( + product_id UInt32, + price UInt32, + region String +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/prices', + '{replica}' +) +ORDER BY product_id; + +INSERT INTO prices VALUES +(101, 999, 'US'), +(101, 1049, 'EU'), +(102, 850, 'US'), +(102, 899, 'CA'), +(103, 799, 'JP'); + + +--№1 Сделай ANY LEFT JOIN, чтобы получить цену любой из регионов. + +SELECT + p.product_id, + p.name, + pr.price, + pr.region +FROM products AS p +ANY LEFT JOIN prices AS pr ON p.product_id = pr.product_id; + +--№2 Что будет, если в таблице `prices` несколько цен на один `product_id`? + +/* Для одного product_id будет выбрана только первая ЛЮБАЯ (ANY) подходящая в поиске.*/ + +--№3 Как повлияет `ALL LEFT JOIN`? Почему в ClickHouse его избегают? + +SELECT + p.product_id, + p.name, + pr.price, + pr.region +FROM products AS p +ALL LEFT JOIN prices AS pr ON p.product_id = pr.product_id; + +/* `ALL LEFT JOIN` в Clickhouse ведет себя также, как LEFT JOIN в SQL, то есть собирает все совпадения. + Начинается перегон данных между таблицами. Из-за этого растет нагрузка на ОЗУ и CPU. + короче, по сути ALL LEFT JOIN создает декартово произведение*/ From 46a366c07a5fe579ae23b9cfac39c32ec69280ee Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Thu, 30 Apr 2026 23:59:43 +0400 Subject: [PATCH 078/123] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D1=85=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clickhouse/Homework/Запросы - JOIN.sql | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql index 29496d9..6d6edfa 100644 --- a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql +++ b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql @@ -4,7 +4,7 @@ DROP TABLE IF EXISTS dsolovianov.products ON CLUSTER clickhouse_cluster SYNC; DROP TABLE IF EXISTS dsolovianov.prices ON CLUSTER clickhouse_cluster SYNC; -CREATE TABLE products +CREATE TABLE dsolovianov.products ON CLUSTER clickhouse_cluster ( product_id UInt32, @@ -16,12 +16,12 @@ ON CLUSTER clickhouse_cluster ORDER BY product_id; -INSERT INTO products VALUES +INSERT INTO dsolovianov.products VALUES (101, 'iPhone 14'), (102, 'Galaxy S22'), (103, 'Pixel 7'); -CREATE TABLE prices +CREATE TABLE dsolovianov.prices ON CLUSTER clickhouse_cluster ( product_id UInt32, @@ -33,7 +33,7 @@ ON CLUSTER clickhouse_cluster ) ORDER BY product_id; -INSERT INTO prices VALUES +INSERT INTO dsolovianov.prices VALUES (101, 999, 'US'), (101, 1049, 'EU'), (102, 850, 'US'), @@ -48,8 +48,8 @@ SELECT p.name, pr.price, pr.region -FROM products AS p -ANY LEFT JOIN prices AS pr ON p.product_id = pr.product_id; +FROM dsolovianov.products AS p +ANY LEFT JOIN dsolovianov.prices AS pr ON p.product_id = pr.product_id; --№2 Что будет, если в таблице `prices` несколько цен на один `product_id`? @@ -62,8 +62,8 @@ SELECT p.name, pr.price, pr.region -FROM products AS p -ALL LEFT JOIN prices AS pr ON p.product_id = pr.product_id; +FROM dsolovianov.products AS p +ALL LEFT JOIN dsolovianov.prices AS pr ON p.product_id = pr.product_id; /* `ALL LEFT JOIN` в Clickhouse ведет себя также, как LEFT JOIN в SQL, то есть собирает все совпадения. Начинается перегон данных между таблицами. Из-за этого растет нагрузка на ОЗУ и CPU. From 4a602481a386862925262b5674acafa30d5e837f Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Fri, 1 May 2026 00:02:20 +0400 Subject: [PATCH 079/123] ORDER BY product_id, region IN TABLE dsolovianov.prices --- dsolovianov/clickhouse/Homework/Запросы - JOIN.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql index 6d6edfa..21108c6 100644 --- a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql +++ b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql @@ -31,7 +31,7 @@ ON CLUSTER clickhouse_cluster '/clickhouse/tables/{shard}/dsolovianov/prices', '{replica}' ) -ORDER BY product_id; +ORDER BY product_id, region; INSERT INTO dsolovianov.prices VALUES (101, 999, 'US'), From d1acaf78a69955c61bbd38dd5a6e5a799a2006fb Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Fri, 1 May 2026 00:06:47 +0400 Subject: [PATCH 080/123] =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B0=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsolovianov/clickhouse/Homework/Запросы - JOIN.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql index 21108c6..6d6edfa 100644 --- a/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql +++ b/dsolovianov/clickhouse/Homework/Запросы - JOIN.sql @@ -31,7 +31,7 @@ ON CLUSTER clickhouse_cluster '/clickhouse/tables/{shard}/dsolovianov/prices', '{replica}' ) -ORDER BY product_id, region; +ORDER BY product_id; INSERT INTO dsolovianov.prices VALUES (101, 999, 'US'), From 209cce48b2d60233217735a5c452395f0429e247 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 4 May 2026 07:16:07 +0400 Subject: [PATCH 081/123] =?UTF-8?q?=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D1=81=D0=B6=D0=B0=D1=82=D0=B8=D0=B5=20-=20?= =?UTF-8?q?=D0=9F=D0=B0=D1=80=D1=82=D0=B8=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Индексы и сжатие - Партиции.sql | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Индексы и сжатие - Партиции.sql diff --git a/dsolovianov/clickhouse/Homework/Индексы и сжатие - Партиции.sql b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Партиции.sql new file mode 100644 index 0000000..4e9ebb8 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Партиции.sql @@ -0,0 +1,146 @@ +-- Индексы и сжатие - Партиции -- + +-- #1 +/* +1. Создай таблицу sales с `PARTITION BY toYYYYMM(sale_date)`. +2. Выполни `ALTER TABLE ... DROP PARTITION '202405'`. +3. Что произойдет, если не указывать `PARTITION BY`? <- удалит все данные из таблицы. партиция то одна :) + +*/ + +DROP TABLE IF EXISTS dsolovianov.sales ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.sales +ON CLUSTER clickhouse_cluster +( + id UInt64, + region String, + sale_date Date, + amount Float64 +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/sales', + '{replica}' +) +PARTITION BY toYYYYMM(sale_date) +ORDER BY (region, sale_date); + +INSERT INTO dsolovianov.sales (id, region, sale_date, amount) VALUES +(1, 'RU', '2024-05-01', '100.0'), +(2, 'RU', '2024-05-15', '120.0'), +(3, 'US', '2024-06-01', '90.0'), +(4, 'US', '2024-07-03', '150.0'), +(5, 'DE', '2024-07-04', '160.0'); + +ALTER TABLE dsolovianov.sales DROP PARTITION '202405'; + +SELECT * +FROM dsolovianov.sales; + +-- #2 +/* +1. Создай таблицу с `PARTITION BY event_type`. +2. Удали все события `click` (подсказка: это `ALTER TABLE DROP PARTITION 'click'`). +3. Чем такой подход может быть опасен (подумай про кардинальность значений)? +При большом количестве уникальных event_type создастся много мелких партиций. +*/ +DROP TABLE IF EXISTS dsolovianov.events ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.events +ON CLUSTER clickhouse_cluster +( + id UInt64, + event_type String, + event_date Date +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/events', + '{replica}' +) +PARTITION BY event_type +ORDER BY (event_date, id); + +INSERT INTO dsolovianov.events (id, event_type, event_date) VALUES +(1, 'login', '2024-06-01'), +(2, 'click', '2024-06-01'), +(3, 'click', '2024-06-02'), +(4, 'purchase', '2024-06-03'), +(5, 'login', '2024-06-04'); + +ALTER TABLE dsolovianov.events DROP PARTITION 'click'; + +-- #3 +/* +1. Создай таблицу с `PARTITION BY cityHash64(user_id) % 100`. +2. Что ты выиграешь с точки зрения баланса? +Данные распределятся примерно поровну между 100 партициями. Упрощается управление удалением старых данных (можно дропать целые партиции) +3. Почему такой тип `PARTITION` не ускорит `SELECT * WHERE user_id = 456`? +Потому что партиции определяются хэшем, а не прямым значением user_id и к тому же сортировка в партициях по log_time, а не user_id. + +*/ + +DROP TABLE IF EXISTS dsolovianov.logs ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.logs +ON CLUSTER clickhouse_cluster +( + id UInt64, + user_id UInt64, + log_time DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/logs', + '{replica}' +) +PARTITION BY cityHash64(user_id) % 100 -- 100 партиций по хэшу user_id +ORDER BY (log_time, id); + +INSERT INTO dsolovianov.logs (id, user_id, log_time) VALUES +(1, 123, '2024-07-17 10:00:00'), +(2, 456, '2024-07-17 10:01:00'), +(3, 789, '2024-07-17 10:02:00'); + +SELECT + id, + user_id, + cityHash64(user_id) % 100 AS partition_number +FROM dsolovianov.logs +ORDER BY id; + +SELECT * FROM logs WHERE user_id = 456; + + +-- #4 +/* +1. Создай таблицу с `PARTITION BY (region, toYYYYMM(visit_date))`. +2. Как будет выглядеть имя партиции? +Имена партиций будут в формате: ('значение_region', 'YYYYMM'). Пример: ('RU', '202406') +3. Сможешь ли ты эффективно удалить **все июльские данные по `RU`**? Да. Удаляется целая партиция без сканирования и нагрузки на систему. +*/ + +DROP TABLE IF EXISTS dsolovianov.visits ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.visits +ON CLUSTER clickhouse_cluster +( + id UInt64, + region String, + visit_date Date +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/visits', + '{replica}' +) +PARTITION BY (region, toYYYYMM(visit_date)) +ORDER BY (region, visit_date, id); + +INSERT INTO dsolovianov.visits (id, region, visit_date) VALUES +(1, 'RU', '2024-06-01'), +(2, 'RU', '2024-06-15'), +(3, 'US', '2024-06-03'), +(4, 'US', '2024-07-01'), +(5, 'RU', '2024-07-05'); + +ALTER TABLE dsolovianov.visits ON CLUSTER clickhouse_cluster +DROP PARTITION ('RU', '202407'); + +SELECT * FROM dsolovianov.visits; \ No newline at end of file From 1de11c1d5e7a45835fa6c8ae39f36c1f734a2dd1 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 4 May 2026 08:09:54 +0400 Subject: [PATCH 082/123] =?UTF-8?q?=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D1=81=D0=B6=D0=B0=D1=82=D0=B8=D0=B5=20-=20?= =?UTF-8?q?=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Индексы и сжатие - Индексы.sql | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Индексы и сжатие - Индексы.sql diff --git a/dsolovianov/clickhouse/Homework/Индексы и сжатие - Индексы.sql b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Индексы.sql new file mode 100644 index 0000000..9332b35 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Индексы.sql @@ -0,0 +1,166 @@ +-- Индексы и сжатие - Индексы -- + +-- #1 +/* +3. Посмотри, сколько блоков было пропущено (в `system.query_log`, `rows_read`, `rows`). +Не посмотрю, так как нет доступа system.query_log, rows_read, rows (ACCESS_DENIED) +4. Что будет, если `ORDER BY log_id`, а не `ts`? +ORDER BY log_id → данные физически отсортированы по log_id. Значения ts расположены хаотично относительно физической сортировки. +Индекс minmax по ts будет почти бесполезен. Minmax индекс будет иметь широкие диапазоны +*/ + +DROP TABLE IF EXISTS dsolovianov.logs ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.logs +ON CLUSTER clickhouse_cluster +( + log_id UInt64, + ts DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/logs', + '{replica}' +) +ORDER BY ts +PARTITION BY toYYYYMM(ts) +PRIMARY KEY (ts); + +INSERT INTO dsolovianov.logs (log_id, ts) VALUES +(1, '2024-06-01 10:00:00'), +(2, '2024-06-01 11:00:00'), +(3, '2024-06-02 12:00:00'), +(4, '2024-06-03 13:00:00'), +(5, '2024-06-04 14:00:00'); + +-- 1. Добавляем minmax индекс: +ALTER TABLE dsolovianov.logs ADD INDEX idx_ts ts TYPE minmax GRANULARITY 2; + +-- 2. Сделай запрос: `SELECT * FROM logs WHERE ts BETWEEN '2024-06-02' AND '2024-06-03'` +SELECT * +FROM dsolovianov.logs +WHERE ts + BETWEEN '2024-06-02' AND '2024-06-03'; + +-- Раз нет доступа, но можно предположить, что вот такой запрос сработает: +SELECT query, read_rows, result_rows +FROM system.query_log +WHERE query LIKE '%logs%' +ORDER BY event_time DESC +LIMIT 1; + + +-- #2 +/* +1. Создай таблицу с `ORDER BY id`, добавь индекс: + + ```sql + ALTER TABLE clicks ADD INDEX idx_country country TYPE set(1000) GRANULARITY 2; + ``` + + +3. Посмотри `EXPLAIN` — индекс используется? +Данные отсортированы по id, а не по country. Значения country распределены хаотически относительно физического порядка. +4. Как будет вести себя индекс, если `country` имеет 10M уникальных значений? +Будет создано 10M партиций, что убьёт производительность. +**/ +DROP TABLE IF EXISTS dsolovianov.clicks ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.clicks +ON CLUSTER clickhouse_cluster +( + id UInt64, + country String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/clicks', + '{replica}' +) +ORDER BY id +PARTITION BY country +PRIMARY KEY (id); + +INSERT INTO dsolovianov.clicks (id, country) VALUES +(1, 'RU'), +(2, 'US'), +(3, 'RU'), +(4, 'DE'), +(5, 'RU'); + +ALTER TABLE dsolovianov.clicks ON CLUSTER clickhouse_cluster +ADD INDEX idx_country country TYPE set(1000) GRANULARITY 2; + +-- 2. Выполни запрос `SELECT * FROM clicks WHERE country = 'RU'`. +EXPLAIN indexes = 1 +SELECT * FROM dsolovianov.clicks WHERE country = 'RU'; + +EXPLAIN +SELECT * FROM dsolovianov.clicks WHERE country = 'RU'; + +/* +Expression ((Project names + Projection)) + Expression ((WHERE + Change column names to column identifiers)) + ReadFromMergeTree (dsolovianov.clicks) + Indexes: + MinMax + Keys: + country + Condition: (country in ['RU', 'RU']) + Parts: 1/3 + Granules: 1/3 + Partition + Keys: + country + Condition: (country in ['RU', 'RU']) + Parts: 1/1 + Granules: 1/1 + PrimaryKey + Condition: true + Parts: 1/1 + Granules: 1/1 + Skip + Name: idx_country + Description: set GRANULARITY 2 + Parts: 1/1 + Granules: 1/1 + Ranges: 1 + */ + +-- #3 + +DROP TABLE IF EXISTS dsolovianov.users ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.users +ON CLUSTER clickhouse_cluster +( + id UInt64, + email String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/users', + '{replica}' +) +ORDER BY id +PRIMARY KEY (id); + +INSERT INTO dsolovianov.users (id, email) VALUES +(1, 'alice@mail.com'), +(2, 'bob@mail.com'), +(3, 'charlie@mail.com'), +(4, 'dave@mail.com'), +(5, 'eve@example.com'); + +ALTER TABLE dsolovianov.users ADD INDEX idx_email email TYPE bloom_filter(0.01) GRANULARITY 2; + +EXPLAIN indexes = 1 +SELECT * FROM users WHERE email = 'alice@mail.com'; +-- Bloom filter может пропустить некоторые гранулы, но не все, ибо данные не отсортированы по email и индексные блоки покрывают случайные email'ы + +EXPLAIN indexes = 1 +SELECT * FROM users WHERE email LIKE '%mail%'; + +EXPLAIN indexes = 1 +SELECT * FROM users WHERE email LIKE 'alice%'; +/* +Bloom filter не поддерживает частичные совпадения (LIKE '..%', LIKE '%..', LIKE '%..%'). Из-за этого ClickHouse не может пропустить гранулы. +Клику становится сложно вычислить хэш для проверки, если подходящих значений по LIKE будет больше одного. Нет гарантии, что данные на выходе будут корректные. +*/ \ No newline at end of file From c654840ffdbbc003bb92a186980c507ac90b9b8e Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 4 May 2026 09:15:50 +0400 Subject: [PATCH 083/123] =?UTF-8?q?=D0=98=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D1=81=D0=B6=D0=B0=D1=82=D0=B8=D0=B5=20-=20?= =?UTF-8?q?=D0=A1=D0=B6=D0=B0=D1=82=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Homework/Индексы и сжатие - Сжатие.sql | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 dsolovianov/clickhouse/Homework/Индексы и сжатие - Сжатие.sql diff --git a/dsolovianov/clickhouse/Homework/Индексы и сжатие - Сжатие.sql b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Сжатие.sql new file mode 100644 index 0000000..ce28674 --- /dev/null +++ b/dsolovianov/clickhouse/Homework/Индексы и сжатие - Сжатие.sql @@ -0,0 +1,39 @@ +-- Индексы и сжатие - Сжатие -- + +-- #1 Создай таблицу logs с колонкой message CODEC(ZSTD(3)). +-- #2 Добавь created_at CODEC(Delta, ZSTD) — это улучшит хранение по времени. +DROP TABLE IF EXISTS dsolovianov.logs ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.logs +ON CLUSTER clickhouse_cluster +( + id UInt64, + level String, + message String CODEC(ZSTD(3)), + created_at DateTime CODEC(Delta, ZSTD) +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/logs', + '{replica}' +) +ORDER BY (created_at, id) +PARTITION BY toYYYYMM(created_at); + +INSERT INTO dsolovianov.logs (id, level, message, created_at) VALUES +(1, 'INFO', 'User logged in', '2024-07-01 10:00:00'), +(2, 'WARN', 'Request took too long', '2024-07-01 10:05:00'), +(3, 'ERROR', 'Unable to connect to server', '2024-07-01 10:10:00'); + +-- #3 Посмотри статистику сжатия: +SELECT + column, + compression_codec +FROM system.columns +WHERE table = 'logs'; + +-- Стата вот так выглядит: +column | compression_codec +id +level +message CODEC(ZSTD(3)) +created_at CODEC(Delta(4), ZSTD(1)) \ No newline at end of file From 3f4fe5f0157a28f2eb0288aba523ba3120677bee Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Wed, 6 May 2026 22:31:35 +0300 Subject: [PATCH 084/123] add home work on ddl --- vberetar/postgresql/ddl.sql | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 vberetar/postgresql/ddl.sql diff --git a/vberetar/postgresql/ddl.sql b/vberetar/postgresql/ddl.sql new file mode 100644 index 0000000..19fd57f --- /dev/null +++ b/vberetar/postgresql/ddl.sql @@ -0,0 +1,39 @@ +CREATE TABLE users ( + id INT PRIMARY KEY NOT NULL, + name VARCHAR(100) NOT NULL, + age SMALLINT, + email VARCHAR(100), + country VARCHAR(50), + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ NOT NULL +); +-- age - для возраста не нужны болшие числа +-- country - можно было бы использовать VARCHAR(2), я бы тут спросил у тимлида как будет удобней использовать поле) +-- created_at - нужно знать не только дату но и время создания/регистрации +-- id - если не прописать not null в primary key, то not null ставится автоматически? + +CREATE TABLE products ( + id INT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + category VARCHAR(50), + price DECIMAL(10, 2) NOT NULL, + in_stock BOOLEAN +); + +CREATE TABLE orders( + id INT PRIMARY KEY, + user_id INT, + order_date TIMESTAMPTZ NOT NULL, + status VARCHAR(20), + FOREING KEY (user_id) REFERENCES users(id) +); +-- status - лучше прописать словами статус, так понятней будет. Я бы сюда поставил ENUM + +CREATE TABLE order_items( + order_id INT, + product_id INT, + quantity INT NOT NULL, + price_at_purchase DECIMAL(10, 2) NOT NULL, + FOREING KEY (order_id) REFERENCES orders(id), + FOREING KEY (product_id) REFERENCES products(id) +); \ No newline at end of file From 06ad98f4faf3d7ad6812f0b5661247f12edae62c Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Fri, 8 May 2026 11:46:36 +0300 Subject: [PATCH 085/123] fix columns in tables --- vberetar/postgresql/ddl.sql | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/vberetar/postgresql/ddl.sql b/vberetar/postgresql/ddl.sql index 19fd57f..ce86724 100644 --- a/vberetar/postgresql/ddl.sql +++ b/vberetar/postgresql/ddl.sql @@ -1,8 +1,8 @@ CREATE TABLE users ( - id INT PRIMARY KEY NOT NULL, + id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, - age SMALLINT, - email VARCHAR(100), + age SMALLINT CHECK (age > 0), + email VARCHAR(100) NOT NULL UNIQUE, country VARCHAR(50), is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ NOT NULL @@ -13,27 +13,26 @@ CREATE TABLE users ( -- id - если не прописать not null в primary key, то not null ставится автоматически? CREATE TABLE products ( - id INT PRIMARY KEY, + id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10, 2) NOT NULL, - in_stock BOOLEAN + in_stock BOOLEAN DEFAULT true, ); CREATE TABLE orders( - id INT PRIMARY KEY, + id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id INT, order_date TIMESTAMPTZ NOT NULL, status VARCHAR(20), - FOREING KEY (user_id) REFERENCES users(id) + FOREIGN KEY (user_id) REFERENCES users(id) ); -- status - лучше прописать словами статус, так понятней будет. Я бы сюда поставил ENUM CREATE TABLE order_items( - order_id INT, - product_id INT, + order_id INT NOT NULL, + product_id INT NOT NULL, quantity INT NOT NULL, price_at_purchase DECIMAL(10, 2) NOT NULL, - FOREING KEY (order_id) REFERENCES orders(id), - FOREING KEY (product_id) REFERENCES products(id) + PRIMARY KEY (order_id, product_id) ); \ No newline at end of file From a3342be276339ca64133bd07969e8488f67aacd8 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Fri, 8 May 2026 11:50:55 +0300 Subject: [PATCH 086/123] add DML home work --- vberetar/postgresql/dml.sql | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 vberetar/postgresql/dml.sql diff --git a/vberetar/postgresql/dml.sql b/vberetar/postgresql/dml.sql new file mode 100644 index 0000000..8829f99 --- /dev/null +++ b/vberetar/postgresql/dml.sql @@ -0,0 +1,42 @@ +INSERT INTO users (id, name, age, email, country, is_active, created_at) +VALUES (1, 'Alice', 25, 'alice@example.com', 'US', true, '2024-01-10 10:00:00'), + (2, 'Bob', 30, 'bob@example.com', 'UK', true, '2024-02-05 09:30:00'), + (3, 'Carol', 27, 'carol@example.com', 'US', false, '2024-03-01 15:20:00'), + (4, 'Diana', 22, 'diana@GMAIL.com', 'US', true, '2024-01-12 08:15:00'), + (5, 'Evan', 35, 'EVAN@example.com', 'UK', true, '2024-01-18 19:40:00'), + (6, 'Farida', 29, 'farida@example.com', 'KZ', true, '2024-02-20 11:05:00'), + (7, 'George', 31, 'george+test@example.com', 'US', true, '2024-03-05 16:45:00'), + (8, 'Helen', 24, NULL, 'UK', false, '2024-03-06 08:20:00'), + (9, 'Ivan', 26, 'ivan@example.com', 'RU', true, '2024-01-11 14:30:00'), + (10, 'Julia', 28, 'julia@example.com', 'US', true, '2024-03-07 18:05:00'); + +INSERT INTO products (id, name, category, price, in_stock) +VALUES (1, 'iPhone 15', 'Электроника', 80000, true), + (2, 'Kindle', 'Электроника', 15000, true), + (3, 'Кроссовки Nike', 'Одежда', 9000, true), + (4, 'Кроссовки NoName', 'Одежда', 3000, false), + (5, 'Наушники Pro', 'Электроника', 12000, true), + (6, 'Фитнес‑браслет', 'Гаджеты', 6000, true), + (7, 'Подарочная карта', 'Сервисы', 1000, true); + +INSERT INTO orders (id, user_id, order_date, status) +VALUES (1, 1, '2025-01-10 12:00:00', 'paid'), + (2, 3, '2025-01-12 09:30:00', 'cancelled'), + (3, 1, '2025-01-15 18:20:00', 'paid'), + (4, 2, '2025-01-20 11:05:00', 'created'), + (5, 4, '2025-02-01 17:45:00', 'paid'), + (6, 5, '2025-02-10 10:10:00', 'refunded'), + (7, 7, '2025-03-01 09:00:00', 'paid'), + (8, 9, '2025-03-05 19:30:00', 'paid'); + +INSERT INTO order_items (order_id, product_id, quantity, price_at_purchase) +VALUES (1, 1, 1, 78000), + (1, 3, 2, 8500), + (2, 2, 1, 14000), + (3, 5, 1, 11000), + (3, 7, 1, 1000), + (4, 2, 1, 15000), + (5, 6, 1, 6000), + (6, 1, 1, 82000), + (7, 3, 1, 9000), + (8, 4, 2, 2800); From 4ffe6cac39463fa423246172f0df747f0b481817 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Sun, 10 May 2026 21:57:26 +0300 Subject: [PATCH 087/123] fix query on dml.sql --- vberetar/postgresql/dml.sql | 55 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/vberetar/postgresql/dml.sql b/vberetar/postgresql/dml.sql index 8829f99..24e8d83 100644 --- a/vberetar/postgresql/dml.sql +++ b/vberetar/postgresql/dml.sql @@ -1,33 +1,32 @@ -INSERT INTO users (id, name, age, email, country, is_active, created_at) -VALUES (1, 'Alice', 25, 'alice@example.com', 'US', true, '2024-01-10 10:00:00'), - (2, 'Bob', 30, 'bob@example.com', 'UK', true, '2024-02-05 09:30:00'), - (3, 'Carol', 27, 'carol@example.com', 'US', false, '2024-03-01 15:20:00'), - (4, 'Diana', 22, 'diana@GMAIL.com', 'US', true, '2024-01-12 08:15:00'), - (5, 'Evan', 35, 'EVAN@example.com', 'UK', true, '2024-01-18 19:40:00'), - (6, 'Farida', 29, 'farida@example.com', 'KZ', true, '2024-02-20 11:05:00'), - (7, 'George', 31, 'george+test@example.com', 'US', true, '2024-03-05 16:45:00'), - (8, 'Helen', 24, NULL, 'UK', false, '2024-03-06 08:20:00'), - (9, 'Ivan', 26, 'ivan@example.com', 'RU', true, '2024-01-11 14:30:00'), - (10, 'Julia', 28, 'julia@example.com', 'US', true, '2024-03-07 18:05:00'); +INSERT INTO users (name, age, email, country, is_active, created_at) +VALUES ('Alice', 25, LOWER('alice@example.com'), 'US', true, '2024-01-10 10:00:00'), + ('Bob', 30, LOWER('bob@example.com'), 'UK', true, '2024-02-05 09:30:00'), + ('Carol', 27, LOWER('carol@example.com'), 'US', false, '2024-03-01 15:20:00'), + ('Diana', 22, LOWER('diana@GMAIL.com'), 'US', true, '2024-01-12 08:15:00'), + ('Evan', 35, LOWER('EVAN@example.com'), 'UK', true, '2024-01-18 19:40:00'), + ('Farida', 29, LOWER('farida@example.com'), 'KZ', true, '2024-02-20 11:05:00'), + ('George', 31, LOWER('george+test@example.com'), 'US', true, '2024-03-05 16:45:00'), + ('Ivan', 26, LOWER('ivan@example.com'), 'RU', true, '2024-01-11 14:30:00'), + ('Julia', 28, LOWER('julia@example.com'), 'US', true, '2024-03-07 18:05:00'); -INSERT INTO products (id, name, category, price, in_stock) -VALUES (1, 'iPhone 15', 'Электроника', 80000, true), - (2, 'Kindle', 'Электроника', 15000, true), - (3, 'Кроссовки Nike', 'Одежда', 9000, true), - (4, 'Кроссовки NoName', 'Одежда', 3000, false), - (5, 'Наушники Pro', 'Электроника', 12000, true), - (6, 'Фитнес‑браслет', 'Гаджеты', 6000, true), - (7, 'Подарочная карта', 'Сервисы', 1000, true); +INSERT INTO products (name, category, price, in_stock) +VALUES ('iPhone 15', 'Электроника', 80000, true), + ('Kindle', 'Электроника', 15000, true), + ('Кроссовки Nike', 'Одежда', 9000, true), + ('Кроссовки NoName', 'Одежда', 3000, false), + ('Наушники Pro', 'Электроника', 12000, true), + ('Фитнес‑браслет', 'Гаджеты', 6000, true), + ('Подарочная карта', 'Сервисы', 1000, true); -INSERT INTO orders (id, user_id, order_date, status) -VALUES (1, 1, '2025-01-10 12:00:00', 'paid'), - (2, 3, '2025-01-12 09:30:00', 'cancelled'), - (3, 1, '2025-01-15 18:20:00', 'paid'), - (4, 2, '2025-01-20 11:05:00', 'created'), - (5, 4, '2025-02-01 17:45:00', 'paid'), - (6, 5, '2025-02-10 10:10:00', 'refunded'), - (7, 7, '2025-03-01 09:00:00', 'paid'), - (8, 9, '2025-03-05 19:30:00', 'paid'); +INSERT INTO orders (user_id, order_date, status) +VALUES (1, '2025-01-10 12:00:00', 'paid'), + (3, '2025-01-12 09:30:00', 'cancelled'), + (1, '2025-01-15 18:20:00', 'paid'), + (2, '2025-01-20 11:05:00', 'created'), + (4, '2025-02-01 17:45:00', 'paid'), + (5, '2025-02-10 10:10:00', 'refunded'), + (7, '2025-03-01 09:00:00', 'paid'), + (9, '2025-03-05 19:30:00', 'paid'); INSERT INTO order_items (order_id, product_id, quantity, price_at_purchase) VALUES (1, 1, 1, 78000), From d280456d0919ddc8f64382d0c7b16846ab8cb38b Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 04:21:02 +0400 Subject: [PATCH 088/123] =?UTF-8?q?=D0=BF=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 1. Загрузка тестовых данных.sql | 90 +++++++++ .../Этап 2. Создание витринных таблиц.sql | 186 ++++++++++++++++++ .../Pet-Project/Этап 3. Анализ и запросы.sql | 88 +++++++++ .../Этап 4. Тестирование и валидация.sql | 13 ++ 4 files changed, 377 insertions(+) create mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 1. Загрузка тестовых данных.sql create mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql create mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql create mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 1. Загрузка тестовых данных.sql b/dsolovianov/clickhouse/Pet-Project/Этап 1. Загрузка тестовых данных.sql new file mode 100644 index 0000000..167607d --- /dev/null +++ b/dsolovianov/clickhouse/Pet-Project/Этап 1. Загрузка тестовых данных.sql @@ -0,0 +1,90 @@ +DROP TABLE IF EXISTS dsolovianov.core_fact_lesson_views ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.core_dim_user ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.core_dim_course ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.core_dim_lesson ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.core_dim_date ON CLUSTER clickhouse_cluster SYNC; +DROP TABLE IF EXISTS dsolovianov.core_enrollments ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.core_dim_user +ON CLUSTER clickhouse_cluster +( + user_id Int32, + name String, + age Int32, + email String, + registration_date Date +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_dim_user', + '{replica}' +) +ORDER BY user_id; + +CREATE TABLE dsolovianov.core_dim_course +ON CLUSTER clickhouse_cluster +( + course_id Int32, + title String, + category String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_dim_course', + '{replica}' +) +ORDER BY course_id; + +CREATE TABLE dsolovianov.core_dim_lesson +ON CLUSTER clickhouse_cluster +( + lesson_id Int32, + title String, + duration_min Int32, + course_id Int32 +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_dim_lesson', + '{replica}' +) +ORDER BY lesson_id; + +CREATE TABLE dsolovianov.core_dim_date +ON CLUSTER clickhouse_cluster +( + date Date, + year Int32, + month Int32, + day Int32, + weekday String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_dim_date', + '{replica}' +) +ORDER BY date; + +CREATE TABLE dsolovianov.core_enrollments +ON CLUSTER clickhouse_cluster +( + user_id Int32, + course_id Int32, + enrolled_at DateTime64(3) +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_enrollments', + '{replica}' +) +ORDER BY user_id; + +CREATE TABLE dsolovianov.core_fact_lesson_views +ON CLUSTER clickhouse_cluster +( + user_id Int32, + lesson_id Int32, + course_id Int32, + viewed_at DateTime64(3) +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/core_fact_lesson_views', + '{replica}' +) +ORDER BY user_id; \ No newline at end of file diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql b/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql new file mode 100644 index 0000000..e2dc606 --- /dev/null +++ b/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql @@ -0,0 +1,186 @@ +-- Этап 2. Создание витринных таблиц -- + +-- Витрина lesson_popularity_summary +DROP TABLE IF EXISTS dsolovianov.lesson_popularity_summary ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.lesson_popularity_summary ON CLUSTER clickhouse_cluster ( + lesson_id UInt32, + lesson_title String, + course_id UInt32, + course_title String, + total_views UInt64, + unique_users UInt64, + first_view DateTime, + last_view DateTime +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/lesson_popularity_summary', + '{replica}' +) +ORDER BY (course_id, lesson_id); + +CREATE MATERIALIZED VIEW dsolovianov.lesson_popularity_summary_mv ON CLUSTER clickhouse_cluster +TO dsolovianov.lesson_popularity_summary +AS +SELECT + l.lesson_id AS lesson_id, + l.title AS lesson_title, + l.course_id AS course_id, + c.title AS course_title, + COUNT(v.viewed_at) AS total_views, + COUNT(DISTINCT v.user_id) AS unique_users, + MIN(v.viewed_at) AS first_view, + MAX(v.viewed_at) AS last_view +FROM dsolovianov.core_fact_lesson_views v +LEFT JOIN dsolovianov.core_dim_lesson l ON v.lesson_id = l.lesson_id +LEFT JOIN dsolovianov.core_dim_course c ON l.course_id = c.course_id +GROUP BY l.lesson_id, l.title, l.course_id, c.title; + +INSERT INTO dsolovianov.lesson_popularity_summary +SELECT + l.lesson_id AS lesson_id, + l.title AS lesson_title, + l.course_id AS course_id, + c.title AS course_title, + COUNT(v.viewed_at) AS total_views, + COUNT(DISTINCT v.user_id) AS unique_users, + MIN(v.viewed_at) AS first_view, + MAX(v.viewed_at) AS last_view +FROM dsolovianov.core_dim_lesson l +JOIN dsolovianov.core_dim_course c ON l.course_id = c.course_id +LEFT JOIN dsolovianov.core_fact_lesson_views v ON l.lesson_id = v.lesson_id +GROUP BY l.lesson_id, l.title, l.course_id, c.title; + +-- Витрину inactive_users_summary + +DROP TABLE IF EXISTS dsolovianov.inactive_users_summary ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.inactive_users_summary ON CLUSTER clickhouse_cluster ( + user_id UInt32, + name String, + email String, + age UInt8, + registered_courses_count UInt32, + registration_date Date +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/inactive_users_summary', + '{replica}' +) +ORDER BY user_id; + +CREATE MATERIALIZED VIEW dsolovianov.inactive_users_summary_mv ON CLUSTER clickhouse_cluster +TO dsolovianov.inactive_users_summary +AS +SELECT + u.user_id AS user_id, + u.name AS name, + u.email AS email, + u.age AS age, + COUNT(DISTINCT e.course_id) AS registered_courses_count, + u.registration_date AS registration_date +FROM dsolovianov.core_dim_user u +LEFT JOIN dsolovianov.core_enrollments e ON u.user_id = e.user_id +WHERE u.user_id NOT IN (SELECT DISTINCT user_id FROM dsolovianov.core_fact_lesson_views) +GROUP BY u.user_id, u.name, u.email, u.age, u.registration_date; + +INSERT INTO dsolovianov.inactive_users_summary +SELECT + u.user_id AS user_id, + u.name AS name, + u.email AS email, + u.age AS age, + COUNT(DISTINCT e.course_id) AS registered_courses_count, + u.registration_date AS registration_date +FROM dsolovianov.core_dim_user u +LEFT JOIN dsolovianov.core_enrollments e ON u.user_id = e.user_id +WHERE u.user_id NOT IN (SELECT DISTINCT user_id FROM dsolovianov.core_fact_lesson_views) +GROUP BY u.user_id, u.name, u.email, u.age, u.registration_date; + +-- Витрина course_completion_rate + +DROP TABLE IF EXISTS dsolovianov.course_completion_rate ON CLUSTER clickhouse_cluster SYNC; + +CREATE TABLE dsolovianov.course_completion_rate ON CLUSTER clickhouse_cluster ( + user_id UInt32, + user_name String, + course_id UInt32, + course_title String, + lessons_in_course UInt32, + lessons_viewed UInt32, + completion_rate Float32 +) ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/dsolovianov/course_completion_rate', + '{replica}' +) +ORDER BY (user_id, course_id); + +DROP TABLE IF EXISTS dsolovianov.course_completion_rate_mv ON CLUSTER clickhouse_cluster SYNC; + +CREATE MATERIALIZED VIEW dsolovianov.course_completion_rate_mv ON CLUSTER clickhouse_cluster +TO dsolovianov.course_completion_rate +AS +SELECT + e.user_id AS user_id, + u.name AS user_name, + e.course_id AS course_id, + c.title AS course_title, + ( + SELECT COUNT(*) + FROM dsolovianov.core_dim_lesson l + WHERE l.course_id = e.course_id + ) AS lessons_in_course, + ( + SELECT COUNT(DISTINCT v.lesson_id) + FROM dsolovianov.core_fact_lesson_views v + JOIN dsolovianov.core_dim_lesson l ON v.lesson_id = l.lesson_id + WHERE v.user_id = e.user_id AND l.course_id = e.course_id + ) AS lessons_viewed, + ( + SELECT COUNT(DISTINCT v.lesson_id) + FROM dsolovianov.core_fact_lesson_views v + JOIN dsolovianov.core_dim_lesson l ON v.lesson_id = l.lesson_id + WHERE v.user_id = e.user_id AND l.course_id = e.course_id + ) / ( + SELECT COUNT(*) + FROM dsolovianov.core_dim_lesson l + WHERE l.course_id = e.course_id + ) AS completion_rate +FROM dsolovianov.core_enrollments e +JOIN dsolovianov.core_dim_user u ON e.user_id = u.user_id +JOIN dsolovianov.core_dim_course c ON e.course_id = c.course_id +GROUP BY e.user_id, u.name, e.course_id, c.title; + +INSERT INTO dsolovianov.course_completion_rate +SELECT + e.user_id AS user_id, + u.name AS user_name, + e.course_id AS course_id, + c.title AS course_title, + ( + SELECT COUNT(*) + FROM dsolovianov.core_dim_lesson l + WHERE l.course_id = e.course_id + ) AS lessons_in_course, + COALESCE( + ( + SELECT COUNT(DISTINCT v.lesson_id) + FROM dsolovianov.core_fact_lesson_views v + JOIN dsolovianov.core_dim_lesson l ON v.lesson_id = l.lesson_id + WHERE v.user_id = e.user_id AND l.course_id = e.course_id + ), 0 + ) AS lessons_viewed, + COALESCE( + ( + SELECT COUNT(DISTINCT v.lesson_id) + FROM dsolovianov.core_fact_lesson_views v + JOIN dsolovianov.core_dim_lesson l ON v.lesson_id = l.lesson_id + WHERE v.user_id = e.user_id AND l.course_id = e.course_id + ), 0 + ) / ( + SELECT COUNT(*) + FROM dsolovianov.core_dim_lesson l + WHERE l.course_id = e.course_id + ) AS completion_rate +FROM dsolovianov.core_enrollments e +JOIN dsolovianov.core_dim_user u ON e.user_id = u.user_id +JOIN dsolovianov.core_dim_course c ON e.course_id = c.course_id +GROUP BY e.user_id, u.name, e.course_id, c.title; diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql b/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql new file mode 100644 index 0000000..6550e5b --- /dev/null +++ b/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql @@ -0,0 +1,88 @@ +-- Этап 3. Анализ и запросы - Напиши SQL-запросы к витринам -- Напиши свои вариации для практики-- +-- Топ-5 самых просматриваемых уроков + +SELECT + lesson_title, + course_title, + total_views +FROM lesson_popularity_summary +ORDER BY total_views DESC +LIMIT 5; + +-- Неактивные пользователи, записавшиеся на курсы + +SELECT + name, + email, + registered_courses_count +FROM inactive_users_summary +WHERE registered_courses_count > 0 +ORDER BY registration_date DESC; + +-- Курсы с самым высоким процентом завершения + +SELECT + course_title, + AVG(completion_rate) AS avg_completion +FROM course_completion_rate +GROUP BY course_title +ORDER BY avg_completion DESC +LIMIT 5; + +-- Мои вариации -- + +-- Среднее количество учеников на урок по каждому курсу + +SELECT + course_title, + ROUND(AVG(unique_users), 2) AS avg_users_lesson, + SUM(total_views) AS total_course_views +FROM dsolovianov.lesson_popularity_summary +GROUP BY course_title +ORDER BY avg_users_lesson DESC; + +-- Возрастное распределение неактивных пользователей + +SELECT + CASE + WHEN age < 25 THEN 'до 25' + WHEN age BETWEEN 25 AND 30 THEN '25-30' + WHEN age BETWEEN 31 AND 35 THEN '31-35' + ELSE '35+' + END AS age_group, + COUNT(*) AS inactive_users_count, + AVG(registered_courses_count) AS avg_courses_enrolled +FROM dsolovianov.inactive_users_summary +GROUP BY age_group +ORDER BY age_group; + +-- Пользователи, которые начали, но не закончили курс (completion_rate между 0 и 1) + +SELECT + user_name, + course_title, + lessons_viewed, + lessons_in_course, + ROUND(completion_rate * 100, 0) AS no_completion, + (lessons_in_course - lessons_viewed) AS remaining_lessons +FROM dsolovianov.course_completion_rate +WHERE completion_rate BETWEEN 0.01 AND 0.99 +ORDER BY completion_rate DESC, user_name; + +-- Прогресс каждого пользователя по курсам (с указанием статуса) + +SELECT + user_name, + course_title, + lessons_viewed, + lessons_in_course, + ROUND(completion_rate * 100, 0) AS progress_percent, + CASE + WHEN completion_rate = 1.0 THEN 'Пройден' + WHEN completion_rate = 0 THEN 'Не начат' + WHEN completion_rate >= 0.5 THEN 'В процессе (>50%)' + ELSE 'В процессе (<50%)' + END AS status +FROM dsolovianov.course_completion_rate +ORDER BY user_name, completion_rate DESC; + diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql b/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql new file mode 100644 index 0000000..f053160 --- /dev/null +++ b/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql @@ -0,0 +1,13 @@ +-- Этап 4. Тестирование и валидация -- + +/* +### **Задание 6: Сравни данные с Greenplum** + +1. Выбери 2–3 значения из таблиц `_summary_source` в Greenplum +2. Сравни с ClickHouse-результатами: + - Совпадает ли `SUM`, `AVG`, `MIN`, `MAX` + - Проверяй `NULL`, `0`, округления +3. Напиши мини-отчёт: **"где совпадает, где нет, почему"** +*/ + +-- Ничего не понятно по этому этапу. У меня в GP нет таблиц "_summary_source" From 0f63e2135317f1e3b37f043769c246872b360fdd Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 06:50:16 +0400 Subject: [PATCH 089/123] DDL, DML, Basic, Filter --- dsolovianov/postgresql/DDL.sql | 32 +++++++ dsolovianov/postgresql/DML.sql | 59 ++++++++++++ dsolovianov/postgresql/Базовый SQL.sql | 89 +++++++++++++++++ dsolovianov/postgresql/Фильтрация.sql | 126 +++++++++++++++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 dsolovianov/postgresql/DDL.sql create mode 100644 dsolovianov/postgresql/DML.sql create mode 100644 dsolovianov/postgresql/Базовый SQL.sql create mode 100644 dsolovianov/postgresql/Фильтрация.sql diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql new file mode 100644 index 0000000..18d5050 --- /dev/null +++ b/dsolovianov/postgresql/DDL.sql @@ -0,0 +1,32 @@ +CREATE TABLE dsolovianov.users ( + id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + name VARCHAR(100) NOT NULL, + age INT, + email VARCHAR(100), + country VARCHAR(100), + is_active BOOLEAN, + created_at TIMESTAMPTZ NOT NULL +); + +CREATE TABLE dsolovianov.products ( + id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + name VARCHAR(100) NOT NULL, + category VARCHAR(50), + price DECIMAL(10,2) NOT NULL, + in_stock BOOLEAN + ); + +CREATE TABLE dsolovianov.orders ( + id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + user_id INT, + order_date TIMESTAMPTZ NOT NULL, + status VARCHAR(20) +); + +CREATE TABLE dsolovianov.order_items ( + order_id INT NOT NULL, + product_id INT NOT NULL, + quantity INT NOT NULL, + price_at_purchase DECIMAL(10,2) NOT NULL, + CONSTRAINT order_items_pk PRIMARY KEY (order_id, product_id) +); \ No newline at end of file diff --git a/dsolovianov/postgresql/DML.sql b/dsolovianov/postgresql/DML.sql new file mode 100644 index 0000000..ef18ac6 --- /dev/null +++ b/dsolovianov/postgresql/DML.sql @@ -0,0 +1,59 @@ +INSERT INTO dsolovianov.users (name, age, email, country, is_active, created_at) VALUES +('Alice', 25, 'alice@example.com', 'US', true, '2024-01-10 10:00:00+00'), +('Bob', 30, 'bob@example.com', 'UK', true, '2024-02-05 09:30:00+00'), +('Carol', 27, 'carol@example.com', 'US', false, '2024-03-01 15:20:00+00'), +('Diana', 22, 'diana@GMAIL.com', 'US', true, '2024-01-12 08:15:00+00'), +('Evan', 35, 'EVAN@example.com', 'UK', true, '2024-01-18 19:40:00+00'), +('Farida', 29, 'farida@example.com', 'KZ', true, '2024-02-20 11:05:00+00'), +('George', 31, 'george+test@example.com', 'US', true, '2024-03-05 16:45:00+00'), +('Helen', 24, NULL, 'UK', false, '2024-03-06 08:20:00+00'), +('Ivan', 26, 'ivan@example.com', 'RU', true, '2024-01-11 14:30:00+00'), +('Julia', 28, 'julia@example.com', 'US', true, '2024-03-07 18:05:00+00'); + +INSERT INTO dsolovianov.products (name, category, price, in_stock) VALUES +('iPhone 15', 'Электроника', 80000, true), +('Kindle', 'Электроника', 15000, true), +('Кроссовки Nike', 'Одежда', 9000, true), +('Кроссовки NoName', 'Одежда', 3000, false), +('Наушники Pro', 'Электроника', 12000, true), +('Фитнес-браслет', 'Гаджеты', 6000, true), +('Подарочная карта', 'Сервисы', 1000, true); + +INSERT INTO dsolovianov.orders (user_id, order_date, status) VALUES +(1, '2025-01-10 12:00:00+00', 'paid'), +(3, '2025-01-12 09:30:00+00', 'cancelled'), +(1, '2025-01-15 18:20:00+00', 'paid'), +(2, '2025-01-20 11:05:00+00', 'created'), +(4, '2025-02-01 17:45:00+00', 'paid'), +(5, '2025-02-10 10:10:00+00', 'refunded'), +(7, '2025-03-01 09:00:00+00', 'paid'), +(9, '2025-03-05 19:30:00+00', 'paid'); + +INSERT INTO dsolovianov.order_items (order_id, product_id, quantity, price_at_purchase) VALUES +-- Заказ #1: iPhone 15 (1 шт) + Наушники Pro (2 шт) +(1, 1, 1, 80000.00), +(1, 5, 2, 12000.00), + +-- Заказ #2 (cancelled): Kindle (1 шт) +(2, 2, 1, 15000.00), + +-- Заказ #3: Кроссовки Nike (2 шт) +(3, 3, 2, 9000.00), + +-- Заказ #4 (created): Фитнес-браслет (1 шт) +(4, 6, 1, 6000.00), + +-- Заказ #5: iPhone 15 (1 шт) + Подарочная карта (3 шт) +(5, 1, 1, 80000.00), +(5, 7, 3, 1000.00), + +-- Заказ #6 (refunded): Наушники Pro (1 шт) +(6, 5, 1, 12000.00), + +-- Заказ #7: Kindle (1 шт) + Фитнес-браслет (2 шт) +(7, 2, 1, 15000.00), +(7, 6, 2, 6000.00), + +-- Заказ #8: iPhone 15 (1 шт) + Кроссовки Nike (1 шт) +(8, 1, 1, 80000.00), +(8, 3, 1, 9000.00); \ No newline at end of file diff --git a/dsolovianov/postgresql/Базовый SQL.sql b/dsolovianov/postgresql/Базовый SQL.sql new file mode 100644 index 0000000..7cf2ef7 --- /dev/null +++ b/dsolovianov/postgresql/Базовый SQL.sql @@ -0,0 +1,89 @@ +/* +- Задание 1 — Спроектируй «карточку пользователя» + + Ты делаешь экран “Пользователи” в админке и решаешь, какие поля там важны. + + Работаем с таблицей **`users`** + + **✅ Требования:** + + 1. Напиши запрос, который вернёт в результате только столбцы: **`id`**, **`name`**, **`country`**, **`created_at`**, **`email`** + 2. Потом измени запрос так, чтобы: + - сначала отсортировать по **`created_at`** (новые сверху) + - потом по **`name`** по алфавиту внутри одной даты + +- Задание 2 — Сегментация по странам (с выбором) + + Маркетолог хочет понять, какая страна сейчас интереснее. + + Работаем с таблицей **`users`** + + **✅ Требования:** + + 1. Выбери страну **`US`**и напиши запрос, который: + - вернёт **`id`**, **`name`**, **`country`**; + - покажет только пользователей из выбранной страны; + - отсортирует их по имени. + 2. Напиши второй запрос, который вернёт пользователей из страны **`UK`**. + +- Задание 3 — Контакты для рассылки (думать про качество) + + Нужно сделать список e‑mail для рассылки так, чтобы его не стыдно было отдавать маркетологу. + + **✅ Требования:** + + 1. Напиши один запрос, который: + - вернёт только пользователей, у которых email **есть**; + - вернёт столбцы **`id`**, **`name`**, **`email`**; + - отсортирует их по имени. + 2. Напиши второй запрос, который подготовит «чистый» вид email: + - вернёт только **`email`** в нижнем регистре (как **`email_lower`**); + - уберёт дубликаты email (нужно использовать **`DISTINCT`**). + - Задание 4 — Новички и активность + + «Тебе нужно посмотреть, кто пришёл недавно и кого стоит “подогреть”.» + + **✅ Требования:** + + 1. Напиши запрос, который: + - выберет пользователей, зарегистрированных **после** даты, которую ты сам выберешь (например, **`2024‑01‑11`**); + - вернёт **`id`**, **`name`**, **`created_at`**; + - отсортирует по **`created_at`** от новых к старым. + 2. Ограничь результат так, чтобы увидеть только 3 самых новых пользователя. + 3. Измени запрос так, чтобы пропустить самого нового и показать следующих двух (используй **`LIMIT`** и **`OFFSET`**). +*/ +SELECT id, + name, + country, + created_at, + email +FROM dsolovianov.users +ORDER BY created_at DESC, name ASC; + +SELECT id, + name, + country +FROM dsolovianov.users +WHERE country LIKE 'US'; + +SELECT id, + name, + country +FROM dsolovianov.users +WHERE country LIKE 'UK'; + +SELECT id, + name, + email, + LOWER(email) AS email_lower +FROM dsolovianov.users +WHERE email IS NOT NULL +ORDER BY name; + +SELECT id, + name, + created_at +FROM dsolovianov.users +WHERE created_at > '2024-01-11' +ORDER BY created_at DESC +LIMIT 3 OFFSET 1; diff --git a/dsolovianov/postgresql/Фильтрация.sql b/dsolovianov/postgresql/Фильтрация.sql new file mode 100644 index 0000000..d928d61 --- /dev/null +++ b/dsolovianov/postgresql/Фильтрация.sql @@ -0,0 +1,126 @@ +/*INSERT INTO dsolovianov.products (name, category, price, in_stock) VALUES + ('Mi Band 7', 'smartwatch', 49.00, true), + ('Surface Pro', 'tablet', 1299.00, true), + ('iPhone 15', 'smartphone', 999.00, true), + ('Logitech MX Master', 'mouse', 120.00, true);*/ + +/* +- Задача 1 — Товары для продажи + + Ты настраиваешь главную витрину магазина. Нужно показывать только товары, которые сейчас реально готовы продавать. + + **✅ Условия:** + + - товар **в наличии** + - категория **НЕ smartwatch** + - цена **от 100 до 1300** + - отдельно хотим видеть цену: + - с наценкой 30% ( price + 30%) + - с «виртуальным НДС» 50% (price + 50%) + + **✅ Вывести:** + + - `name`, `category`, `price` + - `price_with_markup` = price + 30% + - `price_with_vat` = price + 50% + +- Задача 2 — Скидочная корзина: дешёвые или дорогие + + Нужно собрать товары под акцию + + **✅ Условия:** + + - участвуют товары с ценой **меньше 100** или **больше 1000** + - скидка 15% (price - 15%) + + **✅ Вывести:** + + - `name`, `price` + - `price_with_discount` = price - 15% + +- Задача 3 — Категории для рекламы + + Маркетолог просит товары только из нужных категорий: + + **✅ Условия:** + + - category в списке: `tablet`, `smartphone`, `mouse` + - цена **не меньше 100** + + **✅ Вывести:** + + - `id`, `name`, `category`, `price` + - сортировка по цене по убыванию +- Задача 4 — Убрать iPhone из выборки + + Собираем список товаров, **кроме iPhone**, чтобы сделать подборку “не Apple”. + + **✅ Условия:** + + - название **НЕ содержит** `iPhone` + + **✅ Вывести:** + + - `name`, `category`, `price` +- Задача 5 — Поиск товаров по названию + + Представь, что у пользователя есть строка поиска на сайте. Нужно сделать **2 запроса**, которые помогут искать товары по названию. + + - 1) Найти товары, где в названии есть `"pro"` (без учёта регистра) + + 📌 Условия: + + - поиск должен сработать на `pro`, `PRO`, `Pro` + - вывести: `name`, `category`, `price` + - 2) Найти товары, где в названии есть `"mx"` (без учёта регистра) + + 📌 Условия: + + - вывести: `name`, `category`, `price` + - отсортировать по `price DESC` +*/ + + +SELECT name, + category, + price, + (price * 0.30) + price AS price_with_markup, + (price * 0.50) + price AS price_with_vat +FROM dsolovianov.products +WHERE category NOT IN ('smartwatch') + AND (price >= 100 AND price <= 1300); + +SELECT name, + price, + price - (price * 0.15) AS price_with_discount +FROM dsolovianov.products +WHERE price < 100 OR price > 1000; + +SELECT id, + name, + category, + price +FROM dsolovianov.products +WHERE category IN ('tablet', 'smartphone', 'mouse') AND price >= 100 +ORDER BY price DESC; + +SELECT name, + category, + price +FROM dsolovianov.products +WHERE category NOT LIKE '%iPhone%'; + +SELECT id, + name, + category, + price +FROM dsolovianov.products +WHERE name ILIKE '%pro%'; + +SELECT id, + name, + category, + price +FROM dsolovianov.products +WHERE name ILIKE '%mx%'; + From cbdb54a36b623f204776329bf21be4a286d0f862 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 07:02:07 +0400 Subject: [PATCH 090/123] fix postgres --- dsolovianov/postgresql/DDL.sql | 14 +++++++++----- dsolovianov/postgresql/Базовый SQL.sql | 6 ++++-- dsolovianov/postgresql/Фильтрация.sql | 6 ++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql index 18d5050..5d2cdd7 100644 --- a/dsolovianov/postgresql/DDL.sql +++ b/dsolovianov/postgresql/DDL.sql @@ -1,5 +1,5 @@ CREATE TABLE dsolovianov.users ( - id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, age INT, email VARCHAR(100), @@ -9,7 +9,7 @@ CREATE TABLE dsolovianov.users ( ); CREATE TABLE dsolovianov.products ( - id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10,2) NOT NULL, @@ -17,7 +17,7 @@ CREATE TABLE dsolovianov.products ( ); CREATE TABLE dsolovianov.orders ( - id INT GENERATED ALWAYS AS IDENTITY NOT NULL, + id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id INT, order_date TIMESTAMPTZ NOT NULL, status VARCHAR(20) @@ -26,7 +26,11 @@ CREATE TABLE dsolovianov.orders ( CREATE TABLE dsolovianov.order_items ( order_id INT NOT NULL, product_id INT NOT NULL, - quantity INT NOT NULL, - price_at_purchase DECIMAL(10,2) NOT NULL, + quantity INT NOT NULL CHECK (quantity > 0), + price_at_purchase DECIMAL(10,2) NOT NULL CHECK (price_at_purchase > 0), CONSTRAINT order_items_pk PRIMARY KEY (order_id, product_id) + CONSTRAINT order_items_order_fk + FOREIGN KEY (order_id) REFERENCES dsolovianov.orders(id), + CONSTRAINT order_items_product_fk + FOREIGN KEY (product_id) REFERENCES dsolovianov.products(id) ); \ No newline at end of file diff --git a/dsolovianov/postgresql/Базовый SQL.sql b/dsolovianov/postgresql/Базовый SQL.sql index 7cf2ef7..930f258 100644 --- a/dsolovianov/postgresql/Базовый SQL.sql +++ b/dsolovianov/postgresql/Базовый SQL.sql @@ -64,13 +64,15 @@ SELECT id, name, country FROM dsolovianov.users -WHERE country LIKE 'US'; +WHERE country LIKE 'US' +ORDER BY name; SELECT id, name, country FROM dsolovianov.users -WHERE country LIKE 'UK'; +WHERE country LIKE 'UK' +ORDER BY name; SELECT id, name, diff --git a/dsolovianov/postgresql/Фильтрация.sql b/dsolovianov/postgresql/Фильтрация.sql index d928d61..edf113a 100644 --- a/dsolovianov/postgresql/Фильтрация.sql +++ b/dsolovianov/postgresql/Фильтрация.sql @@ -110,15 +110,13 @@ SELECT name, FROM dsolovianov.products WHERE category NOT LIKE '%iPhone%'; -SELECT id, - name, +SELECT name, category, price FROM dsolovianov.products WHERE name ILIKE '%pro%'; -SELECT id, - name, +SELECT name, category, price FROM dsolovianov.products From 327bed019dac2a46eaa3b604170c939d742b079b Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 07:04:28 +0400 Subject: [PATCH 091/123] fix ddl --- dsolovianov/postgresql/DDL.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql index 5d2cdd7..033894c 100644 --- a/dsolovianov/postgresql/DDL.sql +++ b/dsolovianov/postgresql/DDL.sql @@ -32,5 +32,7 @@ CREATE TABLE dsolovianov.order_items ( CONSTRAINT order_items_order_fk FOREIGN KEY (order_id) REFERENCES dsolovianov.orders(id), CONSTRAINT order_items_product_fk - FOREIGN KEY (product_id) REFERENCES dsolovianov.products(id) + FOREIGN KEY (product_id) REFERENCES dsolovianov.products(id), + CONSTRAINT orders_user_fk + FOREIGN KEY (user_id) REFERENCES dsolovianov.users(id) ); \ No newline at end of file From 6fe1e7ceb525e7520d7796e66c0ff9fc91d2d463 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 07:05:38 +0400 Subject: [PATCH 092/123] fix ddl v2 --- dsolovianov/postgresql/DDL.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql index 033894c..c3a8393 100644 --- a/dsolovianov/postgresql/DDL.sql +++ b/dsolovianov/postgresql/DDL.sql @@ -21,6 +21,8 @@ CREATE TABLE dsolovianov.orders ( user_id INT, order_date TIMESTAMPTZ NOT NULL, status VARCHAR(20) + CONSTRAINT orders_user_fk + FOREIGN KEY (user_id) REFERENCES dsolovianov.users(id) ); CREATE TABLE dsolovianov.order_items ( @@ -32,7 +34,5 @@ CREATE TABLE dsolovianov.order_items ( CONSTRAINT order_items_order_fk FOREIGN KEY (order_id) REFERENCES dsolovianov.orders(id), CONSTRAINT order_items_product_fk - FOREIGN KEY (product_id) REFERENCES dsolovianov.products(id), - CONSTRAINT orders_user_fk - FOREIGN KEY (user_id) REFERENCES dsolovianov.users(id) + FOREIGN KEY (product_id) REFERENCES dsolovianov.products(id) ); \ No newline at end of file From 52481d3186855477e48c74aa2c2339626c13cedf Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 07:06:15 +0400 Subject: [PATCH 093/123] fix ddl v3 --- dsolovianov/postgresql/DDL.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql index c3a8393..a578b7b 100644 --- a/dsolovianov/postgresql/DDL.sql +++ b/dsolovianov/postgresql/DDL.sql @@ -20,7 +20,7 @@ CREATE TABLE dsolovianov.orders ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id INT, order_date TIMESTAMPTZ NOT NULL, - status VARCHAR(20) + status VARCHAR(20), CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES dsolovianov.users(id) ); From bfc54f9dcfa39cdcd433dbf06da22512f51a3a1c Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 11 May 2026 07:07:01 +0400 Subject: [PATCH 094/123] ddl v4 --- dsolovianov/postgresql/DDL.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsolovianov/postgresql/DDL.sql b/dsolovianov/postgresql/DDL.sql index a578b7b..0c7c3b1 100644 --- a/dsolovianov/postgresql/DDL.sql +++ b/dsolovianov/postgresql/DDL.sql @@ -30,7 +30,7 @@ CREATE TABLE dsolovianov.order_items ( product_id INT NOT NULL, quantity INT NOT NULL CHECK (quantity > 0), price_at_purchase DECIMAL(10,2) NOT NULL CHECK (price_at_purchase > 0), - CONSTRAINT order_items_pk PRIMARY KEY (order_id, product_id) + CONSTRAINT order_items_pk PRIMARY KEY (order_id, product_id), CONSTRAINT order_items_order_fk FOREIGN KEY (order_id) REFERENCES dsolovianov.orders(id), CONSTRAINT order_items_product_fk From 3d85f4b9de45e5d8ac35d1e7353793411064838a Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 12 May 2026 20:31:23 +0300 Subject: [PATCH 095/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A2=D0=B8=D0=BF=D1=8B=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=BB=D0=B8=D1=86'=20-=20Clickhouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Семейство MergeTree/practice_table_types.sql | 120 ++++++++++++++++++ .../Step_4_preparing_display_cases_at_greenplum.sql | 2 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql diff --git a/mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql b/mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql new file mode 100644 index 0000000..eff4e47 --- /dev/null +++ b/mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql @@ -0,0 +1,120 @@ +-- Задание 1 + +CREATE TABLE mnoskov.page_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + url String, + view_time DateTime +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(view_time) +ORDER BY (user_id, view_time); + + +-- Задание 2 +CREATE TABLE mnoskov.users +ON CLUSTER clickhouse_cluster +( + id UInt64, + name String, + update_at DateTime +) +ENGINE = ReplacingMergeTree(update_at) +ORDER BY id; + + +-- Задание 3 +CREATE TABLE mnoskov.region_sales +ON CLUSTER clickhouse_cluster +( + region String, + date Date, + sales UInt64 +) +ENGINE = SummingMergeTree +PARTITION BY toYYYYMM(date) +ORDER BY (region, date) + +INSERT INTO mnoskov.region_sales (region, date, sales) +VALUES + ('Moscow', '2026-05-01', 100), + ('Moscow', '2026-05-01', 150), + ('Moscow', '2026-05-01', 200), + ('Berlin', '2026-05-02', 300), + ('Berlin', '2026-05-02', 120), + ('Berlin', '2026-05-02', 80), + ('Paris', '2026-05-03', 500), + ('Paris', '2026-05-03', 250), + ('London', '2026-05-04', 400), + ('Tokyo', '2026-05-05', 700); + +SELECT * FROM mnoskov.region_sales; +-- Все четко работает. + + +-- Задание 4 +CREATE TABLE mnoskov.user_metrics +ON CLUSTER clickhouse_cluster +( + region String, + avg_age_state AggregateFunction(avg, UInt8), + active_users_state AggregateFunction(uniq, UInt64) +) +ENGINE = AggregatingMergeTree +ORDER BY region; + +CREATE TABLE mnoskov.user_metrics_source +ON CLUSTER clickhouse_cluster +( + region String, + age UInt8, + user_id UInt64 +) +ENGINE = MergeTree +ORDER BY region; + +INSERT INTO mnoskov.user_metrics_source +(region, age, user_id) +VALUES + ('Moscow', 25, 1001), + ('Moscow', 30, 1002), + ('Moscow', 22, 1003), + ('Moscow', 25, 1001), + ('Berlin', 40, 2001), + ('Berlin', 35, 2002), + ('Berlin', 29, 2003), + ('Berlin', 35, 2002), + ('Paris', 31, 3001), + ('Paris', 28, 3002), + ('Paris', 45, 3003), + ('Paris', 31, 3001), + ('London', 50, 4001), + ('London', 41, 4002), + ('London', 38, 4003), + ('London', 41, 4002), + ('Tokyo', 27, 5001), + ('Tokyo', 33, 5002), + ('Tokyo', 29, 5003), + ('Tokyo', 33, 5002); +SELECT * FROM mnoskov.user_metrics_source; + +INSERT INTO mnoskov.user_metrics +SELECT + region, + avgState(age) AS avg_age_state, + uniqState(user_id) AS active_users_state +FROM + mnoskov.user_metrics_source +GROUP BY + region; + +SELECT + region, + avgMerge(avg_age_state), + uniqMerge(active_users_state) +FROM + mnoskov.user_metrics +GROUP BY + region; +-- Че то кликхаус странно себя ведет, то таблицы сами очищаются, то удаленные таблицы воскресают. \ No newline at end of file diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql index be37917..9c4dbb2 100644 --- a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql +++ b/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql @@ -53,7 +53,7 @@ LEFT JOIN ( ) e ON du.user_id = e.user_id WHERE NOT EXISTS (SELECT 1 FROM core.fact_lesson_views flv WHERE flv.user_id = du.user_id); --- интересно, что при проверки плана запроса, когда кусок с enrollments_count был в CTE, выдавал какую-то хуйню, а когда я засунул его в подзапрос, сразу инлексы заработали, и строки начали эффективней читаться +-- интересно, что во время проверки плана запроса, когда кусок с enrollments_count был в CTE, план запроса выдавал какую-то хуйню, а когда я засунул его в подзапрос, сразу индексы заработали, и строки начали эффективней читаться -- 9 From 318c50b56307b40a2fe7bcc12eea430c0997d041 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 13 May 2026 18:28:46 +0300 Subject: [PATCH 096/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'View=20=D0=B8=20Distribute=20=D0=B4=D0=B2=D0=B8?= =?UTF-8?q?=D0=B6=D0=BA=D0=B8'=20-=20Clickhouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice_view_and_distribute_engines.sql | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql diff --git a/mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql b/mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql new file mode 100644 index 0000000..0c3d9ee --- /dev/null +++ b/mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql @@ -0,0 +1,87 @@ +-- Задание 1 + +CREATE TABLE mnoskov.logs +ON CLUSTER clickhouse_cluster ( + dt DateTime, + user_id UInt32, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/logs', + '{replica}' +) +ORDER BY dt; + +CREATE TABLE mnoskov.dist_logs +ON CLUSTER clickhouse_cluster +ENGINE = Distributed( + 'clickhouse_cluster', + 'mnoskov', + 'logs', + rand() +); +-- параметр rand() - это ключ распределния как я понял. Если стоит он, то распределяются данные случайно, если укащать колонку, то по колонке. + + +-- Задание 2 + +CREATE TABLE mnoskov.events +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + event_type String, + ts DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events', + '{replica}' +) +ORDER BY (ts, user_id); + +CREATE VIEW top_active_users AS +SELECT + user_id, + count(*) AS cnt_user +FROM + mnoskov.events +GROUP BY + user_id +ORDER BY + cnt_user DESC, user_id +LIMIT 100; +-- VIEW не подойдет, тк вьюшка будет каждый раз считаться заново, а это энергозатратно, поэтому надо использовать MATERIALIZED VIEW. + + +-- Задание 3 + +-- тут вообще жопа какая-то. Если смотреть на пример создания MV, то выдает ошибку мол engine указывать нельзя. Полез в документацию, там сказано что после указания имени mv нужно добавлять TO ... но все равно выдется ошибка. +-- этот способ я в хабре нашел +CREATE TABLE raw_events +( + event_time DateTime, + user_id UInt32, + event_type String +) +ENGINE = MergeTree +ORDER BY (event_time, user_id); + +CREATE TABLE agr_clicks_per_day +ON CLUSTER clickhouse_cluster +( + event_date Date, + count UInt64 +) +ENGINE = MergeTree +ORDER BY event_date; + +CREATE MATERIALIZED VIEW mv_clicks_per_day TO agr_clicks_per_day AS +SELECT + toDate(event_time) AS event_date, + count(*) AS count +FROM + mnoskov.raw_events +GROUP BY + event_date; + +SELECT * FROM agr_clicks_per_day; +-- ничего у меня получилось, и с SummingMergeTree ни с MergeTree. Как очень непонятно материализованные таблицы работают. Если делать как в теории в инфре, то тоже ничего не работает. \ No newline at end of file From 80023d6054076329ef5f3562a86a0ec7dfea62a3 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Wed, 13 May 2026 20:14:51 +0300 Subject: [PATCH 097/123] completed my homework - sql base --- vberetar/postgresql/sql_query.sql | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 vberetar/postgresql/sql_query.sql diff --git a/vberetar/postgresql/sql_query.sql b/vberetar/postgresql/sql_query.sql new file mode 100644 index 0000000..a0681ba --- /dev/null +++ b/vberetar/postgresql/sql_query.sql @@ -0,0 +1,73 @@ +-- Задание 1 +SELECT id, name, country, created_at, email +FROM users +ORDER BY created_at DESC, name; + +-- Почему выбрал именно эти столбцы и зачем такая сортировка? +-- Ответ на вопрос: +-- Эти столбцы нужны для анализа пользователей, для тех же аналитиков. +-- Сортировка по дате и имени, я так предпологаю, нужня для удобства. + + +-- Задание 2 +-- Запрос 1 +SELECT id, name, country +FROM users +WHERE country = 'US' +ORDER BY name; + +-- Запрос 2 +SELECT id, name, country +FROM users +WHERE country = 'UK' +ORDER BY name; + +-- Какой из двух запросов маркетологу полезнее и почему? +-- Ответ на вопрос: +-- Я так понимаю маркетологу нужно понять какое кол-во пользователей в каждой из стран зарегестрировалось в приложении. +-- Возможно для понимания продвижения продукта и насколько успешна маркетинговая стратегия. И в дальнейшем её корректировка. + + +-- Задание 3 +-- Запрос 1 +SELECT id, name, email +FROM users +WHERE email IS NOT NULL -- я знаю что email и так должен быть NOT NULL по DDL, но на всякий прописал +ORDER BY name; + +-- Запрос 2 +SELECT DISTINCT LOWER(email) as email_lower +FROM users +WHERE email IS NOT NULL; + +-- Зачем вообще приводить email к одному регистру и убирать дубликаты на стороне базы? +-- Ответ на вопрос: +-- Приводить email к нижнему регистру необходимо для того чтобы небыло дубликатов почты. +-- После гугления я узнал что в почтовых адресах могут допускаться верхние регистры букв, только потом создать email с теми же буквами но в нижнем регистре нельзя будет, они будут считаться индентичными. +-- Получается несколько пользователей могут залогинится под одним и тем же email адресом но с разными регистрами. А такое нельзя допускать. +-- Для рассылки дубликаты нужно убирать что-бы не приходила рассылка дважды в один и тот же email. + +-- Задание 4 +-- Запрос 1 +SELECT id, name, created_at +FROM users +WHERE created_at > '2024-01-14' +ORDER BY created_at DESC; + +-- Запрос 2 +SELECT id, name, created_at +FROM users +WHERE created_at > '2024-01-14' +ORDER BY created_at DESC +LIMIT 3; + +-- Запрос 3 +SELECT id, name, created_at +FROM users +WHERE created_at > '2024-01-14' +ORDER BY created_at DESC +OFFSET 1 LIMIT 2; + +-- В каком реальном сценарии тебе пригодится OFFSET? +-- Ответ на вопрос: +-- Не знаю, возможно понадобится посмотреть несколько записей из середины списка. From fb29b369f6c3203b21d57f11050835d3bcfc74c4 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 14 May 2026 13:13:47 +0300 Subject: [PATCH 098/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A2=D0=B8=D0=BF=D1=8B=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85'=20-=20ClickHouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1. Семейство MergeTree}/practice_table_types.sql | 2 +- .../practice_view_and_distribute_engines.sql | 10 +- .../2. Типы данных/practice_data_types.sql | 111 ++++++++++++++++++ 3 files changed, 117 insertions(+), 6 deletions(-) rename mnoskov/clickhouse/практика clickhouse/{Семейство MergeTree => 1. Движки/1. Семейство MergeTree}/practice_table_types.sql (99%) rename mnoskov/clickhouse/практика clickhouse/{ => 1. Движки}/practice_view_and_distribute_engines.sql (90%) create mode 100644 mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql diff --git a/mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql b/mnoskov/clickhouse/практика clickhouse/1. Движки/1. Семейство MergeTree/practice_table_types.sql similarity index 99% rename from mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql rename to mnoskov/clickhouse/практика clickhouse/1. Движки/1. Семейство MergeTree/practice_table_types.sql index eff4e47..6140c2f 100644 --- a/mnoskov/clickhouse/практика clickhouse/Семейство MergeTree/practice_table_types.sql +++ b/mnoskov/clickhouse/практика clickhouse/1. Движки/1. Семейство MergeTree/practice_table_types.sql @@ -34,7 +34,7 @@ ON CLUSTER clickhouse_cluster ) ENGINE = SummingMergeTree PARTITION BY toYYYYMM(date) -ORDER BY (region, date) +ORDER BY (region, date); INSERT INTO mnoskov.region_sales (region, date, sales) VALUES diff --git a/mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql b/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql similarity index 90% rename from mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql rename to mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql index 0c3d9ee..67eacc4 100644 --- a/mnoskov/clickhouse/практика clickhouse/practice_view_and_distribute_engines.sql +++ b/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql @@ -38,7 +38,7 @@ ENGINE = ReplicatedMergeTree( ) ORDER BY (ts, user_id); -CREATE VIEW top_active_users AS +CREATE VIEW mnoskov.top_active_users AS SELECT user_id, count(*) AS cnt_user @@ -56,7 +56,7 @@ LIMIT 100; -- тут вообще жопа какая-то. Если смотреть на пример создания MV, то выдает ошибку мол engine указывать нельзя. Полез в документацию, там сказано что после указания имени mv нужно добавлять TO ... но все равно выдется ошибка. -- этот способ я в хабре нашел -CREATE TABLE raw_events +CREATE TABLE mnoskov.raw_events ( event_time DateTime, user_id UInt32, @@ -65,7 +65,7 @@ CREATE TABLE raw_events ENGINE = MergeTree ORDER BY (event_time, user_id); -CREATE TABLE agr_clicks_per_day +CREATE TABLE mnoskov.agr_clicks_per_day ON CLUSTER clickhouse_cluster ( event_date Date, @@ -74,7 +74,7 @@ ON CLUSTER clickhouse_cluster ENGINE = MergeTree ORDER BY event_date; -CREATE MATERIALIZED VIEW mv_clicks_per_day TO agr_clicks_per_day AS +CREATE MATERIALIZED VIEW mnoskov.mv_clicks_per_day TO mnosokv.agr_clicks_per_day AS SELECT toDate(event_time) AS event_date, count(*) AS count @@ -83,5 +83,5 @@ FROM GROUP BY event_date; -SELECT * FROM agr_clicks_per_day; +SELECT * FROM mnoskov.agr_clicks_per_day; -- ничего у меня получилось, и с SummingMergeTree ни с MergeTree. Как очень непонятно материализованные таблицы работают. Если делать как в теории в инфре, то тоже ничего не работает. \ No newline at end of file diff --git a/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql b/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql new file mode 100644 index 0000000..a92fb71 --- /dev/null +++ b/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql @@ -0,0 +1,111 @@ +-- Задание 1 + +CREATE TABLE mnoskov.raw_data1 +ON CLUSTER clickhouse_cluster ( + user_id String, + balance String, + created_at String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/raw_data1', + '{replica}' +) +ORDER BY tuple(); + +SELECT + user_id, + toDecimal64OrNull(balance,2) AS balance, + toDateTime64OrNull(created_at, 3) AS created_at +FROM + mnoskov.raw_data1 +WHERE + balance IS NOT NULL AND created_at IS NOT NULL; + + +-- Задание 2 + +CREATE TABLE events +ON CLUSTER clickhouse_cluster ( + event_id String, + event_date String, -- форматы: "2023-01-01", "01.02.2023", "invalid" + event_time String -- форматы: "12:30:45", "25:99:99" +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events', + '{replica}' +) +ORDER BY + event_id; + +SELECT * FROM mnoskov.events; + + +SELECT + event_id, + toDate(parseDateTimeBestEffortOrNull(event_date)) AS event_dates, + CASE + WHEN toDayOfMonth(parseDateTimeBestEffortOrNull(event_time)) = 1 THEN toHour(parseDateTimeBestEffortOrNull(event_time)) + ELSE (toDayOfMonth(parseDateTimeBestEffortOrNull(event_time)) - 1) * 24 + toHour(parseDateTimeBestEffortOrNull(event_time)) + END AS event_times, + toDateTime(parseDateTimeBestEffortOrNull(event_date)) + toIntervalHour(event_times) AS event_datetime +FROM + mnoskov.events +ORDER BY + event_dates; +-- Все преобразования с to... и parse... нашел тут https://clickhouse.com/docs/ru/sql-reference/functions/type-conversion-functions#toIntervalMonth. +-- explain analyze не работает, потэому не могу посмотреть насколько это эффективно, НО главное сам запрос работает. + + +-- Задание 3 + +CREATE TABLE mnoskov.user_activity +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + actions String -- JSON: ["login", "purchase", "logout"] +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/user_activity', + '{replica}' +) +ORDER BY + user_id; + +SELECT + user_id, + count(*) +FROM + mnoskov.user_activity +WHERE + has(JSONExtract(actions, 'Array(String)'), 'purchase') +GROUP BY + user_id +ORDER BY + user_id; + + +-- Задание 4 + +CREATE TABLE mnoskov.sensor_data +ON CLUSTER clickhouse_cluster +( + sensor_id UInt32, + value Float64, + status Enum('ok' = 1, 'error' = 2) +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/sensor_data', + '{replica}' +) +ORDER BY sensor_id; + +--1-2. +SELECT + toString(status) AS str_status, + avg(value) AS avg_value +FROM + mnoskov.sensor_data +GROUP BY + str_status; +--3 +-- не понял задание. все строки в value можно преобразовать в Decimal(10,2), потому что value - Float64, а он все null переделывает в 0. \ No newline at end of file From 4240f34f24d1f6a642452a0ff885704fe527c92c Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Sat, 16 May 2026 20:16:10 +0300 Subject: [PATCH 099/123] add home work on 'sql filters' --- vberetar/postgresql/sql_filters.sql | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 vberetar/postgresql/sql_filters.sql diff --git a/vberetar/postgresql/sql_filters.sql b/vberetar/postgresql/sql_filters.sql new file mode 100644 index 0000000..423f131 --- /dev/null +++ b/vberetar/postgresql/sql_filters.sql @@ -0,0 +1,56 @@ +-- Задача 1 +SELECT name, category, price, + price * 1.3 AS price_with_markup, + price * 1.5 AS price_with_vat +FROM products +WHERE (price BETWEEN 100 AND 1300) + AND category NOT IN ('smartwatch') + AND in_stock = 'true'; + +-- Вопрос: +-- Что лучше читается BETWEEN 100 AND 1300 или price >= 100 AND price <= 1300 и почему? +-- Ответ: +-- Для меня BETWEEN 100 AND 1300 читается лучше, сразу понятен срез от 100 и 1300. + + +-- Задача 2 +SELECT name, price, + price * 0.85 AS price_with_discount +FROM products +WHERE (price < 100 OR price > 1000); + +-- Вопрос: +-- Почему важно явно писать скобки в условии с OR, даже если кажется, что и так понятно? +-- Ответ: +-- Потому что условие может не правильно отобразить результат, AND выполняется раньше OR. +-- Для надежности лучше скобками прописывать явно. + + +-- Задача 3 +SELECT id, name, category, price +FROM products +WHERE price >= 100 + AND category IN ('tablet', 'smartphone', 'mouse') +ORDER BY price DESC; + + +-- Задача 4 +SELECT name, category, price +FROM products +WHERE name NOT LIKE '%iPhone%'; + + +-- Задача 5 +SELECT name, category, price +FROM products +WHERE name ILIKE '%pro%'; + +SELECT name, category, price +FROM products +WHERE name ILIKE '%mx%' +ORDER BY price DESC; + +-- Вопрос: +-- В одном предложении объясни, почему для поисковой строки лучше ILIKE '%pro%', а не = 'Pro'. +-- Ответ: +-- В названии товаров могут быть буквы (в словах) с разными регистрами, при поиске клиенту можен не показать часть товаров только потому что слово выглядить так - PRO а не так - Pro. From 21aa48c237e08642e8f901586a7a9f5cede838fb Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 17 May 2026 17:18:10 +0300 Subject: [PATCH 100/123] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE:=20'=D0=A2=D0=B8=D0=BF=D1=8B=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=B8=D1=86',=20'View=20=D0=B8=20Distribute?= =?UTF-8?q?=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=BA=D0=B8'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice_view_and_distribute_engines.sql | 33 +++++++++---------- .../2. Типы данных/practice_data_types.sql | 30 +++++++++-------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql b/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql index 67eacc4..2a7de23 100644 --- a/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql +++ b/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql @@ -18,9 +18,9 @@ ENGINE = Distributed( 'clickhouse_cluster', 'mnoskov', 'logs', - rand() + user_id ); --- параметр rand() - это ключ распределния как я понял. Если стоит он, то распределяются данные случайно, если укащать колонку, то по колонке. +-- параметр rand() - это ключ распределния между шардами. Если стоит он, то распределяются данные случайно, если укащать колонку, то по колонке. -- Задание 2 @@ -54,16 +54,15 @@ LIMIT 100; -- Задание 3 --- тут вообще жопа какая-то. Если смотреть на пример создания MV, то выдает ошибку мол engine указывать нельзя. Полез в документацию, там сказано что после указания имени mv нужно добавлять TO ... но все равно выдется ошибка. --- этот способ я в хабре нашел CREATE TABLE mnoskov.raw_events +ON CLUSTER clickhouse_cluster ( - event_time DateTime, - user_id UInt32, - event_type String + event_time DateTime, + user_id UInt32, + event_type String ) -ENGINE = MergeTree -ORDER BY (event_time, user_id); +ENGINE=MergeTree +ORDER BY(event_time,user_id); CREATE TABLE mnoskov.agr_clicks_per_day ON CLUSTER clickhouse_cluster @@ -71,17 +70,17 @@ ON CLUSTER clickhouse_cluster event_date Date, count UInt64 ) -ENGINE = MergeTree +ENGINE=SummingMergeTree ORDER BY event_date; -CREATE MATERIALIZED VIEW mnoskov.mv_clicks_per_day TO mnosokv.agr_clicks_per_day AS +CREATE MATERIALIZED VIEW mnoskov.mv_clicks_per_day +ON CLUSTER clickhouse_cluster +TO mnoskov.agr_clicks_per_day AS SELECT toDate(event_time) AS event_date, count(*) AS count -FROM - mnoskov.raw_events -GROUP BY - event_date; +FROM mnoskov.raw_events +GROUP BY event_date; -SELECT * FROM mnoskov.agr_clicks_per_day; --- ничего у меня получилось, и с SummingMergeTree ни с MergeTree. Как очень непонятно материализованные таблицы работают. Если делать как в теории в инфре, то тоже ничего не работает. \ No newline at end of file +SELECT * FROM mnoskov.raw_events; +SELECT * FROM mnoskov.agr_clicks_per_day; \ No newline at end of file diff --git a/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql b/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql index a92fb71..b8d8218 100644 --- a/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql +++ b/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql @@ -13,18 +13,24 @@ ENGINE = ReplicatedMergeTree( ORDER BY tuple(); SELECT + user_id, + balance, + created_at +FROM ( + SELECT user_id, toDecimal64OrNull(balance,2) AS balance, toDateTime64OrNull(created_at, 3) AS created_at FROM mnoskov.raw_data1 +) WHERE balance IS NOT NULL AND created_at IS NOT NULL; -- Задание 2 -CREATE TABLE events +CREATE TABLE mnoskov.events ON CLUSTER clickhouse_cluster ( event_id String, event_date String, -- форматы: "2023-01-01", "01.02.2023", "invalid" @@ -42,18 +48,16 @@ SELECT * FROM mnoskov.events; SELECT event_id, - toDate(parseDateTimeBestEffortOrNull(event_date)) AS event_dates, - CASE - WHEN toDayOfMonth(parseDateTimeBestEffortOrNull(event_time)) = 1 THEN toHour(parseDateTimeBestEffortOrNull(event_time)) - ELSE (toDayOfMonth(parseDateTimeBestEffortOrNull(event_time)) - 1) * 24 + toHour(parseDateTimeBestEffortOrNull(event_time)) - END AS event_times, - toDateTime(parseDateTimeBestEffortOrNull(event_date)) + toIntervalHour(event_times) AS event_datetime -FROM - mnoskov.events + event_datetime +FROM ( + SELECT + event_id, + parseDateTimeBestEffortOrNull(concat(event_date, ' ', event_time)) AS event_datetime + FROM + mnoskov.events +) ORDER BY - event_dates; --- Все преобразования с to... и parse... нашел тут https://clickhouse.com/docs/ru/sql-reference/functions/type-conversion-functions#toIntervalMonth. --- explain analyze не работает, потэому не могу посмотреть насколько это эффективно, НО главное сам запрос работает. + event_datetime; -- Задание 3 @@ -73,7 +77,7 @@ ORDER BY SELECT user_id, - count(*) + count(*) as user_actions_cnt FROM mnoskov.user_activity WHERE From 392f412bbdb2e8a675ac20efe375f166832006de Mon Sep 17 00:00:00 2001 From: mnoskov Date: Sun, 17 May 2026 22:14:14 +0300 Subject: [PATCH 101/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=D1=81'=20-=20Clickhouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1. Движки/1. Семейство MergeTree/practice_table_types.sql | 0 .../2. View и Distribute движки}/practice_view_and_distribute_engines.sql | 0 .../2. Типы данных/practice_data_types.sql | 0 .../2. Запросы/Синтаксис/practice_syntax.sql | 215 ++++++++++++++++++ 4 files changed, 215 insertions(+) rename mnoskov/clickhouse/практика clickhouse/{ => 1. Таблицы и типы данных}/1. Движки/1. Семейство MergeTree/practice_table_types.sql (100%) rename mnoskov/clickhouse/практика clickhouse/{1. Движки => 1. Таблицы и типы данных/1. Движки/2. View и Distribute движки}/practice_view_and_distribute_engines.sql (100%) rename mnoskov/clickhouse/практика clickhouse/{ => 1. Таблицы и типы данных}/2. Типы данных/practice_data_types.sql (100%) create mode 100644 mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql diff --git a/mnoskov/clickhouse/практика clickhouse/1. Движки/1. Семейство MergeTree/practice_table_types.sql b/mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql similarity index 100% rename from mnoskov/clickhouse/практика clickhouse/1. Движки/1. Семейство MergeTree/practice_table_types.sql rename to mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql diff --git a/mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql b/mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql similarity index 100% rename from mnoskov/clickhouse/практика clickhouse/1. Движки/practice_view_and_distribute_engines.sql rename to mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql diff --git a/mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql b/mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql similarity index 100% rename from mnoskov/clickhouse/практика clickhouse/2. Типы данных/practice_data_types.sql rename to mnoskov/clickhouse/практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql diff --git a/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql b/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql new file mode 100644 index 0000000..da7ae46 --- /dev/null +++ b/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql @@ -0,0 +1,215 @@ +-- Задание 1 +CREATE TABLE mnoskov.users +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + name String, + age UInt8, + country String, + is_active UInt8 +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/users', + '{replica}' +) +ORDER BY user_id; + +INSERT INTO mnoskov.users +(user_id,name,age,country,is_active) +VALUES +(1,'Ivan',30,'RU',1), +(2,'Anna',25,'RU',1), +(3,'Petr',45,'RU',0), +(4,'Olga',35,'RU',1), +(5,'John',55,'US',1), +(6,'Alice',42,'US',0), +(7,'Tom',38,'US',1), +(8,'Lucas',28,'BR',1), +(9,'Maria',60,'BR',1), +(10,'Joao',27,'BR',0), +(11,'Kenji',33,'JP',1), +(12,'Yuki',29,'JP',0), +(13,'Chen',40,'CN',1), +(14,'Li',48,'CN',1), +(15,'Fatima',31,'AE',1), +(16,'Ahmed',36,'AE',1), +(17,'George',24,'US',1), +(18,'Nina',34,'RU',1), +(19,'Max',50,'DE',1), +(20,'Sophie',22,'DE',0); + +SELECT * FROM mnoskov.users; + +--1 +SELECT + user_id, + age, + is_active +FROM + mnoskov.users +WHERE + is_active = TRUE +ORDER BY + age DESC +LIMIT 10; + +--2 +SELECT + name, + country +FROM + mnoskov.users +WHERE + country = 'RU' +ORDER BY + name; + +--3 +SELECT + user_id, + age, + is_active +FROM + mnoskov.users +WHERE + is_active = TRUE +LIMIT 10; +-- Данные вывелить так как лежат на диске. + +--4 +SELECT + country, + count() +FROM + mnoskov.users +GROUP BY + country; + +--5 +SELECT + country, + min(age) AS min_age, + max(age) AS max_age +FROM + mnoskov.users +GROUP BY + country; + +--6 +SELECT + country, + count() AS cnt_users +FROM + mnoskov.users +GROUP BY + country +ORDER BY + cnt_users DESC; + +--Доп +SELECT + uniqExact(country), + uniq(country) +FROM + mnoskov.users; +-- uniqExact(country) - кол-во уникальных значений в колонке country. Но uniq работает также. +-- В инете написано что uniq быстрее. + + +-- Задание 2 +CREATE TABLE mnoskov.events1 +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + event_time DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events1', + '{replica}' +) +ORDER BY(user_id,event_time); + +INSERT INTO mnoskov.events1 +(user_id,event_time,event_type) +VALUES +(1,'2025-07-17 09:00:00','click'), +(1,'2025-07-17 09:05:00','view'), +(1,'2025-07-17 09:10:00','purchase'), +(2,'2025-07-17 10:00:00','click'), +(2,'2025-07-17 10:05:00','click'), +(2,'2025-07-17 11:00:00','view'), +(3,'2025-07-16 12:00:00','view'), +(3,'2025-07-16 12:30:00','click'), +(3,'2025-07-17 08:00:00','purchase'), +(4,'2025-07-17 13:00:00','click'), +(4,'2025-07-17 14:00:00','view'), +(4,'2025-07-17 15:00:00','view'), +(5,'2025-07-16 20:00:00','view'), +(5,'2025-07-17 01:00:00','click'), +(5,'2025-07-17 06:00:00','purchase'), +(6,'2025-07-17 07:30:00','view'), +(6,'2025-07-17 08:45:00','click'), +(7,'2025-07-16 23:59:59','click'), +(7,'2025-07-17 00:01:00','purchase'), +(7,'2025-07-17 10:10:00','view'); + +SELECT * FROM mnoskov.events1; + +--1 +SELECT + user_id, + event_time, + event_type, + count() OVER(PARTITION BY user_id ORDER BY event_time) AS com_users +FROM + mnoskov.events1; + +--2 +SELECT + user_id, + event_time, + event_type, + row_number() OVER (PARTITION BY user_id ORDER BY event_time) rn, + dense_rank() OVER (PARTITION BY user_id ORDER BY event_time) dr, + count() OVER(PARTITION BY user_id ORDER BY event_time) AS c, + sum(1) OVER(PARTITION BY user_id ORDER BY event_time) AS s +FROM + mnoskov.events1; + +--3 +SELECT + user_id, + event_time, + event_type, + count() OVER(PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS com_users, + count() OVER(PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS com_users2 +FROM + mnoskov.events1; +-- все четенько работает, никаких ошибкок нет + +--4-5-6 +WITH recent_events AS ( + SELECT + user_id, + event_time, + event_type + FROM + mnoskov.events1 + WHERE + event_time < now() - INTERVAL 1 DAY +) +SELECT + user_id, + count(*) AS cnt_user_actions +FROM + recent_events +GROUP BY + user_id +ORDER BY + cnt_user_actions DESC +LIMIT + 5; +-- не понял, как сделать 6 пункт. + +-- Ответ на вопрос - ну да - поддерживает. \ No newline at end of file From 80d333fe52da63203c936175e2bb65ffe59dbcf4 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 18 May 2026 09:55:03 +0300 Subject: [PATCH 102/123] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20-=20'=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D1=81'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Запросы/Синтаксис/practice_syntax.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql b/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql index da7ae46..c7c4d8c 100644 --- a/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql +++ b/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql @@ -6,7 +6,7 @@ ON CLUSTER clickhouse_cluster name String, age UInt8, country String, - is_active UInt8 + is_active Bool ) ENGINE = ReplicatedMergeTree( '/clickhouse/tables/{shard}/mnoskov/users', @@ -79,7 +79,7 @@ LIMIT 10; --4 SELECT country, - count() + count() as cnt_users FROM mnoskov.users GROUP BY From f427cfe22e29f9935da2e7c684ce42f5c6a039d8 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Mon, 18 May 2026 11:55:15 +0300 Subject: [PATCH 103/123] fix WHERE in task 1 --- vberetar/postgresql/sql_filters.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vberetar/postgresql/sql_filters.sql b/vberetar/postgresql/sql_filters.sql index 423f131..74925dd 100644 --- a/vberetar/postgresql/sql_filters.sql +++ b/vberetar/postgresql/sql_filters.sql @@ -5,7 +5,7 @@ SELECT name, category, price, FROM products WHERE (price BETWEEN 100 AND 1300) AND category NOT IN ('smartwatch') - AND in_stock = 'true'; + AND in_stock = TRUE; -- Вопрос: -- Что лучше читается BETWEEN 100 AND 1300 или price >= 100 AND price <= 1300 и почему? From ae208a76fb8011336f8d8151569d54ed7e885ca3 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 18 May 2026 16:55:22 +0300 Subject: [PATCH 104/123] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8:=20'=D0=A1=D0=B8=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=D1=81'.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Практика: 'JOIN' - 'Массивы, Map и вложенные структуры*' - 'Партиции' - 'Индексы'. --- .../1. Движки/1. Семейство MergeTree/practice_table_types.sql | 120 ++++++++++++++++++ .../2. View и Distribute движки/practice_view_and_distribute_engines.sql | 86 +++++++++++++ .../2. Типы данных/practice_data_types.sql | 115 +++++++++++++++++ .../2. Запросы/1. Синтаксис}/practice_syntax.sql | 0 .../2. Запросы/2. JOIN/practice_join.sql | 57 +++++++++ .../practice_arrays_maps_and_nested_structures.sql | 44 +++++++ .../1. Партиции/practice_partitions.sql | 86 +++++++++++++ .../2. Индексы/practice_indexes.sql | 69 ++++++++++ 8 files changed, 577 insertions(+) create mode 100644 mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql create mode 100644 mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql create mode 100644 mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql rename mnoskov/clickhouse/{практика clickhouse/2. Запросы/Синтаксис => Практика clickhouse/2. Запросы/1. Синтаксис}/practice_syntax.sql (100%) create mode 100644 mnoskov/clickhouse/Практика clickhouse/2. Запросы/2. JOIN/practice_join.sql create mode 100644 mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql create mode 100644 mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/1. Партиции/practice_partitions.sql create mode 100644 mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/2. Индексы/practice_indexes.sql diff --git a/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql new file mode 100644 index 0000000..6140c2f --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/1. Семейство MergeTree/practice_table_types.sql @@ -0,0 +1,120 @@ +-- Задание 1 + +CREATE TABLE mnoskov.page_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + url String, + view_time DateTime +) +ENGINE = MergeTree +PARTITION BY toYYYYMM(view_time) +ORDER BY (user_id, view_time); + + +-- Задание 2 +CREATE TABLE mnoskov.users +ON CLUSTER clickhouse_cluster +( + id UInt64, + name String, + update_at DateTime +) +ENGINE = ReplacingMergeTree(update_at) +ORDER BY id; + + +-- Задание 3 +CREATE TABLE mnoskov.region_sales +ON CLUSTER clickhouse_cluster +( + region String, + date Date, + sales UInt64 +) +ENGINE = SummingMergeTree +PARTITION BY toYYYYMM(date) +ORDER BY (region, date); + +INSERT INTO mnoskov.region_sales (region, date, sales) +VALUES + ('Moscow', '2026-05-01', 100), + ('Moscow', '2026-05-01', 150), + ('Moscow', '2026-05-01', 200), + ('Berlin', '2026-05-02', 300), + ('Berlin', '2026-05-02', 120), + ('Berlin', '2026-05-02', 80), + ('Paris', '2026-05-03', 500), + ('Paris', '2026-05-03', 250), + ('London', '2026-05-04', 400), + ('Tokyo', '2026-05-05', 700); + +SELECT * FROM mnoskov.region_sales; +-- Все четко работает. + + +-- Задание 4 +CREATE TABLE mnoskov.user_metrics +ON CLUSTER clickhouse_cluster +( + region String, + avg_age_state AggregateFunction(avg, UInt8), + active_users_state AggregateFunction(uniq, UInt64) +) +ENGINE = AggregatingMergeTree +ORDER BY region; + +CREATE TABLE mnoskov.user_metrics_source +ON CLUSTER clickhouse_cluster +( + region String, + age UInt8, + user_id UInt64 +) +ENGINE = MergeTree +ORDER BY region; + +INSERT INTO mnoskov.user_metrics_source +(region, age, user_id) +VALUES + ('Moscow', 25, 1001), + ('Moscow', 30, 1002), + ('Moscow', 22, 1003), + ('Moscow', 25, 1001), + ('Berlin', 40, 2001), + ('Berlin', 35, 2002), + ('Berlin', 29, 2003), + ('Berlin', 35, 2002), + ('Paris', 31, 3001), + ('Paris', 28, 3002), + ('Paris', 45, 3003), + ('Paris', 31, 3001), + ('London', 50, 4001), + ('London', 41, 4002), + ('London', 38, 4003), + ('London', 41, 4002), + ('Tokyo', 27, 5001), + ('Tokyo', 33, 5002), + ('Tokyo', 29, 5003), + ('Tokyo', 33, 5002); +SELECT * FROM mnoskov.user_metrics_source; + +INSERT INTO mnoskov.user_metrics +SELECT + region, + avgState(age) AS avg_age_state, + uniqState(user_id) AS active_users_state +FROM + mnoskov.user_metrics_source +GROUP BY + region; + +SELECT + region, + avgMerge(avg_age_state), + uniqMerge(active_users_state) +FROM + mnoskov.user_metrics +GROUP BY + region; +-- Че то кликхаус странно себя ведет, то таблицы сами очищаются, то удаленные таблицы воскресают. \ No newline at end of file diff --git a/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql new file mode 100644 index 0000000..2a7de23 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/1. Движки/2. View и Distribute движки/practice_view_and_distribute_engines.sql @@ -0,0 +1,86 @@ +-- Задание 1 + +CREATE TABLE mnoskov.logs +ON CLUSTER clickhouse_cluster ( + dt DateTime, + user_id UInt32, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/logs', + '{replica}' +) +ORDER BY dt; + +CREATE TABLE mnoskov.dist_logs +ON CLUSTER clickhouse_cluster +ENGINE = Distributed( + 'clickhouse_cluster', + 'mnoskov', + 'logs', + user_id +); +-- параметр rand() - это ключ распределния между шардами. Если стоит он, то распределяются данные случайно, если укащать колонку, то по колонке. + + +-- Задание 2 + +CREATE TABLE mnoskov.events +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + event_type String, + ts DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events', + '{replica}' +) +ORDER BY (ts, user_id); + +CREATE VIEW mnoskov.top_active_users AS +SELECT + user_id, + count(*) AS cnt_user +FROM + mnoskov.events +GROUP BY + user_id +ORDER BY + cnt_user DESC, user_id +LIMIT 100; +-- VIEW не подойдет, тк вьюшка будет каждый раз считаться заново, а это энергозатратно, поэтому надо использовать MATERIALIZED VIEW. + + +-- Задание 3 + +CREATE TABLE mnoskov.raw_events +ON CLUSTER clickhouse_cluster +( + event_time DateTime, + user_id UInt32, + event_type String +) +ENGINE=MergeTree +ORDER BY(event_time,user_id); + +CREATE TABLE mnoskov.agr_clicks_per_day +ON CLUSTER clickhouse_cluster +( + event_date Date, + count UInt64 +) +ENGINE=SummingMergeTree +ORDER BY event_date; + +CREATE MATERIALIZED VIEW mnoskov.mv_clicks_per_day +ON CLUSTER clickhouse_cluster +TO mnoskov.agr_clicks_per_day AS +SELECT + toDate(event_time) AS event_date, + count(*) AS count +FROM mnoskov.raw_events +GROUP BY event_date; + +SELECT * FROM mnoskov.raw_events; +SELECT * FROM mnoskov.agr_clicks_per_day; \ No newline at end of file diff --git a/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql new file mode 100644 index 0000000..b8d8218 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/1. Таблицы и типы данных/2. Типы данных/practice_data_types.sql @@ -0,0 +1,115 @@ +-- Задание 1 + +CREATE TABLE mnoskov.raw_data1 +ON CLUSTER clickhouse_cluster ( + user_id String, + balance String, + created_at String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/raw_data1', + '{replica}' +) +ORDER BY tuple(); + +SELECT + user_id, + balance, + created_at +FROM ( + SELECT + user_id, + toDecimal64OrNull(balance,2) AS balance, + toDateTime64OrNull(created_at, 3) AS created_at +FROM + mnoskov.raw_data1 +) +WHERE + balance IS NOT NULL AND created_at IS NOT NULL; + + +-- Задание 2 + +CREATE TABLE mnoskov.events +ON CLUSTER clickhouse_cluster ( + event_id String, + event_date String, -- форматы: "2023-01-01", "01.02.2023", "invalid" + event_time String -- форматы: "12:30:45", "25:99:99" +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events', + '{replica}' +) +ORDER BY + event_id; + +SELECT * FROM mnoskov.events; + + +SELECT + event_id, + event_datetime +FROM ( + SELECT + event_id, + parseDateTimeBestEffortOrNull(concat(event_date, ' ', event_time)) AS event_datetime + FROM + mnoskov.events +) +ORDER BY + event_datetime; + + +-- Задание 3 + +CREATE TABLE mnoskov.user_activity +ON CLUSTER clickhouse_cluster +( + user_id UInt32, + actions String -- JSON: ["login", "purchase", "logout"] +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/user_activity', + '{replica}' +) +ORDER BY + user_id; + +SELECT + user_id, + count(*) as user_actions_cnt +FROM + mnoskov.user_activity +WHERE + has(JSONExtract(actions, 'Array(String)'), 'purchase') +GROUP BY + user_id +ORDER BY + user_id; + + +-- Задание 4 + +CREATE TABLE mnoskov.sensor_data +ON CLUSTER clickhouse_cluster +( + sensor_id UInt32, + value Float64, + status Enum('ok' = 1, 'error' = 2) +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/sensor_data', + '{replica}' +) +ORDER BY sensor_id; + +--1-2. +SELECT + toString(status) AS str_status, + avg(value) AS avg_value +FROM + mnoskov.sensor_data +GROUP BY + str_status; +--3 +-- не понял задание. все строки в value можно преобразовать в Decimal(10,2), потому что value - Float64, а он все null переделывает в 0. \ No newline at end of file diff --git a/mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/1. Синтаксис/practice_syntax.sql similarity index 100% rename from mnoskov/clickhouse/практика clickhouse/2. Запросы/Синтаксис/practice_syntax.sql rename to mnoskov/clickhouse/Практика clickhouse/2. Запросы/1. Синтаксис/practice_syntax.sql diff --git a/mnoskov/clickhouse/Практика clickhouse/2. Запросы/2. JOIN/practice_join.sql b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/2. JOIN/practice_join.sql new file mode 100644 index 0000000..f29dbc3 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/2. JOIN/practice_join.sql @@ -0,0 +1,57 @@ +-- Задание 1 + +SELECT + pro.product_id, + pro.name, + pri.price +FROM + mnoskov.products pro +ANY LEFT JOIN + mnoskov.prices pri ON pro.product_id = pri.product_id; +-- 2 Выберется случайная из цен. +-- 3 ALL LEFT JOIN - работает также как и LEFT JOIN. Его избегают потому что он тянет больше строк в оперативную память. + + +-- Задание 2 + +SELECT + o.time, + o.symbol, + o.size, + t.price +FROM + mnoskov.orders o +ASOF JOIN + mnoskov.ticks t ON o.symbol = t.symbol AND t.time <= o.time +WHERE symbol = 'AAPL'; +-- Потому что дата сделки и дата последней фиксации цены - разные. + + +-- Задание 3 + +SELECT + user_id, + name +FROM + mnoskov.users +WHERE user_id IN ( + SELECT user_id + FROM mnoskov.payments + WHERE amount > 1000 +); +-- 3 Потому что это фильтрация по существованию. Как оптимизируется не знаю, наверное как подзапрос. + + +-- Задание 4 + +SELECT + bl.user_id, + u.name AS user_name, + bl.event_time, + bl.event_type +FROM + mnoskov.big_logs bl +JOIN + mnoskov.users u ON bl.user_id = u.user_id; +-- 2 потому что таблица users меньше, а значит в оператику полетит меньше данных. +-- 3 если user_id уникален, то нужно выбирать ALL, так как дублей нет. В текущем задании дубли есть, но мы все равно выбираем ALL, тк нужно вывести имя для каждого лога а не юзера. \ No newline at end of file diff --git a/mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql new file mode 100644 index 0000000..6ed16cc --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql @@ -0,0 +1,44 @@ +-- Задание 1 + +--1-2 +SELECT + user_id, + tags, + indexOf(tags, 'promo') AS pos_promo +FROM + mnoskov.events +WHERE + has(tags, 'promo'); + +--3 +SELECT + user_id, + arrayJoin(tags) AS tag +FROM + mnoskov.events; + + +-- Задание 2 + +--1 +SELECT user_id, scores['math'] FROM mnoskov.metrics; + +--2 +SELECT user_id, mapValues(scores) FROM mnoskov.metrics; + + +-- Заданик 3 + +--1 +SELECT * FROM mnoskov.users_nested WHERE has(contacts.type, 'email'); + +--2 +SELECT + id, + name, + c.type, + c.value +FROM + mnoskov.users_nested +ARRAY JOIN + contacts AS c; \ No newline at end of file diff --git a/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/1. Партиции/practice_partitions.sql b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/1. Партиции/practice_partitions.sql new file mode 100644 index 0000000..2a855a6 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/1. Партиции/practice_partitions.sql @@ -0,0 +1,86 @@ +-- Задание 1 + +CREATE TABLE mnoskov.sales +ON CLUSTER clickhouse_cluster +( + id UInt64, + region String, + sale_date Date, + amount Float64 +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/sales', + '{replica}' +) +PARTITION BY toYYYYMM(sale_date) +ORDER BY sale_date; + +ALTER TABLE mnoskov.sales DROP PARTITION '202405'; +SELECT * FROM mnoskov.sales; +-- Пропали строки где год/месяц 202405. +-- Если не указывать PARTITION BY, то строки бы искались по всей таблице, что дольше и затранее. + + +-- Задание 2 + +CREATE TABLE mnoskov.events +ON CLUSTER clickhouse_cluster +( + id UInt64, + event_type String, + event_date Date +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/events_partitioned', + '{replica}' +) +PARTITION BY event_type +ORDER BY event_date; + +ALTER TABLE mnoskov.events DROP PARTITION 'click'; +ELECT * FROM mnoskov.events; +-- Такой подход опасен, тк например login выполняется не часто по сравнению с click и получается жесткий перевес. + + +-- Задание 3 + +CREATE TABLE mnoskov.logs +ON CLUSTER clickhouse_cluster +( + id UInt64, + user_id UInt64, + log_time DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/logs_partitioned', + '{replica}' +) +PARTITION BY cityHash64(user_id) % 100 +ORDER BY log_time; + +SELECT * FROM mnoskov.logs + +-- 2 с точки зрения баланса, у нас юзеры распределные равномерно и нет никаких перекосов. +-- 3 хз если честно, мне кажется должны ускорять. + + +-- Задание 4 + +CREATE TABLE mnoskov.visits +ON CLUSTER clickhouse_cluster +( + id UInt64, + region String, + visit_date Date +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/visits', + '{replica}' +) +PARTITION BY (region, toYYYYMM(visit_date)) +ORDER BY visit_date; + +SELECT * FROM mnoskov.visits; +-- 2 доступа к системным таблицам нет, потэтому я не могу посмотреть имя партиции. +-- 3 ну да +ALTER TABLE mnoskov.visits DROP PARTITION ('RU', '202407'); \ No newline at end of file diff --git a/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/2. Индексы/practice_indexes.sql b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/2. Индексы/practice_indexes.sql new file mode 100644 index 0000000..7147e3e --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/2. Индексы/practice_indexes.sql @@ -0,0 +1,69 @@ +-- Задание 1 + +CREATE TABLE mnoskov.logs +ON CLUSTER clickhouse_cluster +( + log_id UInt64, + ts DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/logs_ttl', + '{replica}' +) +ORDER BY ts; +ALTER TABLE mnoskov.logs ADD INDEX idx_ts ts TYPE minmax granularity 2; + +SELECT * FROM logs WHERE ts BETWEEN '2024-06-02' AND '2024-06-03'; + +-- 2 У меня нет доступа к системным таблицам, я не могу посмотреть. +-- Если в order by будет log_id а не ts, то индекс не будет работать. + + +-- Задание 2 + +CREATE TABLE mnoskov.clicks +ON CLUSTER clickhouse_cluster +( + id UInt64, + country String +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/clicks', + '{replica}' +) +ORDER BY id; + +ALTER TABLE mnoskov.clicks ADD INDEX idx_country country TYPE set(1000) GRANULARITY 2; +ALTER TABLE mnoskov.clicks DROP INDEX idx_country; + +explain +SELECT * FROM clicks WHERE country = 'RU'; +-- 3 не использутся индеск, я туда 11тыс строк заинсертил, все равно не используется. +-- 4 я думаю индекс не будет работать эффективно, а может быть и вообще будет запрос замедлять. + + +-- Задание 3 + +CREATE TABLE mnoskov.users +ON CLUSTER clickhouse_cluster +( + id UInt64, + email String +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/users_simple', + '{replica}' +) +ORDER BY id; + +ALTER TABLE mnoskov.users ADD INDEX idx_email email TYPE bloom_filter(0.01) GRANULARITY 2; + +explain +SELECT * FROM users WHERE email = 'alice@mail.com'; +-- 2 Ну я не вижу в EXPLAIN какое либо упоминание индекса, но я ссылаюсь на то, что просто мало данных в таблице. + +-- 3 Потому что этот индекс работает только на точные совпадения - exact match. + +explain +SELECT * FROM users WHERE email LIKE 'alice%'; +-- 4 Индекс не будет использоваться. \ No newline at end of file From c2eacd2bde15422c0d22d6950e3c00e72ec2f226 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 18 May 2026 19:55:26 +0400 Subject: [PATCH 105/123] =?UTF-8?q?1.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BE=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B9?= =?UTF-8?q?=20=D0=BA=D0=BB=D1=8E=D1=87=20=D0=B4=D0=BB=D1=8F=20course=5Fcom?= =?UTF-8?q?pletion=5Frate,=20=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20DROP?= =?UTF-8?q?=20TABLE=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8C=D1=8E=D1=85,=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20age=20UInt8=20?= =?UTF-8?q?=D0=BD=D0=B0=20UInt32=202.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D1=85=D0=B5=D0=BC=D1=8B=20=D0=B2=20=D0=AD?= =?UTF-8?q?=D1=82=D0=B0=D0=BF=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 2. Создание витринных таблиц.sql | 9 ++++++++- .../Pet-Project/Этап 3. Анализ и запросы.sql | 12 ++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql b/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql index e2dc606..8f6e555 100644 --- a/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql +++ b/dsolovianov/clickhouse/Pet-Project/Этап 2. Создание витринных таблиц.sql @@ -18,6 +18,8 @@ CREATE TABLE dsolovianov.lesson_popularity_summary ON CLUSTER clickhouse_cluster ) ORDER BY (course_id, lesson_id); +DROP TABLE IF EXISTS dsolovianov.lesson_popularity_summary_mv ON CLUSTER clickhouse_cluster SYNC; + CREATE MATERIALIZED VIEW dsolovianov.lesson_popularity_summary_mv ON CLUSTER clickhouse_cluster TO dsolovianov.lesson_popularity_summary AS @@ -58,7 +60,7 @@ CREATE TABLE dsolovianov.inactive_users_summary ON CLUSTER clickhouse_cluster ( user_id UInt32, name String, email String, - age UInt8, + age UInt32, registered_courses_count UInt32, registration_date Date ) ENGINE = ReplicatedMergeTree( @@ -67,6 +69,8 @@ CREATE TABLE dsolovianov.inactive_users_summary ON CLUSTER clickhouse_cluster ( ) ORDER BY user_id; +DROP TABLE IF EXISTS dsolovianov.inactive_users_summary_mv ON CLUSTER clickhouse_cluster SYNC; + CREATE MATERIALIZED VIEW dsolovianov.inactive_users_summary_mv ON CLUSTER clickhouse_cluster TO dsolovianov.inactive_users_summary AS @@ -184,3 +188,6 @@ FROM dsolovianov.core_enrollments e JOIN dsolovianov.core_dim_user u ON e.user_id = u.user_id JOIN dsolovianov.core_dim_course c ON e.course_id = c.course_id GROUP BY e.user_id, u.name, e.course_id, c.title; + +SELECT * FROM dsolovianov.course_completion_rate ORDER BY user_id; + diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql b/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql index 6550e5b..875ee9a 100644 --- a/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql +++ b/dsolovianov/clickhouse/Pet-Project/Этап 3. Анализ и запросы.sql @@ -1,21 +1,25 @@ --- Этап 3. Анализ и запросы - Напиши SQL-запросы к витринам -- Напиши свои вариации для практики-- +-- Этап 3. Анализ и запросы - Напиши SQL-запросы к витринам +-- Напиши свои вариации для практики-- + + -- Топ-5 самых просматриваемых уроков SELECT lesson_title, course_title, total_views -FROM lesson_popularity_summary +FROM dsolovianov.lesson_popularity_summary ORDER BY total_views DESC LIMIT 5; + -- Неактивные пользователи, записавшиеся на курсы SELECT name, email, registered_courses_count -FROM inactive_users_summary +FROM dsolovianov.inactive_users_summary WHERE registered_courses_count > 0 ORDER BY registration_date DESC; @@ -24,7 +28,7 @@ ORDER BY registration_date DESC; SELECT course_title, AVG(completion_rate) AS avg_completion -FROM course_completion_rate +FROM dsolovianov.course_completion_rate GROUP BY course_title ORDER BY avg_completion DESC LIMIT 5; From 4828bd4afbb78b994fb8704d40eb943cdd970622 Mon Sep 17 00:00:00 2001 From: dmitry solovianov Date: Mon, 18 May 2026 19:57:59 +0400 Subject: [PATCH 106/123] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20dsolovianov/clickhouse/Pet-Project/test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsolovianov/clickhouse/Pet-Project/test | 0 .../Этап 4. Тестирование и валидация.sql | 13 ------------- 2 files changed, 13 deletions(-) create mode 100644 dsolovianov/clickhouse/Pet-Project/test delete mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql diff --git a/dsolovianov/clickhouse/Pet-Project/test b/dsolovianov/clickhouse/Pet-Project/test new file mode 100644 index 0000000..e69de29 diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql b/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql deleted file mode 100644 index f053160..0000000 --- a/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Этап 4. Тестирование и валидация -- - -/* -### **Задание 6: Сравни данные с Greenplum** - -1. Выбери 2–3 значения из таблиц `_summary_source` в Greenplum -2. Сравни с ClickHouse-результатами: - - Совпадает ли `SUM`, `AVG`, `MIN`, `MAX` - - Проверяй `NULL`, `0`, округления -3. Напиши мини-отчёт: **"где совпадает, где нет, почему"** -*/ - --- Ничего не понятно по этому этапу. У меня в GP нет таблиц "_summary_source" From e8cd8858fc5a124d3b50e2a2223f67863b14d767 Mon Sep 17 00:00:00 2001 From: Bonifatsiy Date: Mon, 18 May 2026 19:58:38 +0400 Subject: [PATCH 107/123] skip --- .../Этап 4. Тестирование и валидация.sql | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql diff --git a/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql b/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql deleted file mode 100644 index f053160..0000000 --- a/dsolovianov/clickhouse/Pet-Project/Этап 4. Тестирование и валидация.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Этап 4. Тестирование и валидация -- - -/* -### **Задание 6: Сравни данные с Greenplum** - -1. Выбери 2–3 значения из таблиц `_summary_source` в Greenplum -2. Сравни с ClickHouse-результатами: - - Совпадает ли `SUM`, `AVG`, `MIN`, `MAX` - - Проверяй `NULL`, `0`, округления -3. Напиши мини-отчёт: **"где совпадает, где нет, почему"** -*/ - --- Ничего не понятно по этому этапу. У меня в GP нет таблиц "_summary_source" From 5949db1db8b9daac0ec80385c14ecf0141c9f8e6 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 19 May 2026 09:45:42 +0300 Subject: [PATCH 108/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'=D0=A1=D0=B6=D0=B0=D1=82=D0=B8=D0=B5'=20-=20Cli?= =?UTF-8?q?ckhouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3. Сжатие/practice_compression.sql | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/3. Сжатие/practice_compression.sql diff --git a/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/3. Сжатие/practice_compression.sql b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/3. Сжатие/practice_compression.sql new file mode 100644 index 0000000..f8758f4 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/3. Индексы и сжатие/3. Сжатие/practice_compression.sql @@ -0,0 +1,27 @@ +-- Задание 1 + +CREATE TABLE mnoskov.logs +ON CLUSTER clickhouse_cluster +( + id UInt64, + level String, + message String CODEC(ZSTD(3)), + created_at DateTime CODEC(Delta, ZSTD) +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/logs', + '{replica}' +) +ORDER BY created_at; + +SELECT + column, + compression_codec +FROM system.columns +WHERE table = 'logs'; +-- если делать с codec_expression, то ошибка вылазит мол такой колонки нет +-- Выглядит вот так: +-- id +-- level +-- message CODEC(ZSTD(3)) +-- created_at CODEC(Delta(4), ZSTD(1)) \ No newline at end of file From 84006e9b80a699d4e1b21591f98b0ea07ef2b450 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 19 May 2026 12:04:41 +0300 Subject: [PATCH 109/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=201:=20=D0=97=D0=B0=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=B7=D0=B8=D1=82=D1=8C=20CSV-=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql new file mode 100644 index 0000000..f2045ef --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql @@ -0,0 +1,61 @@ +--DROP TABLE IF EXISTS mnoskov.core_dim_user ON CLUSTER clickhouse_cluster SYNC; +CREATE TABLE mnoskov.core_dim_user +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + name String, + age UInt8, + email String, + registration_date DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_dim_user', + '{replica}' +) +ORDER BY user_id; + +--DROP TABLE IF EXISTS mnoskov.core_dim_course ON CLUSTER clickhouse_cluster SYNC; +CREATE TABLE mnoskov.core_dim_course +ON CLUSTER clickhouse_cluster +( + course_id UInt64, + title String, + category LowCardinality(String), + created_at DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_dim_course', + '{replica}' +) +ORDER BY course_id; + +--DROP TABLE IF EXISTS mnoskov.core_dim_lesson ON CLUSTER clickhouse_cluster SYNC; +CREATE TABLE mnoskov.core_dim_lesson +ON CLUSTER clickhouse_cluster +( + lesson_id UInt64, + title String, + duration_min UInt16, + course_id UInt64 +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_dim_lesson', + '{replica}' +) +ORDER BY (lesson_id, course_id); + +--DROP TABLE IF EXISTS mnoskov.core_dim_date ON CLUSTER clickhouse_cluster SYNC; +CREATE TABLE mnoskov.core_dim_date +ON CLUSTER clickhouse_cluster +( + date Date, + year Int16, + month Int8, + day Int8, + weekday LowCardinality(String) +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_dim_date', + '{replica}' +) +ORDER BY date; \ No newline at end of file From a83112d1550cd3d3b5cbd05d0e262e362f2f1b54 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 19 May 2026 13:19:32 +0300 Subject: [PATCH 110/123] =?UTF-8?q?=D0=94=D0=9E=D0=9F=D0=9E=D0=9B=D0=9D?= =?UTF-8?q?=D0=98=D0=9B=20-=20=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=201:=20=D0=97=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D1=8C=20CSV-=D1=84=D0=B0=D0=B9=D0=BB=D1=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csv files/dim_course_202605191311.csv | 6 + .../csv files/dim_date_202605191312.csv | 2923 +++++++++++++++++ .../csv files/dim_lesson_202605191312.csv | 19 + .../csv files/dim_user_202605191313.csv | 21 + .../csv files/enrollments_202605191313.csv | 46 + .../csv files/fact_lesson_views_202605191313.csv | 59 + .../Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql | 38 +- 7 files changed, 3111 insertions(+), 1 deletion(-) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv new file mode 100644 index 0000000..0bae8f3 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv @@ -0,0 +1,6 @@ +"course_id","title","category","created_at" +2,Python для анализа данных,programming,2023-01-15 00:00:00.000 +0300 +3,BI с нуля,business,2023-03-01 00:00:00.000 +0300 +4,Excel для аналитиков,business,2023-02-01 00:00:00.000 +0300 +1,SQL для начинающих,data,2023-01-01 00:00:00.000 +0300 +5,Основы машинного обучения,data,2023-03-15 00:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv new file mode 100644 index 0000000..319c0ad --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv @@ -0,0 +1,2923 @@ +"date","year","month","day","weekday" +2023-01-01,2023,1,1,sunday +2023-01-03,2023,1,3,tuesday +2023-01-05,2023,1,5,thursday +2023-01-09,2023,1,9,monday +2023-01-11,2023,1,11,wednesday +2023-01-13,2023,1,13,friday +2023-01-15,2023,1,15,sunday +2023-01-17,2023,1,17,tuesday +2023-01-21,2023,1,21,saturday +2023-01-23,2023,1,23,monday +2023-01-24,2023,1,24,tuesday +2023-01-26,2023,1,26,thursday +2023-01-27,2023,1,27,friday +2023-01-28,2023,1,28,saturday +2023-01-31,2023,1,31,tuesday +2023-02-02,2023,2,2,thursday +2023-02-06,2023,2,6,monday +2023-02-08,2023,2,8,wednesday +2023-02-10,2023,2,10,friday +2023-02-14,2023,2,14,tuesday +2023-02-18,2023,2,18,saturday +2023-02-21,2023,2,21,tuesday +2023-02-23,2023,2,23,thursday +2023-02-24,2023,2,24,friday +2023-02-25,2023,2,25,saturday +2023-02-26,2023,2,26,sunday +2023-02-27,2023,2,27,monday +2023-03-01,2023,3,1,wednesday +2023-03-05,2023,3,5,sunday +2023-03-07,2023,3,7,tuesday +2023-03-11,2023,3,11,saturday +2023-03-12,2023,3,12,sunday +2023-03-16,2023,3,16,thursday +2023-03-18,2023,3,18,saturday +2023-03-20,2023,3,20,monday +2023-03-21,2023,3,21,tuesday +2023-03-23,2023,3,23,thursday +2023-03-24,2023,3,24,friday +2023-03-28,2023,3,28,tuesday +2023-03-30,2023,3,30,thursday +2023-04-02,2023,4,2,sunday +2023-04-04,2023,4,4,tuesday +2023-04-05,2023,4,5,wednesday +2023-04-09,2023,4,9,sunday +2023-04-10,2023,4,10,monday +2023-04-11,2023,4,11,tuesday +2023-04-12,2023,4,12,wednesday +2023-04-16,2023,4,16,sunday +2023-04-18,2023,4,18,tuesday +2023-04-20,2023,4,20,thursday +2023-04-21,2023,4,21,friday +2023-04-22,2023,4,22,saturday +2023-04-25,2023,4,25,tuesday +2023-04-27,2023,4,27,thursday +2023-04-29,2023,4,29,saturday +2023-05-02,2023,5,2,tuesday +2023-05-06,2023,5,6,saturday +2023-05-09,2023,5,9,tuesday +2023-05-11,2023,5,11,thursday +2023-05-12,2023,5,12,friday +2023-05-14,2023,5,14,sunday +2023-05-15,2023,5,15,monday +2023-05-17,2023,5,17,wednesday +2023-05-19,2023,5,19,friday +2023-05-20,2023,5,20,saturday +2023-05-22,2023,5,22,monday +2023-05-23,2023,5,23,tuesday +2023-05-24,2023,5,24,wednesday +2023-05-25,2023,5,25,thursday +2023-05-26,2023,5,26,friday +2023-05-29,2023,5,29,monday +2023-06-01,2023,6,1,thursday +2023-06-02,2023,6,2,friday +2023-06-08,2023,6,8,thursday +2023-06-10,2023,6,10,saturday +2023-06-12,2023,6,12,monday +2023-06-14,2023,6,14,wednesday +2023-06-21,2023,6,21,wednesday +2023-06-24,2023,6,24,saturday +2023-06-25,2023,6,25,sunday +2023-06-26,2023,6,26,monday +2023-06-27,2023,6,27,tuesday +2023-06-29,2023,6,29,thursday +2023-06-30,2023,6,30,friday +2023-07-03,2023,7,3,monday +2023-07-06,2023,7,6,thursday +2023-07-08,2023,7,8,saturday +2023-07-10,2023,7,10,monday +2023-07-12,2023,7,12,wednesday +2023-07-14,2023,7,14,friday +2023-07-15,2023,7,15,saturday +2023-07-16,2023,7,16,sunday +2023-07-17,2023,7,17,monday +2023-07-20,2023,7,20,thursday +2023-07-21,2023,7,21,friday +2023-07-23,2023,7,23,sunday +2023-07-24,2023,7,24,monday +2023-07-25,2023,7,25,tuesday +2023-07-30,2023,7,30,sunday +2023-07-31,2023,7,31,monday +2023-08-01,2023,8,1,tuesday +2023-08-04,2023,8,4,friday +2023-08-08,2023,8,8,tuesday +2023-08-09,2023,8,9,wednesday +2023-08-10,2023,8,10,thursday +2023-08-12,2023,8,12,saturday +2023-08-13,2023,8,13,sunday +2023-08-21,2023,8,21,monday +2023-08-26,2023,8,26,saturday +2023-08-27,2023,8,27,sunday +2023-08-28,2023,8,28,monday +2023-08-29,2023,8,29,tuesday +2023-08-30,2023,8,30,wednesday +2023-09-01,2023,9,1,friday +2023-09-02,2023,9,2,saturday +2023-09-03,2023,9,3,sunday +2023-09-06,2023,9,6,wednesday +2023-09-08,2023,9,8,friday +2023-09-09,2023,9,9,saturday +2023-09-11,2023,9,11,monday +2023-09-13,2023,9,13,wednesday +2023-09-14,2023,9,14,thursday +2023-09-15,2023,9,15,friday +2023-09-17,2023,9,17,sunday +2023-09-19,2023,9,19,tuesday +2023-09-22,2023,9,22,friday +2023-09-23,2023,9,23,saturday +2023-09-25,2023,9,25,monday +2023-09-26,2023,9,26,tuesday +2023-09-27,2023,9,27,wednesday +2023-09-28,2023,9,28,thursday +2023-09-30,2023,9,30,saturday +2023-10-03,2023,10,3,tuesday +2023-10-11,2023,10,11,wednesday +2023-10-12,2023,10,12,thursday +2023-10-14,2023,10,14,saturday +2023-10-15,2023,10,15,sunday +2023-10-16,2023,10,16,monday +2023-10-18,2023,10,18,wednesday +2023-10-19,2023,10,19,thursday +2023-10-20,2023,10,20,friday +2023-10-21,2023,10,21,saturday +2023-10-22,2023,10,22,sunday +2023-10-23,2023,10,23,monday +2023-10-27,2023,10,27,friday +2023-10-28,2023,10,28,saturday +2023-10-31,2023,10,31,tuesday +2023-11-01,2023,11,1,wednesday +2023-11-02,2023,11,2,thursday +2023-11-05,2023,11,5,sunday +2023-11-06,2023,11,6,monday +2023-11-08,2023,11,8,wednesday +2023-11-10,2023,11,10,friday +2023-11-11,2023,11,11,saturday +2023-11-14,2023,11,14,tuesday +2023-11-15,2023,11,15,wednesday +2023-11-17,2023,11,17,friday +2023-11-19,2023,11,19,sunday +2023-11-21,2023,11,21,tuesday +2023-11-22,2023,11,22,wednesday +2023-11-24,2023,11,24,friday +2023-11-28,2023,11,28,tuesday +2023-11-30,2023,11,30,thursday +2023-12-01,2023,12,1,friday +2023-12-02,2023,12,2,saturday +2023-12-03,2023,12,3,sunday +2023-12-04,2023,12,4,monday +2023-12-07,2023,12,7,thursday +2023-12-12,2023,12,12,tuesday +2023-12-14,2023,12,14,thursday +2023-12-15,2023,12,15,friday +2023-12-17,2023,12,17,sunday +2023-12-18,2023,12,18,monday +2023-12-19,2023,12,19,tuesday +2023-12-21,2023,12,21,thursday +2023-12-22,2023,12,22,friday +2023-12-23,2023,12,23,saturday +2023-12-27,2023,12,27,wednesday +2023-12-31,2023,12,31,sunday +2024-01-02,2024,1,2,tuesday +2024-01-03,2024,1,3,wednesday +2024-01-06,2024,1,6,saturday +2024-01-07,2024,1,7,sunday +2024-01-08,2024,1,8,monday +2024-01-13,2024,1,13,saturday +2024-01-15,2024,1,15,monday +2024-01-18,2024,1,18,thursday +2024-01-19,2024,1,19,friday +2024-01-20,2024,1,20,saturday +2024-01-21,2024,1,21,sunday +2024-01-22,2024,1,22,monday +2024-01-24,2024,1,24,wednesday +2024-01-25,2024,1,25,thursday +2024-01-26,2024,1,26,friday +2024-01-27,2024,1,27,saturday +2024-01-28,2024,1,28,sunday +2024-01-31,2024,1,31,wednesday +2024-02-02,2024,2,2,friday +2024-02-04,2024,2,4,sunday +2024-02-06,2024,2,6,tuesday +2024-02-07,2024,2,7,wednesday +2024-02-11,2024,2,11,sunday +2024-02-12,2024,2,12,monday +2024-02-17,2024,2,17,saturday +2024-02-18,2024,2,18,sunday +2024-02-21,2024,2,21,wednesday +2024-02-22,2024,2,22,thursday +2024-02-25,2024,2,25,sunday +2024-02-26,2024,2,26,monday +2024-02-28,2024,2,28,wednesday +2024-03-01,2024,3,1,friday +2024-03-02,2024,3,2,saturday +2024-03-06,2024,3,6,wednesday +2023-01-02,2023,1,2,monday +2023-01-04,2023,1,4,wednesday +2023-01-06,2023,1,6,friday +2023-01-07,2023,1,7,saturday +2023-01-08,2023,1,8,sunday +2023-01-10,2023,1,10,tuesday +2023-01-12,2023,1,12,thursday +2023-01-14,2023,1,14,saturday +2023-01-16,2023,1,16,monday +2023-01-18,2023,1,18,wednesday +2023-01-19,2023,1,19,thursday +2023-01-20,2023,1,20,friday +2023-01-22,2023,1,22,sunday +2023-01-25,2023,1,25,wednesday +2023-01-29,2023,1,29,sunday +2023-01-30,2023,1,30,monday +2023-02-01,2023,2,1,wednesday +2023-02-03,2023,2,3,friday +2023-02-04,2023,2,4,saturday +2023-02-05,2023,2,5,sunday +2023-02-07,2023,2,7,tuesday +2023-02-09,2023,2,9,thursday +2023-02-11,2023,2,11,saturday +2023-02-12,2023,2,12,sunday +2023-02-13,2023,2,13,monday +2023-02-15,2023,2,15,wednesday +2023-02-16,2023,2,16,thursday +2023-02-17,2023,2,17,friday +2023-02-19,2023,2,19,sunday +2023-02-20,2023,2,20,monday +2023-02-22,2023,2,22,wednesday +2023-02-28,2023,2,28,tuesday +2023-03-02,2023,3,2,thursday +2023-03-03,2023,3,3,friday +2023-03-04,2023,3,4,saturday +2023-03-06,2023,3,6,monday +2023-03-08,2023,3,8,wednesday +2023-03-09,2023,3,9,thursday +2023-03-10,2023,3,10,friday +2023-03-13,2023,3,13,monday +2023-03-14,2023,3,14,tuesday +2023-03-15,2023,3,15,wednesday +2023-03-17,2023,3,17,friday +2023-03-19,2023,3,19,sunday +2023-03-22,2023,3,22,wednesday +2023-03-25,2023,3,25,saturday +2023-03-26,2023,3,26,sunday +2023-03-27,2023,3,27,monday +2023-03-29,2023,3,29,wednesday +2023-03-31,2023,3,31,friday +2023-04-01,2023,4,1,saturday +2023-04-03,2023,4,3,monday +2023-04-06,2023,4,6,thursday +2023-04-07,2023,4,7,friday +2023-04-08,2023,4,8,saturday +2023-04-13,2023,4,13,thursday +2023-04-14,2023,4,14,friday +2023-04-15,2023,4,15,saturday +2023-04-17,2023,4,17,monday +2023-04-19,2023,4,19,wednesday +2023-04-23,2023,4,23,sunday +2023-04-24,2023,4,24,monday +2023-04-26,2023,4,26,wednesday +2023-04-28,2023,4,28,friday +2023-04-30,2023,4,30,sunday +2023-05-01,2023,5,1,monday +2023-05-03,2023,5,3,wednesday +2023-05-04,2023,5,4,thursday +2023-05-05,2023,5,5,friday +2023-05-07,2023,5,7,sunday +2023-05-08,2023,5,8,monday +2023-05-10,2023,5,10,wednesday +2023-05-13,2023,5,13,saturday +2023-05-16,2023,5,16,tuesday +2023-05-18,2023,5,18,thursday +2023-05-21,2023,5,21,sunday +2023-05-27,2023,5,27,saturday +2023-05-28,2023,5,28,sunday +2023-05-30,2023,5,30,tuesday +2023-05-31,2023,5,31,wednesday +2023-06-03,2023,6,3,saturday +2023-06-04,2023,6,4,sunday +2023-06-05,2023,6,5,monday +2023-06-06,2023,6,6,tuesday +2023-06-07,2023,6,7,wednesday +2023-06-09,2023,6,9,friday +2023-06-11,2023,6,11,sunday +2023-06-13,2023,6,13,tuesday +2023-06-15,2023,6,15,thursday +2023-06-16,2023,6,16,friday +2023-06-17,2023,6,17,saturday +2023-06-18,2023,6,18,sunday +2023-06-19,2023,6,19,monday +2023-06-20,2023,6,20,tuesday +2023-06-22,2023,6,22,thursday +2023-06-23,2023,6,23,friday +2023-06-28,2023,6,28,wednesday +2023-07-01,2023,7,1,saturday +2023-07-02,2023,7,2,sunday +2023-07-04,2023,7,4,tuesday +2023-07-05,2023,7,5,wednesday +2023-07-07,2023,7,7,friday +2023-07-09,2023,7,9,sunday +2023-07-11,2023,7,11,tuesday +2023-07-13,2023,7,13,thursday +2023-07-18,2023,7,18,tuesday +2023-07-19,2023,7,19,wednesday +2023-07-22,2023,7,22,saturday +2023-07-26,2023,7,26,wednesday +2023-07-27,2023,7,27,thursday +2023-07-28,2023,7,28,friday +2023-07-29,2023,7,29,saturday +2023-08-02,2023,8,2,wednesday +2023-08-03,2023,8,3,thursday +2023-08-05,2023,8,5,saturday +2023-08-06,2023,8,6,sunday +2023-08-07,2023,8,7,monday +2023-08-11,2023,8,11,friday +2023-08-14,2023,8,14,monday +2023-08-15,2023,8,15,tuesday +2023-08-16,2023,8,16,wednesday +2023-08-17,2023,8,17,thursday +2023-08-18,2023,8,18,friday +2023-08-19,2023,8,19,saturday +2023-08-20,2023,8,20,sunday +2023-08-22,2023,8,22,tuesday +2023-08-23,2023,8,23,wednesday +2023-08-24,2023,8,24,thursday +2023-08-25,2023,8,25,friday +2023-08-31,2023,8,31,thursday +2023-09-04,2023,9,4,monday +2023-09-05,2023,9,5,tuesday +2023-09-07,2023,9,7,thursday +2023-09-10,2023,9,10,sunday +2023-09-12,2023,9,12,tuesday +2023-09-16,2023,9,16,saturday +2023-09-18,2023,9,18,monday +2023-09-20,2023,9,20,wednesday +2023-09-21,2023,9,21,thursday +2023-09-24,2023,9,24,sunday +2023-09-29,2023,9,29,friday +2023-10-01,2023,10,1,sunday +2023-10-02,2023,10,2,monday +2023-10-04,2023,10,4,wednesday +2023-10-05,2023,10,5,thursday +2023-10-06,2023,10,6,friday +2023-10-07,2023,10,7,saturday +2023-10-08,2023,10,8,sunday +2023-10-09,2023,10,9,monday +2023-10-10,2023,10,10,tuesday +2023-10-13,2023,10,13,friday +2023-10-17,2023,10,17,tuesday +2023-10-24,2023,10,24,tuesday +2023-10-25,2023,10,25,wednesday +2023-10-26,2023,10,26,thursday +2023-10-29,2023,10,29,sunday +2023-10-30,2023,10,30,monday +2023-11-03,2023,11,3,friday +2023-11-04,2023,11,4,saturday +2023-11-07,2023,11,7,tuesday +2023-11-09,2023,11,9,thursday +2023-11-12,2023,11,12,sunday +2023-11-13,2023,11,13,monday +2023-11-16,2023,11,16,thursday +2023-11-18,2023,11,18,saturday +2023-11-20,2023,11,20,monday +2023-11-23,2023,11,23,thursday +2023-11-25,2023,11,25,saturday +2023-11-26,2023,11,26,sunday +2023-11-27,2023,11,27,monday +2023-11-29,2023,11,29,wednesday +2023-12-05,2023,12,5,tuesday +2023-12-06,2023,12,6,wednesday +2023-12-08,2023,12,8,friday +2023-12-09,2023,12,9,saturday +2023-12-10,2023,12,10,sunday +2023-12-11,2023,12,11,monday +2023-12-13,2023,12,13,wednesday +2023-12-16,2023,12,16,saturday +2023-12-20,2023,12,20,wednesday +2023-12-24,2023,12,24,sunday +2023-12-25,2023,12,25,monday +2023-12-26,2023,12,26,tuesday +2023-12-28,2023,12,28,thursday +2023-12-29,2023,12,29,friday +2023-12-30,2023,12,30,saturday +2024-01-01,2024,1,1,monday +2024-01-04,2024,1,4,thursday +2024-01-05,2024,1,5,friday +2024-01-09,2024,1,9,tuesday +2024-01-10,2024,1,10,wednesday +2024-01-11,2024,1,11,thursday +2024-01-12,2024,1,12,friday +2024-01-14,2024,1,14,sunday +2024-01-16,2024,1,16,tuesday +2024-01-17,2024,1,17,wednesday +2024-01-23,2024,1,23,tuesday +2024-01-29,2024,1,29,monday +2024-01-30,2024,1,30,tuesday +2024-02-01,2024,2,1,thursday +2024-02-03,2024,2,3,saturday +2024-02-05,2024,2,5,monday +2024-02-08,2024,2,8,thursday +2024-02-09,2024,2,9,friday +2024-02-10,2024,2,10,saturday +2024-02-13,2024,2,13,tuesday +2024-02-14,2024,2,14,wednesday +2024-02-15,2024,2,15,thursday +2024-02-16,2024,2,16,friday +2024-02-19,2024,2,19,monday +2024-02-20,2024,2,20,tuesday +2024-02-23,2024,2,23,friday +2024-02-24,2024,2,24,saturday +2024-03-07,2024,3,7,thursday +2024-03-08,2024,3,8,friday +2024-03-09,2024,3,9,saturday +2024-03-10,2024,3,10,sunday +2024-03-11,2024,3,11,monday +2024-03-12,2024,3,12,tuesday +2024-03-13,2024,3,13,wednesday +2024-03-15,2024,3,15,friday +2024-03-17,2024,3,17,sunday +2024-03-18,2024,3,18,monday +2024-03-19,2024,3,19,tuesday +2024-03-22,2024,3,22,friday +2024-03-23,2024,3,23,saturday +2024-03-24,2024,3,24,sunday +2024-03-27,2024,3,27,wednesday +2024-03-28,2024,3,28,thursday +2024-03-29,2024,3,29,friday +2024-03-30,2024,3,30,saturday +2024-03-31,2024,3,31,sunday +2024-04-01,2024,4,1,monday +2024-04-02,2024,4,2,tuesday +2024-04-03,2024,4,3,wednesday +2024-04-04,2024,4,4,thursday +2024-04-06,2024,4,6,saturday +2024-04-07,2024,4,7,sunday +2024-04-09,2024,4,9,tuesday +2024-04-10,2024,4,10,wednesday +2024-04-13,2024,4,13,saturday +2024-04-16,2024,4,16,tuesday +2024-04-17,2024,4,17,wednesday +2024-04-18,2024,4,18,thursday +2024-04-19,2024,4,19,friday +2024-04-20,2024,4,20,saturday +2024-04-23,2024,4,23,tuesday +2024-04-27,2024,4,27,saturday +2024-04-28,2024,4,28,sunday +2024-04-29,2024,4,29,monday +2024-05-02,2024,5,2,thursday +2024-05-03,2024,5,3,friday +2024-05-04,2024,5,4,saturday +2024-05-05,2024,5,5,sunday +2024-05-07,2024,5,7,tuesday +2024-05-15,2024,5,15,wednesday +2024-05-16,2024,5,16,thursday +2024-05-17,2024,5,17,friday +2024-05-20,2024,5,20,monday +2024-05-21,2024,5,21,tuesday +2024-05-23,2024,5,23,thursday +2024-05-28,2024,5,28,tuesday +2024-05-29,2024,5,29,wednesday +2024-06-01,2024,6,1,saturday +2024-06-02,2024,6,2,sunday +2024-06-03,2024,6,3,monday +2024-06-05,2024,6,5,wednesday +2024-06-07,2024,6,7,friday +2024-06-10,2024,6,10,monday +2024-06-12,2024,6,12,wednesday +2024-06-14,2024,6,14,friday +2024-06-15,2024,6,15,saturday +2024-06-16,2024,6,16,sunday +2024-06-18,2024,6,18,tuesday +2024-06-19,2024,6,19,wednesday +2024-06-20,2024,6,20,thursday +2024-06-22,2024,6,22,saturday +2024-06-25,2024,6,25,tuesday +2024-06-26,2024,6,26,wednesday +2024-06-27,2024,6,27,thursday +2024-06-29,2024,6,29,saturday +2024-06-30,2024,6,30,sunday +2024-07-01,2024,7,1,monday +2024-07-02,2024,7,2,tuesday +2024-07-04,2024,7,4,thursday +2024-07-06,2024,7,6,saturday +2024-07-08,2024,7,8,monday +2024-07-09,2024,7,9,tuesday +2024-07-13,2024,7,13,saturday +2024-07-14,2024,7,14,sunday +2024-07-15,2024,7,15,monday +2024-07-18,2024,7,18,thursday +2024-07-21,2024,7,21,sunday +2024-07-23,2024,7,23,tuesday +2024-07-25,2024,7,25,thursday +2024-07-26,2024,7,26,friday +2024-07-28,2024,7,28,sunday +2024-08-01,2024,8,1,thursday +2024-08-03,2024,8,3,saturday +2024-08-06,2024,8,6,tuesday +2024-08-09,2024,8,9,friday +2024-08-10,2024,8,10,saturday +2024-08-11,2024,8,11,sunday +2024-08-12,2024,8,12,monday +2024-08-14,2024,8,14,wednesday +2024-08-20,2024,8,20,tuesday +2024-08-22,2024,8,22,thursday +2024-08-24,2024,8,24,saturday +2024-08-28,2024,8,28,wednesday +2024-08-29,2024,8,29,thursday +2024-08-30,2024,8,30,friday +2024-08-31,2024,8,31,saturday +2024-09-01,2024,9,1,sunday +2024-09-04,2024,9,4,wednesday +2024-09-07,2024,9,7,saturday +2024-09-10,2024,9,10,tuesday +2024-09-11,2024,9,11,wednesday +2024-09-13,2024,9,13,friday +2024-09-16,2024,9,16,monday +2024-09-19,2024,9,19,thursday +2024-09-20,2024,9,20,friday +2024-09-21,2024,9,21,saturday +2024-09-22,2024,9,22,sunday +2024-09-23,2024,9,23,monday +2024-09-24,2024,9,24,tuesday +2024-09-25,2024,9,25,wednesday +2024-09-26,2024,9,26,thursday +2024-09-28,2024,9,28,saturday +2024-09-30,2024,9,30,monday +2024-10-01,2024,10,1,tuesday +2024-10-03,2024,10,3,thursday +2024-10-04,2024,10,4,friday +2024-10-06,2024,10,6,sunday +2024-10-09,2024,10,9,wednesday +2024-10-11,2024,10,11,friday +2024-10-12,2024,10,12,saturday +2024-10-13,2024,10,13,sunday +2024-10-23,2024,10,23,wednesday +2024-10-24,2024,10,24,thursday +2024-10-26,2024,10,26,saturday +2024-10-27,2024,10,27,sunday +2024-10-29,2024,10,29,tuesday +2024-10-30,2024,10,30,wednesday +2024-10-31,2024,10,31,thursday +2024-11-01,2024,11,1,friday +2024-11-02,2024,11,2,saturday +2024-11-04,2024,11,4,monday +2024-11-05,2024,11,5,tuesday +2024-11-06,2024,11,6,wednesday +2024-11-07,2024,11,7,thursday +2024-11-08,2024,11,8,friday +2024-11-12,2024,11,12,tuesday +2024-11-13,2024,11,13,wednesday +2024-11-14,2024,11,14,thursday +2024-11-15,2024,11,15,friday +2024-11-16,2024,11,16,saturday +2024-11-19,2024,11,19,tuesday +2024-11-20,2024,11,20,wednesday +2024-11-21,2024,11,21,thursday +2024-11-22,2024,11,22,friday +2024-11-23,2024,11,23,saturday +2024-11-26,2024,11,26,tuesday +2024-11-29,2024,11,29,friday +2024-12-04,2024,12,4,wednesday +2024-12-07,2024,12,7,saturday +2024-12-12,2024,12,12,thursday +2024-12-15,2024,12,15,sunday +2024-12-21,2024,12,21,saturday +2024-12-22,2024,12,22,sunday +2024-12-25,2024,12,25,wednesday +2024-12-27,2024,12,27,friday +2024-12-28,2024,12,28,saturday +2024-12-30,2024,12,30,monday +2025-01-03,2025,1,3,friday +2025-01-04,2025,1,4,saturday +2025-01-07,2025,1,7,tuesday +2025-01-09,2025,1,9,thursday +2025-01-12,2025,1,12,sunday +2025-01-13,2025,1,13,monday +2025-01-16,2025,1,16,thursday +2025-01-17,2025,1,17,friday +2025-01-18,2025,1,18,saturday +2025-01-20,2025,1,20,monday +2025-01-23,2025,1,23,thursday +2025-01-24,2025,1,24,friday +2025-01-25,2025,1,25,saturday +2025-01-26,2025,1,26,sunday +2025-01-29,2025,1,29,wednesday +2025-01-31,2025,1,31,friday +2025-02-01,2025,2,1,saturday +2025-02-02,2025,2,2,sunday +2025-02-04,2025,2,4,tuesday +2025-02-06,2025,2,6,thursday +2025-02-07,2025,2,7,friday +2025-02-08,2025,2,8,saturday +2025-02-14,2025,2,14,friday +2025-02-15,2025,2,15,saturday +2025-02-16,2025,2,16,sunday +2025-02-17,2025,2,17,monday +2025-02-18,2025,2,18,tuesday +2025-02-20,2025,2,20,thursday +2025-02-22,2025,2,22,saturday +2025-02-23,2025,2,23,sunday +2025-02-26,2025,2,26,wednesday +2025-02-27,2025,2,27,thursday +2025-03-05,2025,3,5,wednesday +2025-03-06,2025,3,6,thursday +2025-03-07,2025,3,7,friday +2025-03-08,2025,3,8,saturday +2025-03-11,2025,3,11,tuesday +2025-03-12,2025,3,12,wednesday +2025-03-13,2025,3,13,thursday +2025-03-16,2025,3,16,sunday +2025-03-18,2025,3,18,tuesday +2025-03-21,2025,3,21,friday +2025-03-22,2025,3,22,saturday +2025-03-23,2025,3,23,sunday +2025-03-25,2025,3,25,tuesday +2025-03-26,2025,3,26,wednesday +2025-03-29,2025,3,29,saturday +2025-04-01,2025,4,1,tuesday +2025-04-02,2025,4,2,wednesday +2025-04-03,2025,4,3,thursday +2025-04-05,2025,4,5,saturday +2025-04-07,2025,4,7,monday +2025-04-12,2025,4,12,saturday +2024-02-27,2024,2,27,tuesday +2024-02-29,2024,2,29,thursday +2024-03-03,2024,3,3,sunday +2024-03-04,2024,3,4,monday +2024-03-05,2024,3,5,tuesday +2024-03-14,2024,3,14,thursday +2024-03-16,2024,3,16,saturday +2024-03-20,2024,3,20,wednesday +2024-03-21,2024,3,21,thursday +2024-03-25,2024,3,25,monday +2024-03-26,2024,3,26,tuesday +2024-04-05,2024,4,5,friday +2024-04-08,2024,4,8,monday +2024-04-11,2024,4,11,thursday +2024-04-12,2024,4,12,friday +2024-04-14,2024,4,14,sunday +2024-04-15,2024,4,15,monday +2024-04-21,2024,4,21,sunday +2024-04-22,2024,4,22,monday +2024-04-24,2024,4,24,wednesday +2024-04-25,2024,4,25,thursday +2024-04-26,2024,4,26,friday +2024-04-30,2024,4,30,tuesday +2024-05-01,2024,5,1,wednesday +2024-05-06,2024,5,6,monday +2024-05-08,2024,5,8,wednesday +2024-05-09,2024,5,9,thursday +2024-05-10,2024,5,10,friday +2024-05-11,2024,5,11,saturday +2024-05-12,2024,5,12,sunday +2024-05-13,2024,5,13,monday +2024-05-14,2024,5,14,tuesday +2024-05-18,2024,5,18,saturday +2024-05-19,2024,5,19,sunday +2024-05-22,2024,5,22,wednesday +2024-05-24,2024,5,24,friday +2024-05-25,2024,5,25,saturday +2024-05-26,2024,5,26,sunday +2024-05-27,2024,5,27,monday +2024-05-30,2024,5,30,thursday +2024-05-31,2024,5,31,friday +2024-06-04,2024,6,4,tuesday +2024-06-06,2024,6,6,thursday +2024-06-08,2024,6,8,saturday +2024-06-09,2024,6,9,sunday +2024-06-11,2024,6,11,tuesday +2024-06-13,2024,6,13,thursday +2024-06-17,2024,6,17,monday +2024-06-21,2024,6,21,friday +2024-06-23,2024,6,23,sunday +2024-06-24,2024,6,24,monday +2024-06-28,2024,6,28,friday +2024-07-03,2024,7,3,wednesday +2024-07-05,2024,7,5,friday +2024-07-07,2024,7,7,sunday +2024-07-10,2024,7,10,wednesday +2024-07-11,2024,7,11,thursday +2024-07-12,2024,7,12,friday +2024-07-16,2024,7,16,tuesday +2024-07-17,2024,7,17,wednesday +2024-07-19,2024,7,19,friday +2024-07-20,2024,7,20,saturday +2024-07-22,2024,7,22,monday +2024-07-24,2024,7,24,wednesday +2024-07-27,2024,7,27,saturday +2024-07-29,2024,7,29,monday +2024-07-30,2024,7,30,tuesday +2024-07-31,2024,7,31,wednesday +2024-08-02,2024,8,2,friday +2024-08-04,2024,8,4,sunday +2024-08-05,2024,8,5,monday +2024-08-07,2024,8,7,wednesday +2024-08-08,2024,8,8,thursday +2024-08-13,2024,8,13,tuesday +2024-08-15,2024,8,15,thursday +2024-08-16,2024,8,16,friday +2024-08-17,2024,8,17,saturday +2024-08-18,2024,8,18,sunday +2024-08-19,2024,8,19,monday +2024-08-21,2024,8,21,wednesday +2024-08-23,2024,8,23,friday +2024-08-25,2024,8,25,sunday +2024-08-26,2024,8,26,monday +2024-08-27,2024,8,27,tuesday +2024-09-02,2024,9,2,monday +2024-09-03,2024,9,3,tuesday +2024-09-05,2024,9,5,thursday +2024-09-06,2024,9,6,friday +2024-09-08,2024,9,8,sunday +2024-09-09,2024,9,9,monday +2024-09-12,2024,9,12,thursday +2024-09-14,2024,9,14,saturday +2024-09-15,2024,9,15,sunday +2024-09-17,2024,9,17,tuesday +2024-09-18,2024,9,18,wednesday +2024-09-27,2024,9,27,friday +2024-09-29,2024,9,29,sunday +2024-10-02,2024,10,2,wednesday +2024-10-05,2024,10,5,saturday +2024-10-07,2024,10,7,monday +2024-10-08,2024,10,8,tuesday +2024-10-10,2024,10,10,thursday +2024-10-14,2024,10,14,monday +2024-10-15,2024,10,15,tuesday +2024-10-16,2024,10,16,wednesday +2024-10-17,2024,10,17,thursday +2024-10-18,2024,10,18,friday +2024-10-19,2024,10,19,saturday +2024-10-20,2024,10,20,sunday +2024-10-21,2024,10,21,monday +2024-10-22,2024,10,22,tuesday +2024-10-25,2024,10,25,friday +2024-10-28,2024,10,28,monday +2024-11-03,2024,11,3,sunday +2024-11-09,2024,11,9,saturday +2024-11-10,2024,11,10,sunday +2024-11-11,2024,11,11,monday +2024-11-17,2024,11,17,sunday +2024-11-18,2024,11,18,monday +2024-11-24,2024,11,24,sunday +2024-11-25,2024,11,25,monday +2024-11-27,2024,11,27,wednesday +2024-11-28,2024,11,28,thursday +2024-11-30,2024,11,30,saturday +2024-12-01,2024,12,1,sunday +2024-12-02,2024,12,2,monday +2024-12-03,2024,12,3,tuesday +2024-12-05,2024,12,5,thursday +2024-12-06,2024,12,6,friday +2024-12-08,2024,12,8,sunday +2024-12-09,2024,12,9,monday +2024-12-10,2024,12,10,tuesday +2024-12-11,2024,12,11,wednesday +2024-12-13,2024,12,13,friday +2024-12-14,2024,12,14,saturday +2024-12-16,2024,12,16,monday +2024-12-17,2024,12,17,tuesday +2024-12-18,2024,12,18,wednesday +2024-12-19,2024,12,19,thursday +2024-12-20,2024,12,20,friday +2024-12-23,2024,12,23,monday +2024-12-24,2024,12,24,tuesday +2024-12-26,2024,12,26,thursday +2024-12-29,2024,12,29,sunday +2024-12-31,2024,12,31,tuesday +2025-01-01,2025,1,1,wednesday +2025-01-02,2025,1,2,thursday +2025-01-05,2025,1,5,sunday +2025-01-06,2025,1,6,monday +2025-01-08,2025,1,8,wednesday +2025-01-10,2025,1,10,friday +2025-01-11,2025,1,11,saturday +2025-01-14,2025,1,14,tuesday +2025-01-15,2025,1,15,wednesday +2025-01-19,2025,1,19,sunday +2025-01-21,2025,1,21,tuesday +2025-01-22,2025,1,22,wednesday +2025-01-27,2025,1,27,monday +2025-01-28,2025,1,28,tuesday +2025-01-30,2025,1,30,thursday +2025-02-03,2025,2,3,monday +2025-02-05,2025,2,5,wednesday +2025-02-09,2025,2,9,sunday +2025-02-10,2025,2,10,monday +2025-02-11,2025,2,11,tuesday +2025-02-12,2025,2,12,wednesday +2025-02-13,2025,2,13,thursday +2025-02-19,2025,2,19,wednesday +2025-02-21,2025,2,21,friday +2025-02-24,2025,2,24,monday +2025-02-25,2025,2,25,tuesday +2025-02-28,2025,2,28,friday +2025-03-01,2025,3,1,saturday +2025-03-02,2025,3,2,sunday +2025-03-03,2025,3,3,monday +2025-03-04,2025,3,4,tuesday +2025-03-09,2025,3,9,sunday +2025-03-10,2025,3,10,monday +2025-03-14,2025,3,14,friday +2025-03-15,2025,3,15,saturday +2025-03-17,2025,3,17,monday +2025-03-19,2025,3,19,wednesday +2025-03-20,2025,3,20,thursday +2025-03-24,2025,3,24,monday +2025-03-27,2025,3,27,thursday +2025-03-28,2025,3,28,friday +2025-03-30,2025,3,30,sunday +2025-03-31,2025,3,31,monday +2025-04-04,2025,4,4,friday +2025-04-06,2025,4,6,sunday +2025-04-08,2025,4,8,tuesday +2025-04-09,2025,4,9,wednesday +2025-04-10,2025,4,10,thursday +2025-04-11,2025,4,11,friday +2025-04-13,2025,4,13,sunday +2025-04-14,2025,4,14,monday +2025-04-15,2025,4,15,tuesday +2025-04-16,2025,4,16,wednesday +2025-04-17,2025,4,17,thursday +2025-04-18,2025,4,18,friday +2025-04-24,2025,4,24,thursday +2025-04-25,2025,4,25,friday +2025-04-28,2025,4,28,monday +2025-04-29,2025,4,29,tuesday +2025-04-30,2025,4,30,wednesday +2025-05-01,2025,5,1,thursday +2025-05-05,2025,5,5,monday +2025-05-07,2025,5,7,wednesday +2025-05-08,2025,5,8,thursday +2025-05-09,2025,5,9,friday +2025-05-10,2025,5,10,saturday +2025-05-11,2025,5,11,sunday +2025-05-14,2025,5,14,wednesday +2025-04-19,2025,4,19,saturday +2025-04-20,2025,4,20,sunday +2025-04-21,2025,4,21,monday +2025-04-22,2025,4,22,tuesday +2025-04-23,2025,4,23,wednesday +2025-04-26,2025,4,26,saturday +2025-04-27,2025,4,27,sunday +2025-05-02,2025,5,2,friday +2025-05-03,2025,5,3,saturday +2025-05-04,2025,5,4,sunday +2025-05-06,2025,5,6,tuesday +2025-05-12,2025,5,12,monday +2025-05-13,2025,5,13,tuesday +2025-05-17,2025,5,17,saturday +2025-05-19,2025,5,19,monday +2025-05-21,2025,5,21,wednesday +2025-05-25,2025,5,25,sunday +2025-05-27,2025,5,27,tuesday +2025-05-30,2025,5,30,friday +2025-06-01,2025,6,1,sunday +2025-06-02,2025,6,2,monday +2025-06-03,2025,6,3,tuesday +2025-06-05,2025,6,5,thursday +2025-06-08,2025,6,8,sunday +2025-06-10,2025,6,10,tuesday +2025-06-11,2025,6,11,wednesday +2025-06-13,2025,6,13,friday +2025-06-14,2025,6,14,saturday +2025-06-17,2025,6,17,tuesday +2025-06-19,2025,6,19,thursday +2025-06-20,2025,6,20,friday +2025-06-22,2025,6,22,sunday +2025-06-23,2025,6,23,monday +2025-06-24,2025,6,24,tuesday +2025-06-28,2025,6,28,saturday +2025-06-29,2025,6,29,sunday +2025-06-30,2025,6,30,monday +2025-07-01,2025,7,1,tuesday +2025-07-02,2025,7,2,wednesday +2025-07-03,2025,7,3,thursday +2025-07-04,2025,7,4,friday +2025-07-05,2025,7,5,saturday +2025-07-09,2025,7,9,wednesday +2025-07-11,2025,7,11,friday +2025-07-12,2025,7,12,saturday +2025-07-13,2025,7,13,sunday +2025-07-14,2025,7,14,monday +2025-07-15,2025,7,15,tuesday +2025-07-17,2025,7,17,thursday +2025-07-19,2025,7,19,saturday +2025-07-20,2025,7,20,sunday +2025-07-22,2025,7,22,tuesday +2025-07-24,2025,7,24,thursday +2025-07-25,2025,7,25,friday +2025-07-28,2025,7,28,monday +2025-07-29,2025,7,29,tuesday +2025-07-30,2025,7,30,wednesday +2025-08-02,2025,8,2,saturday +2025-08-03,2025,8,3,sunday +2025-08-06,2025,8,6,wednesday +2025-08-07,2025,8,7,thursday +2025-08-08,2025,8,8,friday +2025-08-09,2025,8,9,saturday +2025-08-10,2025,8,10,sunday +2025-08-15,2025,8,15,friday +2025-08-22,2025,8,22,friday +2025-08-25,2025,8,25,monday +2025-08-27,2025,8,27,wednesday +2025-08-29,2025,8,29,friday +2025-08-30,2025,8,30,saturday +2025-09-02,2025,9,2,tuesday +2025-09-03,2025,9,3,wednesday +2025-09-04,2025,9,4,thursday +2025-09-06,2025,9,6,saturday +2025-09-11,2025,9,11,thursday +2025-09-14,2025,9,14,sunday +2025-09-15,2025,9,15,monday +2025-09-20,2025,9,20,saturday +2025-09-21,2025,9,21,sunday +2025-09-23,2025,9,23,tuesday +2025-09-30,2025,9,30,tuesday +2025-10-02,2025,10,2,thursday +2025-10-03,2025,10,3,friday +2025-10-04,2025,10,4,saturday +2025-10-06,2025,10,6,monday +2025-10-09,2025,10,9,thursday +2025-10-13,2025,10,13,monday +2025-10-14,2025,10,14,tuesday +2025-10-15,2025,10,15,wednesday +2025-10-19,2025,10,19,sunday +2025-10-20,2025,10,20,monday +2025-10-23,2025,10,23,thursday +2025-10-24,2025,10,24,friday +2025-10-25,2025,10,25,saturday +2025-10-30,2025,10,30,thursday +2025-11-01,2025,11,1,saturday +2025-11-02,2025,11,2,sunday +2025-11-04,2025,11,4,tuesday +2025-11-05,2025,11,5,wednesday +2025-11-06,2025,11,6,thursday +2025-11-08,2025,11,8,saturday +2025-11-12,2025,11,12,wednesday +2025-11-13,2025,11,13,thursday +2025-11-14,2025,11,14,friday +2025-11-16,2025,11,16,sunday +2025-11-17,2025,11,17,monday +2025-11-18,2025,11,18,tuesday +2025-11-21,2025,11,21,friday +2025-11-22,2025,11,22,saturday +2025-11-26,2025,11,26,wednesday +2025-11-27,2025,11,27,thursday +2025-11-30,2025,11,30,sunday +2025-12-01,2025,12,1,monday +2025-12-02,2025,12,2,tuesday +2025-12-04,2025,12,4,thursday +2025-12-08,2025,12,8,monday +2025-12-11,2025,12,11,thursday +2025-12-12,2025,12,12,friday +2025-12-13,2025,12,13,saturday +2025-12-18,2025,12,18,thursday +2025-12-21,2025,12,21,sunday +2025-12-22,2025,12,22,monday +2025-12-23,2025,12,23,tuesday +2025-12-25,2025,12,25,thursday +2025-12-27,2025,12,27,saturday +2025-12-28,2025,12,28,sunday +2025-12-29,2025,12,29,monday +2026-01-04,2026,1,4,sunday +2026-01-06,2026,1,6,tuesday +2026-01-08,2026,1,8,thursday +2026-01-10,2026,1,10,saturday +2026-01-11,2026,1,11,sunday +2026-01-14,2026,1,14,wednesday +2026-01-15,2026,1,15,thursday +2026-01-17,2026,1,17,saturday +2026-01-19,2026,1,19,monday +2026-01-20,2026,1,20,tuesday +2026-01-21,2026,1,21,wednesday +2026-01-23,2026,1,23,friday +2026-01-24,2026,1,24,saturday +2026-01-25,2026,1,25,sunday +2026-01-26,2026,1,26,monday +2026-01-28,2026,1,28,wednesday +2026-01-29,2026,1,29,thursday +2026-01-30,2026,1,30,friday +2026-01-31,2026,1,31,saturday +2026-02-03,2026,2,3,tuesday +2026-02-04,2026,2,4,wednesday +2026-02-05,2026,2,5,thursday +2026-02-06,2026,2,6,friday +2026-02-07,2026,2,7,saturday +2026-02-09,2026,2,9,monday +2026-02-10,2026,2,10,tuesday +2026-02-12,2026,2,12,thursday +2026-02-13,2026,2,13,friday +2026-02-14,2026,2,14,saturday +2026-02-15,2026,2,15,sunday +2026-02-18,2026,2,18,wednesday +2026-02-20,2026,2,20,friday +2026-02-22,2026,2,22,sunday +2026-02-23,2026,2,23,monday +2026-02-24,2026,2,24,tuesday +2026-02-25,2026,2,25,wednesday +2026-02-26,2026,2,26,thursday +2026-03-02,2026,3,2,monday +2026-03-03,2026,3,3,tuesday +2026-03-06,2026,3,6,friday +2026-03-08,2026,3,8,sunday +2026-03-09,2026,3,9,monday +2026-03-10,2026,3,10,tuesday +2026-03-11,2026,3,11,wednesday +2026-03-12,2026,3,12,thursday +2026-03-14,2026,3,14,saturday +2026-03-17,2026,3,17,tuesday +2026-03-20,2026,3,20,friday +2026-03-24,2026,3,24,tuesday +2026-03-27,2026,3,27,friday +2026-03-29,2026,3,29,sunday +2026-04-02,2026,4,2,thursday +2026-04-03,2026,4,3,friday +2026-04-04,2026,4,4,saturday +2026-04-06,2026,4,6,monday +2026-04-09,2026,4,9,thursday +2026-04-11,2026,4,11,saturday +2026-04-13,2026,4,13,monday +2026-04-14,2026,4,14,tuesday +2026-04-20,2026,4,20,monday +2026-04-21,2026,4,21,tuesday +2026-04-24,2026,4,24,friday +2026-04-25,2026,4,25,saturday +2026-04-26,2026,4,26,sunday +2026-04-28,2026,4,28,tuesday +2026-04-30,2026,4,30,thursday +2026-05-01,2026,5,1,friday +2026-05-02,2026,5,2,saturday +2026-05-05,2026,5,5,tuesday +2026-05-06,2026,5,6,wednesday +2026-05-07,2026,5,7,thursday +2026-05-09,2026,5,9,saturday +2026-05-10,2026,5,10,sunday +2026-05-11,2026,5,11,monday +2026-05-12,2026,5,12,tuesday +2026-05-13,2026,5,13,wednesday +2026-05-15,2026,5,15,friday +2026-05-16,2026,5,16,saturday +2026-05-19,2026,5,19,tuesday +2026-05-20,2026,5,20,wednesday +2026-05-25,2026,5,25,monday +2026-05-27,2026,5,27,wednesday +2026-05-28,2026,5,28,thursday +2026-05-30,2026,5,30,saturday +2026-06-01,2026,6,1,monday +2026-06-03,2026,6,3,wednesday +2025-05-15,2025,5,15,thursday +2025-05-16,2025,5,16,friday +2025-05-18,2025,5,18,sunday +2025-05-20,2025,5,20,tuesday +2025-05-22,2025,5,22,thursday +2025-05-23,2025,5,23,friday +2025-05-24,2025,5,24,saturday +2025-05-26,2025,5,26,monday +2025-05-28,2025,5,28,wednesday +2025-05-29,2025,5,29,thursday +2025-05-31,2025,5,31,saturday +2025-06-04,2025,6,4,wednesday +2025-06-06,2025,6,6,friday +2025-06-07,2025,6,7,saturday +2025-06-09,2025,6,9,monday +2025-06-12,2025,6,12,thursday +2025-06-15,2025,6,15,sunday +2025-06-16,2025,6,16,monday +2025-06-18,2025,6,18,wednesday +2025-06-21,2025,6,21,saturday +2025-06-25,2025,6,25,wednesday +2025-06-26,2025,6,26,thursday +2025-06-27,2025,6,27,friday +2025-07-06,2025,7,6,sunday +2025-07-07,2025,7,7,monday +2025-07-08,2025,7,8,tuesday +2025-07-10,2025,7,10,thursday +2025-07-16,2025,7,16,wednesday +2025-07-18,2025,7,18,friday +2025-07-21,2025,7,21,monday +2025-07-23,2025,7,23,wednesday +2025-07-26,2025,7,26,saturday +2025-07-27,2025,7,27,sunday +2025-07-31,2025,7,31,thursday +2025-08-01,2025,8,1,friday +2025-08-04,2025,8,4,monday +2025-08-05,2025,8,5,tuesday +2025-08-11,2025,8,11,monday +2025-08-12,2025,8,12,tuesday +2025-08-13,2025,8,13,wednesday +2025-08-14,2025,8,14,thursday +2025-08-16,2025,8,16,saturday +2025-08-17,2025,8,17,sunday +2025-08-18,2025,8,18,monday +2025-08-19,2025,8,19,tuesday +2025-08-20,2025,8,20,wednesday +2025-08-21,2025,8,21,thursday +2025-08-23,2025,8,23,saturday +2025-08-24,2025,8,24,sunday +2025-08-26,2025,8,26,tuesday +2025-08-28,2025,8,28,thursday +2025-08-31,2025,8,31,sunday +2025-09-01,2025,9,1,monday +2025-09-05,2025,9,5,friday +2025-09-07,2025,9,7,sunday +2025-09-08,2025,9,8,monday +2025-09-09,2025,9,9,tuesday +2025-09-10,2025,9,10,wednesday +2025-09-12,2025,9,12,friday +2025-09-13,2025,9,13,saturday +2025-09-16,2025,9,16,tuesday +2025-09-17,2025,9,17,wednesday +2025-09-18,2025,9,18,thursday +2025-09-19,2025,9,19,friday +2025-09-22,2025,9,22,monday +2025-09-24,2025,9,24,wednesday +2025-09-25,2025,9,25,thursday +2025-09-26,2025,9,26,friday +2025-09-27,2025,9,27,saturday +2025-09-28,2025,9,28,sunday +2025-09-29,2025,9,29,monday +2025-10-01,2025,10,1,wednesday +2025-10-05,2025,10,5,sunday +2025-10-07,2025,10,7,tuesday +2025-10-08,2025,10,8,wednesday +2025-10-10,2025,10,10,friday +2025-10-11,2025,10,11,saturday +2025-10-12,2025,10,12,sunday +2025-10-16,2025,10,16,thursday +2025-10-17,2025,10,17,friday +2025-10-18,2025,10,18,saturday +2025-10-21,2025,10,21,tuesday +2025-10-22,2025,10,22,wednesday +2025-10-26,2025,10,26,sunday +2025-10-27,2025,10,27,monday +2025-10-28,2025,10,28,tuesday +2025-10-29,2025,10,29,wednesday +2025-10-31,2025,10,31,friday +2025-11-03,2025,11,3,monday +2025-11-07,2025,11,7,friday +2025-11-09,2025,11,9,sunday +2025-11-10,2025,11,10,monday +2025-11-11,2025,11,11,tuesday +2025-11-15,2025,11,15,saturday +2025-11-19,2025,11,19,wednesday +2025-11-20,2025,11,20,thursday +2025-11-23,2025,11,23,sunday +2025-11-24,2025,11,24,monday +2025-11-25,2025,11,25,tuesday +2025-11-28,2025,11,28,friday +2025-11-29,2025,11,29,saturday +2025-12-03,2025,12,3,wednesday +2025-12-05,2025,12,5,friday +2025-12-06,2025,12,6,saturday +2025-12-07,2025,12,7,sunday +2025-12-09,2025,12,9,tuesday +2025-12-10,2025,12,10,wednesday +2025-12-14,2025,12,14,sunday +2025-12-15,2025,12,15,monday +2025-12-16,2025,12,16,tuesday +2025-12-17,2025,12,17,wednesday +2025-12-19,2025,12,19,friday +2025-12-20,2025,12,20,saturday +2025-12-24,2025,12,24,wednesday +2025-12-26,2025,12,26,friday +2025-12-30,2025,12,30,tuesday +2025-12-31,2025,12,31,wednesday +2026-01-01,2026,1,1,thursday +2026-01-02,2026,1,2,friday +2026-01-03,2026,1,3,saturday +2026-01-05,2026,1,5,monday +2026-01-07,2026,1,7,wednesday +2026-01-09,2026,1,9,friday +2026-01-12,2026,1,12,monday +2026-01-13,2026,1,13,tuesday +2026-01-16,2026,1,16,friday +2026-01-18,2026,1,18,sunday +2026-01-22,2026,1,22,thursday +2026-01-27,2026,1,27,tuesday +2026-02-01,2026,2,1,sunday +2026-02-02,2026,2,2,monday +2026-02-08,2026,2,8,sunday +2026-02-11,2026,2,11,wednesday +2026-02-16,2026,2,16,monday +2026-02-17,2026,2,17,tuesday +2026-02-19,2026,2,19,thursday +2026-02-21,2026,2,21,saturday +2026-02-27,2026,2,27,friday +2026-02-28,2026,2,28,saturday +2026-03-01,2026,3,1,sunday +2026-03-04,2026,3,4,wednesday +2026-03-05,2026,3,5,thursday +2026-03-07,2026,3,7,saturday +2026-03-13,2026,3,13,friday +2026-03-15,2026,3,15,sunday +2026-03-16,2026,3,16,monday +2026-03-18,2026,3,18,wednesday +2026-03-19,2026,3,19,thursday +2026-03-21,2026,3,21,saturday +2026-03-22,2026,3,22,sunday +2026-03-23,2026,3,23,monday +2026-03-25,2026,3,25,wednesday +2026-03-26,2026,3,26,thursday +2026-03-28,2026,3,28,saturday +2026-03-30,2026,3,30,monday +2026-03-31,2026,3,31,tuesday +2026-04-01,2026,4,1,wednesday +2026-04-05,2026,4,5,sunday +2026-04-07,2026,4,7,tuesday +2026-04-08,2026,4,8,wednesday +2026-04-10,2026,4,10,friday +2026-04-12,2026,4,12,sunday +2026-04-15,2026,4,15,wednesday +2026-04-16,2026,4,16,thursday +2026-04-17,2026,4,17,friday +2026-04-18,2026,4,18,saturday +2026-04-19,2026,4,19,sunday +2026-04-22,2026,4,22,wednesday +2026-04-23,2026,4,23,thursday +2026-04-27,2026,4,27,monday +2026-04-29,2026,4,29,wednesday +2026-05-03,2026,5,3,sunday +2026-05-04,2026,5,4,monday +2026-05-08,2026,5,8,friday +2026-05-14,2026,5,14,thursday +2026-05-17,2026,5,17,sunday +2026-05-18,2026,5,18,monday +2026-05-21,2026,5,21,thursday +2026-05-22,2026,5,22,friday +2026-05-23,2026,5,23,saturday +2026-05-24,2026,5,24,sunday +2026-05-26,2026,5,26,tuesday +2026-05-29,2026,5,29,friday +2026-05-31,2026,5,31,sunday +2026-06-02,2026,6,2,tuesday +2026-06-06,2026,6,6,saturday +2026-06-07,2026,6,7,sunday +2026-06-08,2026,6,8,monday +2026-06-09,2026,6,9,tuesday +2026-06-10,2026,6,10,wednesday +2026-06-11,2026,6,11,thursday +2026-06-12,2026,6,12,friday +2026-06-14,2026,6,14,sunday +2026-06-16,2026,6,16,tuesday +2026-06-17,2026,6,17,wednesday +2026-06-19,2026,6,19,friday +2026-06-21,2026,6,21,sunday +2026-06-22,2026,6,22,monday +2026-06-23,2026,6,23,tuesday +2026-06-25,2026,6,25,thursday +2026-06-26,2026,6,26,friday +2026-06-27,2026,6,27,saturday +2026-07-02,2026,7,2,thursday +2026-07-04,2026,7,4,saturday +2026-07-05,2026,7,5,sunday +2026-07-08,2026,7,8,wednesday +2026-07-09,2026,7,9,thursday +2026-07-10,2026,7,10,friday +2026-07-12,2026,7,12,sunday +2026-07-17,2026,7,17,friday +2026-07-18,2026,7,18,saturday +2026-07-21,2026,7,21,tuesday +2026-07-23,2026,7,23,thursday +2026-06-04,2026,6,4,thursday +2026-06-05,2026,6,5,friday +2026-06-13,2026,6,13,saturday +2026-06-15,2026,6,15,monday +2026-06-18,2026,6,18,thursday +2026-06-20,2026,6,20,saturday +2026-06-24,2026,6,24,wednesday +2026-06-28,2026,6,28,sunday +2026-06-29,2026,6,29,monday +2026-06-30,2026,6,30,tuesday +2026-07-01,2026,7,1,wednesday +2026-07-03,2026,7,3,friday +2026-07-06,2026,7,6,monday +2026-07-07,2026,7,7,tuesday +2026-07-11,2026,7,11,saturday +2026-07-13,2026,7,13,monday +2026-07-14,2026,7,14,tuesday +2026-07-15,2026,7,15,wednesday +2026-07-16,2026,7,16,thursday +2026-07-19,2026,7,19,sunday +2026-07-20,2026,7,20,monday +2026-07-22,2026,7,22,wednesday +2026-07-25,2026,7,25,saturday +2026-07-27,2026,7,27,monday +2026-08-01,2026,8,1,saturday +2026-08-03,2026,8,3,monday +2026-08-05,2026,8,5,wednesday +2026-08-09,2026,8,9,sunday +2026-08-10,2026,8,10,monday +2026-08-14,2026,8,14,friday +2026-08-15,2026,8,15,saturday +2026-08-16,2026,8,16,sunday +2026-08-17,2026,8,17,monday +2026-08-20,2026,8,20,thursday +2026-08-21,2026,8,21,friday +2026-08-22,2026,8,22,saturday +2026-08-25,2026,8,25,tuesday +2026-08-26,2026,8,26,wednesday +2026-08-27,2026,8,27,thursday +2026-08-29,2026,8,29,saturday +2026-08-31,2026,8,31,monday +2026-09-01,2026,9,1,tuesday +2026-09-03,2026,9,3,thursday +2026-09-04,2026,9,4,friday +2026-09-08,2026,9,8,tuesday +2026-09-12,2026,9,12,saturday +2026-09-14,2026,9,14,monday +2026-09-16,2026,9,16,wednesday +2026-09-20,2026,9,20,sunday +2026-09-21,2026,9,21,monday +2026-09-22,2026,9,22,tuesday +2026-09-25,2026,9,25,friday +2026-09-26,2026,9,26,saturday +2026-09-28,2026,9,28,monday +2026-09-29,2026,9,29,tuesday +2026-09-30,2026,9,30,wednesday +2026-10-02,2026,10,2,friday +2026-10-03,2026,10,3,saturday +2026-10-04,2026,10,4,sunday +2026-10-05,2026,10,5,monday +2026-10-06,2026,10,6,tuesday +2026-10-07,2026,10,7,wednesday +2026-10-08,2026,10,8,thursday +2026-10-12,2026,10,12,monday +2026-10-15,2026,10,15,thursday +2026-10-18,2026,10,18,sunday +2026-10-20,2026,10,20,tuesday +2026-10-21,2026,10,21,wednesday +2026-10-23,2026,10,23,friday +2026-10-25,2026,10,25,sunday +2026-10-26,2026,10,26,monday +2026-10-27,2026,10,27,tuesday +2026-10-28,2026,10,28,wednesday +2026-10-29,2026,10,29,thursday +2026-10-30,2026,10,30,friday +2026-10-31,2026,10,31,saturday +2026-11-02,2026,11,2,monday +2026-11-04,2026,11,4,wednesday +2026-11-05,2026,11,5,thursday +2026-11-07,2026,11,7,saturday +2026-11-11,2026,11,11,wednesday +2026-11-14,2026,11,14,saturday +2026-11-15,2026,11,15,sunday +2026-11-19,2026,11,19,thursday +2026-11-22,2026,11,22,sunday +2026-11-30,2026,11,30,monday +2026-12-02,2026,12,2,wednesday +2026-12-03,2026,12,3,thursday +2026-12-04,2026,12,4,friday +2026-12-05,2026,12,5,saturday +2026-12-06,2026,12,6,sunday +2026-12-07,2026,12,7,monday +2026-12-09,2026,12,9,wednesday +2026-12-12,2026,12,12,saturday +2026-12-15,2026,12,15,tuesday +2026-12-17,2026,12,17,thursday +2026-12-20,2026,12,20,sunday +2026-12-22,2026,12,22,tuesday +2026-12-24,2026,12,24,thursday +2026-12-25,2026,12,25,friday +2026-12-26,2026,12,26,saturday +2026-12-27,2026,12,27,sunday +2026-12-28,2026,12,28,monday +2026-12-29,2026,12,29,tuesday +2027-01-03,2027,1,3,sunday +2027-01-04,2027,1,4,monday +2027-01-07,2027,1,7,thursday +2027-01-08,2027,1,8,friday +2027-01-10,2027,1,10,sunday +2027-01-11,2027,1,11,monday +2027-01-17,2027,1,17,sunday +2027-01-18,2027,1,18,monday +2027-01-20,2027,1,20,wednesday +2027-01-22,2027,1,22,friday +2027-01-24,2027,1,24,sunday +2027-01-25,2027,1,25,monday +2027-01-26,2027,1,26,tuesday +2027-01-27,2027,1,27,wednesday +2027-01-30,2027,1,30,saturday +2027-01-31,2027,1,31,sunday +2027-02-01,2027,2,1,monday +2027-02-03,2027,2,3,wednesday +2027-02-05,2027,2,5,friday +2027-02-09,2027,2,9,tuesday +2027-02-10,2027,2,10,wednesday +2027-02-15,2027,2,15,monday +2027-02-18,2027,2,18,thursday +2027-02-20,2027,2,20,saturday +2027-02-23,2027,2,23,tuesday +2027-02-26,2027,2,26,friday +2027-03-01,2027,3,1,monday +2027-03-03,2027,3,3,wednesday +2027-03-04,2027,3,4,thursday +2027-03-06,2027,3,6,saturday +2027-03-09,2027,3,9,tuesday +2027-03-11,2027,3,11,thursday +2027-03-13,2027,3,13,saturday +2027-03-14,2027,3,14,sunday +2027-03-15,2027,3,15,monday +2027-03-17,2027,3,17,wednesday +2027-03-19,2027,3,19,friday +2027-03-20,2027,3,20,saturday +2027-03-21,2027,3,21,sunday +2027-03-25,2027,3,25,thursday +2027-03-26,2027,3,26,friday +2027-03-27,2027,3,27,saturday +2027-03-28,2027,3,28,sunday +2027-04-03,2027,4,3,saturday +2027-04-04,2027,4,4,sunday +2027-04-05,2027,4,5,monday +2027-04-08,2027,4,8,thursday +2027-04-11,2027,4,11,sunday +2027-04-12,2027,4,12,monday +2027-04-16,2027,4,16,friday +2027-04-18,2027,4,18,sunday +2027-04-19,2027,4,19,monday +2027-04-20,2027,4,20,tuesday +2027-04-28,2027,4,28,wednesday +2027-05-02,2027,5,2,sunday +2027-05-05,2027,5,5,wednesday +2027-05-06,2027,5,6,thursday +2027-05-07,2027,5,7,friday +2027-05-08,2027,5,8,saturday +2027-05-12,2027,5,12,wednesday +2027-05-15,2027,5,15,saturday +2027-05-16,2027,5,16,sunday +2027-05-17,2027,5,17,monday +2027-05-20,2027,5,20,thursday +2027-05-21,2027,5,21,friday +2027-05-22,2027,5,22,saturday +2027-05-23,2027,5,23,sunday +2027-05-24,2027,5,24,monday +2027-05-25,2027,5,25,tuesday +2027-05-27,2027,5,27,thursday +2027-05-28,2027,5,28,friday +2027-05-29,2027,5,29,saturday +2027-05-30,2027,5,30,sunday +2027-05-31,2027,5,31,monday +2027-06-01,2027,6,1,tuesday +2027-06-04,2027,6,4,friday +2027-06-05,2027,6,5,saturday +2027-06-09,2027,6,9,wednesday +2027-06-10,2027,6,10,thursday +2027-06-12,2027,6,12,saturday +2027-06-13,2027,6,13,sunday +2027-06-14,2027,6,14,monday +2027-06-15,2027,6,15,tuesday +2027-06-16,2027,6,16,wednesday +2027-06-17,2027,6,17,thursday +2027-06-19,2027,6,19,saturday +2027-06-21,2027,6,21,monday +2027-06-23,2027,6,23,wednesday +2027-06-24,2027,6,24,thursday +2027-06-26,2027,6,26,saturday +2027-06-27,2027,6,27,sunday +2027-06-28,2027,6,28,monday +2027-06-29,2027,6,29,tuesday +2027-07-01,2027,7,1,thursday +2027-07-02,2027,7,2,friday +2027-07-04,2027,7,4,sunday +2027-07-05,2027,7,5,monday +2027-07-07,2027,7,7,wednesday +2027-07-10,2027,7,10,saturday +2027-07-12,2027,7,12,monday +2027-07-14,2027,7,14,wednesday +2027-07-16,2027,7,16,friday +2027-07-17,2027,7,17,saturday +2027-07-19,2027,7,19,monday +2027-07-20,2027,7,20,tuesday +2027-07-22,2027,7,22,thursday +2027-07-23,2027,7,23,friday +2027-07-26,2027,7,26,monday +2027-07-27,2027,7,27,tuesday +2026-07-24,2026,7,24,friday +2026-07-26,2026,7,26,sunday +2026-07-28,2026,7,28,tuesday +2026-07-29,2026,7,29,wednesday +2026-07-30,2026,7,30,thursday +2026-07-31,2026,7,31,friday +2026-08-02,2026,8,2,sunday +2026-08-04,2026,8,4,tuesday +2026-08-06,2026,8,6,thursday +2026-08-07,2026,8,7,friday +2026-08-08,2026,8,8,saturday +2026-08-11,2026,8,11,tuesday +2026-08-12,2026,8,12,wednesday +2026-08-13,2026,8,13,thursday +2026-08-18,2026,8,18,tuesday +2026-08-19,2026,8,19,wednesday +2026-08-23,2026,8,23,sunday +2026-08-24,2026,8,24,monday +2026-08-28,2026,8,28,friday +2026-08-30,2026,8,30,sunday +2026-09-02,2026,9,2,wednesday +2026-09-05,2026,9,5,saturday +2026-09-06,2026,9,6,sunday +2026-09-07,2026,9,7,monday +2026-09-09,2026,9,9,wednesday +2026-09-10,2026,9,10,thursday +2026-09-11,2026,9,11,friday +2026-09-13,2026,9,13,sunday +2026-09-15,2026,9,15,tuesday +2026-09-17,2026,9,17,thursday +2026-09-18,2026,9,18,friday +2026-09-19,2026,9,19,saturday +2026-09-23,2026,9,23,wednesday +2026-09-24,2026,9,24,thursday +2026-09-27,2026,9,27,sunday +2026-10-01,2026,10,1,thursday +2026-10-09,2026,10,9,friday +2026-10-10,2026,10,10,saturday +2026-10-11,2026,10,11,sunday +2026-10-13,2026,10,13,tuesday +2026-10-14,2026,10,14,wednesday +2026-10-16,2026,10,16,friday +2026-10-17,2026,10,17,saturday +2026-10-19,2026,10,19,monday +2026-10-22,2026,10,22,thursday +2026-10-24,2026,10,24,saturday +2026-11-01,2026,11,1,sunday +2026-11-03,2026,11,3,tuesday +2026-11-06,2026,11,6,friday +2026-11-08,2026,11,8,sunday +2026-11-09,2026,11,9,monday +2026-11-10,2026,11,10,tuesday +2026-11-12,2026,11,12,thursday +2026-11-13,2026,11,13,friday +2026-11-16,2026,11,16,monday +2026-11-17,2026,11,17,tuesday +2026-11-18,2026,11,18,wednesday +2026-11-20,2026,11,20,friday +2026-11-21,2026,11,21,saturday +2026-11-23,2026,11,23,monday +2026-11-24,2026,11,24,tuesday +2026-11-25,2026,11,25,wednesday +2026-11-26,2026,11,26,thursday +2026-11-27,2026,11,27,friday +2026-11-28,2026,11,28,saturday +2026-11-29,2026,11,29,sunday +2026-12-01,2026,12,1,tuesday +2026-12-08,2026,12,8,tuesday +2026-12-10,2026,12,10,thursday +2026-12-11,2026,12,11,friday +2026-12-13,2026,12,13,sunday +2026-12-14,2026,12,14,monday +2026-12-16,2026,12,16,wednesday +2026-12-18,2026,12,18,friday +2026-12-19,2026,12,19,saturday +2026-12-21,2026,12,21,monday +2026-12-23,2026,12,23,wednesday +2026-12-30,2026,12,30,wednesday +2026-12-31,2026,12,31,thursday +2027-01-01,2027,1,1,friday +2027-01-02,2027,1,2,saturday +2027-01-05,2027,1,5,tuesday +2027-01-06,2027,1,6,wednesday +2027-01-09,2027,1,9,saturday +2027-01-12,2027,1,12,tuesday +2027-01-13,2027,1,13,wednesday +2027-01-14,2027,1,14,thursday +2027-01-15,2027,1,15,friday +2027-01-16,2027,1,16,saturday +2027-01-19,2027,1,19,tuesday +2027-01-21,2027,1,21,thursday +2027-01-23,2027,1,23,saturday +2027-01-28,2027,1,28,thursday +2027-01-29,2027,1,29,friday +2027-02-02,2027,2,2,tuesday +2027-02-04,2027,2,4,thursday +2027-02-06,2027,2,6,saturday +2027-02-07,2027,2,7,sunday +2027-02-08,2027,2,8,monday +2027-02-11,2027,2,11,thursday +2027-02-12,2027,2,12,friday +2027-02-13,2027,2,13,saturday +2027-02-14,2027,2,14,sunday +2027-02-16,2027,2,16,tuesday +2027-02-17,2027,2,17,wednesday +2027-02-19,2027,2,19,friday +2027-02-21,2027,2,21,sunday +2027-02-22,2027,2,22,monday +2027-02-24,2027,2,24,wednesday +2027-02-25,2027,2,25,thursday +2027-02-27,2027,2,27,saturday +2027-02-28,2027,2,28,sunday +2027-03-02,2027,3,2,tuesday +2027-03-05,2027,3,5,friday +2027-03-07,2027,3,7,sunday +2027-03-08,2027,3,8,monday +2027-03-10,2027,3,10,wednesday +2027-03-12,2027,3,12,friday +2027-03-16,2027,3,16,tuesday +2027-03-18,2027,3,18,thursday +2027-03-22,2027,3,22,monday +2027-03-23,2027,3,23,tuesday +2027-03-24,2027,3,24,wednesday +2027-03-29,2027,3,29,monday +2027-03-30,2027,3,30,tuesday +2027-03-31,2027,3,31,wednesday +2027-04-01,2027,4,1,thursday +2027-04-02,2027,4,2,friday +2027-04-06,2027,4,6,tuesday +2027-04-07,2027,4,7,wednesday +2027-04-09,2027,4,9,friday +2027-04-10,2027,4,10,saturday +2027-04-13,2027,4,13,tuesday +2027-04-14,2027,4,14,wednesday +2027-04-15,2027,4,15,thursday +2027-04-17,2027,4,17,saturday +2027-04-21,2027,4,21,wednesday +2027-04-22,2027,4,22,thursday +2027-04-23,2027,4,23,friday +2027-04-24,2027,4,24,saturday +2027-04-25,2027,4,25,sunday +2027-04-26,2027,4,26,monday +2027-04-27,2027,4,27,tuesday +2027-04-29,2027,4,29,thursday +2027-04-30,2027,4,30,friday +2027-05-01,2027,5,1,saturday +2027-05-03,2027,5,3,monday +2027-05-04,2027,5,4,tuesday +2027-05-09,2027,5,9,sunday +2027-05-10,2027,5,10,monday +2027-05-11,2027,5,11,tuesday +2027-05-13,2027,5,13,thursday +2027-05-14,2027,5,14,friday +2027-05-18,2027,5,18,tuesday +2027-05-19,2027,5,19,wednesday +2027-05-26,2027,5,26,wednesday +2027-06-02,2027,6,2,wednesday +2027-06-03,2027,6,3,thursday +2027-06-06,2027,6,6,sunday +2027-06-07,2027,6,7,monday +2027-06-08,2027,6,8,tuesday +2027-06-11,2027,6,11,friday +2027-06-18,2027,6,18,friday +2027-06-20,2027,6,20,sunday +2027-06-22,2027,6,22,tuesday +2027-06-25,2027,6,25,friday +2027-06-30,2027,6,30,wednesday +2027-07-03,2027,7,3,saturday +2027-07-06,2027,7,6,tuesday +2027-07-08,2027,7,8,thursday +2027-07-09,2027,7,9,friday +2027-07-11,2027,7,11,sunday +2027-07-13,2027,7,13,tuesday +2027-07-15,2027,7,15,thursday +2027-07-18,2027,7,18,sunday +2027-07-21,2027,7,21,wednesday +2027-07-24,2027,7,24,saturday +2027-07-25,2027,7,25,sunday +2027-07-28,2027,7,28,wednesday +2027-07-30,2027,7,30,friday +2027-08-02,2027,8,2,monday +2027-08-05,2027,8,5,thursday +2027-08-06,2027,8,6,friday +2027-08-09,2027,8,9,monday +2027-08-10,2027,8,10,tuesday +2027-08-12,2027,8,12,thursday +2027-08-14,2027,8,14,saturday +2027-08-16,2027,8,16,monday +2027-08-17,2027,8,17,tuesday +2027-08-18,2027,8,18,wednesday +2027-08-20,2027,8,20,friday +2027-08-21,2027,8,21,saturday +2027-08-23,2027,8,23,monday +2027-08-24,2027,8,24,tuesday +2027-08-26,2027,8,26,thursday +2027-08-27,2027,8,27,friday +2027-08-29,2027,8,29,sunday +2027-08-30,2027,8,30,monday +2027-09-01,2027,9,1,wednesday +2027-09-03,2027,9,3,friday +2027-09-05,2027,9,5,sunday +2027-09-07,2027,9,7,tuesday +2027-09-08,2027,9,8,wednesday +2027-09-09,2027,9,9,thursday +2027-09-13,2027,9,13,monday +2027-09-14,2027,9,14,tuesday +2027-09-16,2027,9,16,thursday +2027-09-17,2027,9,17,friday +2027-09-18,2027,9,18,saturday +2027-09-19,2027,9,19,sunday +2027-09-21,2027,9,21,tuesday +2027-09-22,2027,9,22,wednesday +2027-09-23,2027,9,23,thursday +2027-07-29,2027,7,29,thursday +2027-07-31,2027,7,31,saturday +2027-08-01,2027,8,1,sunday +2027-08-03,2027,8,3,tuesday +2027-08-04,2027,8,4,wednesday +2027-08-07,2027,8,7,saturday +2027-08-08,2027,8,8,sunday +2027-08-11,2027,8,11,wednesday +2027-08-13,2027,8,13,friday +2027-08-15,2027,8,15,sunday +2027-08-19,2027,8,19,thursday +2027-08-22,2027,8,22,sunday +2027-08-25,2027,8,25,wednesday +2027-08-28,2027,8,28,saturday +2027-08-31,2027,8,31,tuesday +2027-09-02,2027,9,2,thursday +2027-09-04,2027,9,4,saturday +2027-09-06,2027,9,6,monday +2027-09-10,2027,9,10,friday +2027-09-11,2027,9,11,saturday +2027-09-12,2027,9,12,sunday +2027-09-15,2027,9,15,wednesday +2027-09-20,2027,9,20,monday +2027-09-24,2027,9,24,friday +2027-09-29,2027,9,29,wednesday +2027-10-07,2027,10,7,thursday +2027-10-08,2027,10,8,friday +2027-10-11,2027,10,11,monday +2027-10-13,2027,10,13,wednesday +2027-10-16,2027,10,16,saturday +2027-10-19,2027,10,19,tuesday +2027-10-20,2027,10,20,wednesday +2027-10-22,2027,10,22,friday +2027-10-24,2027,10,24,sunday +2027-10-26,2027,10,26,tuesday +2027-10-29,2027,10,29,friday +2027-10-30,2027,10,30,saturday +2027-11-01,2027,11,1,monday +2027-11-06,2027,11,6,saturday +2027-11-08,2027,11,8,monday +2027-11-09,2027,11,9,tuesday +2027-11-10,2027,11,10,wednesday +2027-11-11,2027,11,11,thursday +2027-11-12,2027,11,12,friday +2027-11-13,2027,11,13,saturday +2027-11-14,2027,11,14,sunday +2027-11-16,2027,11,16,tuesday +2027-11-17,2027,11,17,wednesday +2027-11-20,2027,11,20,saturday +2027-11-21,2027,11,21,sunday +2027-11-22,2027,11,22,monday +2027-11-23,2027,11,23,tuesday +2027-11-24,2027,11,24,wednesday +2027-11-25,2027,11,25,thursday +2027-11-26,2027,11,26,friday +2027-11-27,2027,11,27,saturday +2027-11-28,2027,11,28,sunday +2027-12-01,2027,12,1,wednesday +2027-12-05,2027,12,5,sunday +2027-12-06,2027,12,6,monday +2027-12-07,2027,12,7,tuesday +2027-12-10,2027,12,10,friday +2027-12-13,2027,12,13,monday +2027-12-14,2027,12,14,tuesday +2027-12-16,2027,12,16,thursday +2027-12-17,2027,12,17,friday +2027-12-19,2027,12,19,sunday +2027-12-20,2027,12,20,monday +2027-12-25,2027,12,25,saturday +2027-12-28,2027,12,28,tuesday +2027-12-29,2027,12,29,wednesday +2027-12-31,2027,12,31,friday +2028-01-02,2028,1,2,sunday +2028-01-05,2028,1,5,wednesday +2028-01-07,2028,1,7,friday +2028-01-09,2028,1,9,sunday +2028-01-10,2028,1,10,monday +2028-01-14,2028,1,14,friday +2028-01-16,2028,1,16,sunday +2028-01-17,2028,1,17,monday +2028-01-20,2028,1,20,thursday +2028-01-21,2028,1,21,friday +2028-01-22,2028,1,22,saturday +2028-01-26,2028,1,26,wednesday +2028-01-27,2028,1,27,thursday +2028-01-29,2028,1,29,saturday +2028-02-02,2028,2,2,wednesday +2028-02-03,2028,2,3,thursday +2028-02-05,2028,2,5,saturday +2028-02-11,2028,2,11,friday +2028-02-13,2028,2,13,sunday +2028-02-15,2028,2,15,tuesday +2028-02-18,2028,2,18,friday +2028-02-19,2028,2,19,saturday +2028-02-20,2028,2,20,sunday +2028-02-21,2028,2,21,monday +2028-02-22,2028,2,22,tuesday +2028-02-23,2028,2,23,wednesday +2028-02-24,2028,2,24,thursday +2028-02-25,2028,2,25,friday +2028-02-26,2028,2,26,saturday +2028-02-27,2028,2,27,sunday +2028-02-28,2028,2,28,monday +2028-03-01,2028,3,1,wednesday +2028-03-02,2028,3,2,thursday +2028-03-06,2028,3,6,monday +2028-03-07,2028,3,7,tuesday +2028-03-08,2028,3,8,wednesday +2028-03-10,2028,3,10,friday +2028-03-12,2028,3,12,sunday +2028-03-18,2028,3,18,saturday +2028-03-20,2028,3,20,monday +2028-03-22,2028,3,22,wednesday +2028-03-25,2028,3,25,saturday +2028-03-28,2028,3,28,tuesday +2028-03-29,2028,3,29,wednesday +2028-03-30,2028,3,30,thursday +2028-03-31,2028,3,31,friday +2028-04-02,2028,4,2,sunday +2028-04-03,2028,4,3,monday +2028-04-04,2028,4,4,tuesday +2028-04-06,2028,4,6,thursday +2028-04-08,2028,4,8,saturday +2028-04-10,2028,4,10,monday +2028-04-11,2028,4,11,tuesday +2028-04-13,2028,4,13,thursday +2028-04-17,2028,4,17,monday +2028-04-18,2028,4,18,tuesday +2028-04-19,2028,4,19,wednesday +2028-04-21,2028,4,21,friday +2028-04-22,2028,4,22,saturday +2028-04-26,2028,4,26,wednesday +2028-04-29,2028,4,29,saturday +2028-05-02,2028,5,2,tuesday +2028-05-06,2028,5,6,saturday +2028-05-09,2028,5,9,tuesday +2028-05-10,2028,5,10,wednesday +2028-05-11,2028,5,11,thursday +2028-05-12,2028,5,12,friday +2028-05-14,2028,5,14,sunday +2028-05-15,2028,5,15,monday +2028-05-20,2028,5,20,saturday +2028-05-23,2028,5,23,tuesday +2028-05-24,2028,5,24,wednesday +2028-05-25,2028,5,25,thursday +2028-05-27,2028,5,27,saturday +2028-05-28,2028,5,28,sunday +2028-05-29,2028,5,29,monday +2028-05-30,2028,5,30,tuesday +2028-06-01,2028,6,1,thursday +2028-06-06,2028,6,6,tuesday +2028-06-07,2028,6,7,wednesday +2028-06-09,2028,6,9,friday +2028-06-12,2028,6,12,monday +2028-06-13,2028,6,13,tuesday +2028-06-16,2028,6,16,friday +2028-06-17,2028,6,17,saturday +2028-06-20,2028,6,20,tuesday +2028-06-23,2028,6,23,friday +2028-06-25,2028,6,25,sunday +2028-06-29,2028,6,29,thursday +2028-07-04,2028,7,4,tuesday +2028-07-07,2028,7,7,friday +2028-07-09,2028,7,9,sunday +2028-07-12,2028,7,12,wednesday +2028-07-13,2028,7,13,thursday +2028-07-16,2028,7,16,sunday +2028-07-17,2028,7,17,monday +2028-07-18,2028,7,18,tuesday +2028-07-19,2028,7,19,wednesday +2028-07-22,2028,7,22,saturday +2028-07-23,2028,7,23,sunday +2028-07-24,2028,7,24,monday +2028-07-26,2028,7,26,wednesday +2028-07-27,2028,7,27,thursday +2028-07-28,2028,7,28,friday +2028-07-31,2028,7,31,monday +2028-08-02,2028,8,2,wednesday +2028-08-04,2028,8,4,friday +2028-08-05,2028,8,5,saturday +2028-08-06,2028,8,6,sunday +2028-08-07,2028,8,7,monday +2028-08-08,2028,8,8,tuesday +2028-08-13,2028,8,13,sunday +2028-08-14,2028,8,14,monday +2028-08-18,2028,8,18,friday +2028-08-23,2028,8,23,wednesday +2028-08-25,2028,8,25,friday +2028-08-26,2028,8,26,saturday +2028-08-27,2028,8,27,sunday +2028-08-28,2028,8,28,monday +2028-08-29,2028,8,29,tuesday +2028-08-30,2028,8,30,wednesday +2028-08-31,2028,8,31,thursday +2028-09-02,2028,9,2,saturday +2028-09-03,2028,9,3,sunday +2028-09-05,2028,9,5,tuesday +2028-09-08,2028,9,8,friday +2028-09-09,2028,9,9,saturday +2028-09-11,2028,9,11,monday +2028-09-12,2028,9,12,tuesday +2028-09-15,2028,9,15,friday +2028-09-17,2028,9,17,sunday +2028-09-19,2028,9,19,tuesday +2028-09-20,2028,9,20,wednesday +2028-09-25,2028,9,25,monday +2028-09-27,2028,9,27,wednesday +2028-09-28,2028,9,28,thursday +2028-09-30,2028,9,30,saturday +2028-10-01,2028,10,1,sunday +2028-10-05,2028,10,5,thursday +2028-10-06,2028,10,6,friday +2028-10-12,2028,10,12,thursday +2027-09-25,2027,9,25,saturday +2027-09-26,2027,9,26,sunday +2027-09-27,2027,9,27,monday +2027-09-28,2027,9,28,tuesday +2027-09-30,2027,9,30,thursday +2027-10-01,2027,10,1,friday +2027-10-02,2027,10,2,saturday +2027-10-03,2027,10,3,sunday +2027-10-04,2027,10,4,monday +2027-10-05,2027,10,5,tuesday +2027-10-06,2027,10,6,wednesday +2027-10-09,2027,10,9,saturday +2027-10-10,2027,10,10,sunday +2027-10-12,2027,10,12,tuesday +2027-10-14,2027,10,14,thursday +2027-10-15,2027,10,15,friday +2027-10-17,2027,10,17,sunday +2027-10-18,2027,10,18,monday +2027-10-21,2027,10,21,thursday +2027-10-23,2027,10,23,saturday +2027-10-25,2027,10,25,monday +2027-10-27,2027,10,27,wednesday +2027-10-28,2027,10,28,thursday +2027-10-31,2027,10,31,sunday +2027-11-02,2027,11,2,tuesday +2027-11-03,2027,11,3,wednesday +2027-11-04,2027,11,4,thursday +2027-11-05,2027,11,5,friday +2027-11-07,2027,11,7,sunday +2027-11-15,2027,11,15,monday +2027-11-18,2027,11,18,thursday +2027-11-19,2027,11,19,friday +2027-11-29,2027,11,29,monday +2027-11-30,2027,11,30,tuesday +2027-12-02,2027,12,2,thursday +2027-12-03,2027,12,3,friday +2027-12-04,2027,12,4,saturday +2027-12-08,2027,12,8,wednesday +2027-12-09,2027,12,9,thursday +2027-12-11,2027,12,11,saturday +2027-12-12,2027,12,12,sunday +2027-12-15,2027,12,15,wednesday +2027-12-18,2027,12,18,saturday +2027-12-21,2027,12,21,tuesday +2027-12-22,2027,12,22,wednesday +2027-12-23,2027,12,23,thursday +2027-12-24,2027,12,24,friday +2027-12-26,2027,12,26,sunday +2027-12-27,2027,12,27,monday +2027-12-30,2027,12,30,thursday +2028-01-01,2028,1,1,saturday +2028-01-03,2028,1,3,monday +2028-01-04,2028,1,4,tuesday +2028-01-06,2028,1,6,thursday +2028-01-08,2028,1,8,saturday +2028-01-11,2028,1,11,tuesday +2028-01-12,2028,1,12,wednesday +2028-01-13,2028,1,13,thursday +2028-01-15,2028,1,15,saturday +2028-01-18,2028,1,18,tuesday +2028-01-19,2028,1,19,wednesday +2028-01-23,2028,1,23,sunday +2028-01-24,2028,1,24,monday +2028-01-25,2028,1,25,tuesday +2028-01-28,2028,1,28,friday +2028-01-30,2028,1,30,sunday +2028-01-31,2028,1,31,monday +2028-02-01,2028,2,1,tuesday +2028-02-04,2028,2,4,friday +2028-02-06,2028,2,6,sunday +2028-02-07,2028,2,7,monday +2028-02-08,2028,2,8,tuesday +2028-02-09,2028,2,9,wednesday +2028-02-10,2028,2,10,thursday +2028-02-12,2028,2,12,saturday +2028-02-14,2028,2,14,monday +2028-02-16,2028,2,16,wednesday +2028-02-17,2028,2,17,thursday +2028-02-29,2028,2,29,tuesday +2028-03-03,2028,3,3,friday +2028-03-04,2028,3,4,saturday +2028-03-05,2028,3,5,sunday +2028-03-09,2028,3,9,thursday +2028-03-11,2028,3,11,saturday +2028-03-13,2028,3,13,monday +2028-03-14,2028,3,14,tuesday +2028-03-15,2028,3,15,wednesday +2028-03-16,2028,3,16,thursday +2028-03-17,2028,3,17,friday +2028-03-19,2028,3,19,sunday +2028-03-21,2028,3,21,tuesday +2028-03-23,2028,3,23,thursday +2028-03-24,2028,3,24,friday +2028-03-26,2028,3,26,sunday +2028-03-27,2028,3,27,monday +2028-04-01,2028,4,1,saturday +2028-04-05,2028,4,5,wednesday +2028-04-07,2028,4,7,friday +2028-04-09,2028,4,9,sunday +2028-04-12,2028,4,12,wednesday +2028-04-14,2028,4,14,friday +2028-04-15,2028,4,15,saturday +2028-04-16,2028,4,16,sunday +2028-04-20,2028,4,20,thursday +2028-04-23,2028,4,23,sunday +2028-04-24,2028,4,24,monday +2028-04-25,2028,4,25,tuesday +2028-04-27,2028,4,27,thursday +2028-04-28,2028,4,28,friday +2028-04-30,2028,4,30,sunday +2028-05-01,2028,5,1,monday +2028-05-03,2028,5,3,wednesday +2028-05-04,2028,5,4,thursday +2028-05-05,2028,5,5,friday +2028-05-07,2028,5,7,sunday +2028-05-08,2028,5,8,monday +2028-05-13,2028,5,13,saturday +2028-05-16,2028,5,16,tuesday +2028-05-17,2028,5,17,wednesday +2028-05-18,2028,5,18,thursday +2028-05-19,2028,5,19,friday +2028-05-21,2028,5,21,sunday +2028-05-22,2028,5,22,monday +2028-05-26,2028,5,26,friday +2028-05-31,2028,5,31,wednesday +2028-06-02,2028,6,2,friday +2028-06-03,2028,6,3,saturday +2028-06-04,2028,6,4,sunday +2028-06-05,2028,6,5,monday +2028-06-08,2028,6,8,thursday +2028-06-10,2028,6,10,saturday +2028-06-11,2028,6,11,sunday +2028-06-14,2028,6,14,wednesday +2028-06-15,2028,6,15,thursday +2028-06-18,2028,6,18,sunday +2028-06-19,2028,6,19,monday +2028-06-21,2028,6,21,wednesday +2028-06-22,2028,6,22,thursday +2028-06-24,2028,6,24,saturday +2028-06-26,2028,6,26,monday +2028-06-27,2028,6,27,tuesday +2028-06-28,2028,6,28,wednesday +2028-06-30,2028,6,30,friday +2028-07-01,2028,7,1,saturday +2028-07-02,2028,7,2,sunday +2028-07-03,2028,7,3,monday +2028-07-05,2028,7,5,wednesday +2028-07-06,2028,7,6,thursday +2028-07-08,2028,7,8,saturday +2028-07-10,2028,7,10,monday +2028-07-11,2028,7,11,tuesday +2028-07-14,2028,7,14,friday +2028-07-15,2028,7,15,saturday +2028-07-20,2028,7,20,thursday +2028-07-21,2028,7,21,friday +2028-07-25,2028,7,25,tuesday +2028-07-29,2028,7,29,saturday +2028-07-30,2028,7,30,sunday +2028-08-01,2028,8,1,tuesday +2028-08-03,2028,8,3,thursday +2028-08-09,2028,8,9,wednesday +2028-08-10,2028,8,10,thursday +2028-08-11,2028,8,11,friday +2028-08-12,2028,8,12,saturday +2028-08-15,2028,8,15,tuesday +2028-08-16,2028,8,16,wednesday +2028-08-17,2028,8,17,thursday +2028-08-19,2028,8,19,saturday +2028-08-20,2028,8,20,sunday +2028-08-21,2028,8,21,monday +2028-08-22,2028,8,22,tuesday +2028-08-24,2028,8,24,thursday +2028-09-01,2028,9,1,friday +2028-09-04,2028,9,4,monday +2028-09-06,2028,9,6,wednesday +2028-09-07,2028,9,7,thursday +2028-09-10,2028,9,10,sunday +2028-09-13,2028,9,13,wednesday +2028-09-14,2028,9,14,thursday +2028-09-16,2028,9,16,saturday +2028-09-18,2028,9,18,monday +2028-09-21,2028,9,21,thursday +2028-09-22,2028,9,22,friday +2028-09-23,2028,9,23,saturday +2028-09-24,2028,9,24,sunday +2028-09-26,2028,9,26,tuesday +2028-09-29,2028,9,29,friday +2028-10-02,2028,10,2,monday +2028-10-03,2028,10,3,tuesday +2028-10-04,2028,10,4,wednesday +2028-10-07,2028,10,7,saturday +2028-10-08,2028,10,8,sunday +2028-10-09,2028,10,9,monday +2028-10-10,2028,10,10,tuesday +2028-10-11,2028,10,11,wednesday +2028-10-14,2028,10,14,saturday +2028-10-15,2028,10,15,sunday +2028-10-17,2028,10,17,tuesday +2028-10-18,2028,10,18,wednesday +2028-10-19,2028,10,19,thursday +2028-10-22,2028,10,22,sunday +2028-10-25,2028,10,25,wednesday +2028-10-27,2028,10,27,friday +2028-10-29,2028,10,29,sunday +2028-10-31,2028,10,31,tuesday +2028-11-02,2028,11,2,thursday +2028-11-03,2028,11,3,friday +2028-11-05,2028,11,5,sunday +2028-11-07,2028,11,7,tuesday +2028-11-09,2028,11,9,thursday +2028-11-11,2028,11,11,saturday +2028-11-12,2028,11,12,sunday +2028-11-14,2028,11,14,tuesday +2028-10-13,2028,10,13,friday +2028-10-16,2028,10,16,monday +2028-10-20,2028,10,20,friday +2028-10-21,2028,10,21,saturday +2028-10-23,2028,10,23,monday +2028-10-24,2028,10,24,tuesday +2028-10-26,2028,10,26,thursday +2028-10-28,2028,10,28,saturday +2028-10-30,2028,10,30,monday +2028-11-01,2028,11,1,wednesday +2028-11-04,2028,11,4,saturday +2028-11-06,2028,11,6,monday +2028-11-08,2028,11,8,wednesday +2028-11-10,2028,11,10,friday +2028-11-13,2028,11,13,monday +2028-11-16,2028,11,16,thursday +2028-11-17,2028,11,17,friday +2028-11-19,2028,11,19,sunday +2028-11-20,2028,11,20,monday +2028-11-22,2028,11,22,wednesday +2028-11-24,2028,11,24,friday +2028-11-25,2028,11,25,saturday +2028-11-29,2028,11,29,wednesday +2028-11-30,2028,11,30,thursday +2028-12-03,2028,12,3,sunday +2028-12-09,2028,12,9,saturday +2028-12-11,2028,12,11,monday +2028-12-12,2028,12,12,tuesday +2028-12-16,2028,12,16,saturday +2028-12-17,2028,12,17,sunday +2028-12-18,2028,12,18,monday +2028-12-19,2028,12,19,tuesday +2028-12-21,2028,12,21,thursday +2028-12-22,2028,12,22,friday +2028-12-27,2028,12,27,wednesday +2028-12-29,2028,12,29,friday +2029-01-01,2029,1,1,monday +2029-01-03,2029,1,3,wednesday +2029-01-06,2029,1,6,saturday +2029-01-08,2029,1,8,monday +2029-01-13,2029,1,13,saturday +2029-01-17,2029,1,17,wednesday +2029-01-19,2029,1,19,friday +2029-01-21,2029,1,21,sunday +2029-01-23,2029,1,23,tuesday +2029-01-26,2029,1,26,friday +2029-01-28,2029,1,28,sunday +2029-01-31,2029,1,31,wednesday +2029-02-01,2029,2,1,thursday +2029-02-03,2029,2,3,saturday +2029-02-04,2029,2,4,sunday +2029-02-05,2029,2,5,monday +2029-02-08,2029,2,8,thursday +2029-02-17,2029,2,17,saturday +2029-02-18,2029,2,18,sunday +2029-02-19,2029,2,19,monday +2029-03-06,2029,3,6,tuesday +2029-03-13,2029,3,13,tuesday +2029-03-14,2029,3,14,wednesday +2029-03-15,2029,3,15,thursday +2029-03-16,2029,3,16,friday +2029-03-18,2029,3,18,sunday +2029-03-20,2029,3,20,tuesday +2029-03-21,2029,3,21,wednesday +2029-03-23,2029,3,23,friday +2029-03-25,2029,3,25,sunday +2029-03-28,2029,3,28,wednesday +2029-03-29,2029,3,29,thursday +2029-04-01,2029,4,1,sunday +2029-04-03,2029,4,3,tuesday +2029-04-06,2029,4,6,friday +2029-04-08,2029,4,8,sunday +2029-04-12,2029,4,12,thursday +2029-04-13,2029,4,13,friday +2029-04-14,2029,4,14,saturday +2029-04-16,2029,4,16,monday +2029-04-19,2029,4,19,thursday +2029-04-20,2029,4,20,friday +2029-04-25,2029,4,25,wednesday +2029-04-28,2029,4,28,saturday +2029-04-29,2029,4,29,sunday +2029-05-01,2029,5,1,tuesday +2029-05-02,2029,5,2,wednesday +2029-05-03,2029,5,3,thursday +2029-05-05,2029,5,5,saturday +2029-05-06,2029,5,6,sunday +2029-05-08,2029,5,8,tuesday +2029-05-10,2029,5,10,thursday +2029-05-16,2029,5,16,wednesday +2029-05-19,2029,5,19,saturday +2029-05-22,2029,5,22,tuesday +2029-05-23,2029,5,23,wednesday +2029-05-26,2029,5,26,saturday +2029-05-27,2029,5,27,sunday +2029-05-29,2029,5,29,tuesday +2029-06-03,2029,6,3,sunday +2029-06-05,2029,6,5,tuesday +2029-06-09,2029,6,9,saturday +2029-06-11,2029,6,11,monday +2029-06-12,2029,6,12,tuesday +2029-06-13,2029,6,13,wednesday +2029-06-17,2029,6,17,sunday +2029-06-19,2029,6,19,tuesday +2029-06-21,2029,6,21,thursday +2029-06-22,2029,6,22,friday +2029-06-23,2029,6,23,saturday +2029-06-25,2029,6,25,monday +2029-06-26,2029,6,26,tuesday +2029-06-27,2029,6,27,wednesday +2029-06-28,2029,6,28,thursday +2029-06-30,2029,6,30,saturday +2029-07-01,2029,7,1,sunday +2029-07-02,2029,7,2,monday +2029-07-03,2029,7,3,tuesday +2029-07-04,2029,7,4,wednesday +2029-07-08,2029,7,8,sunday +2029-07-09,2029,7,9,monday +2029-07-11,2029,7,11,wednesday +2029-07-12,2029,7,12,thursday +2029-07-17,2029,7,17,tuesday +2029-07-18,2029,7,18,wednesday +2029-07-19,2029,7,19,thursday +2029-07-20,2029,7,20,friday +2029-07-23,2029,7,23,monday +2029-07-25,2029,7,25,wednesday +2029-07-26,2029,7,26,thursday +2029-07-27,2029,7,27,friday +2029-07-28,2029,7,28,saturday +2029-08-02,2029,8,2,thursday +2029-08-04,2029,8,4,saturday +2029-08-05,2029,8,5,sunday +2029-08-08,2029,8,8,wednesday +2029-08-09,2029,8,9,thursday +2029-08-10,2029,8,10,friday +2029-08-12,2029,8,12,sunday +2029-08-13,2029,8,13,monday +2029-08-14,2029,8,14,tuesday +2029-08-15,2029,8,15,wednesday +2029-08-16,2029,8,16,thursday +2029-08-18,2029,8,18,saturday +2029-08-19,2029,8,19,sunday +2029-08-20,2029,8,20,monday +2029-08-22,2029,8,22,wednesday +2029-08-23,2029,8,23,thursday +2029-08-26,2029,8,26,sunday +2029-08-28,2029,8,28,tuesday +2029-08-29,2029,8,29,wednesday +2029-08-31,2029,8,31,friday +2029-09-01,2029,9,1,saturday +2029-09-04,2029,9,4,tuesday +2029-09-08,2029,9,8,saturday +2029-09-09,2029,9,9,sunday +2029-09-12,2029,9,12,wednesday +2029-09-16,2029,9,16,sunday +2029-09-19,2029,9,19,wednesday +2029-09-21,2029,9,21,friday +2029-09-22,2029,9,22,saturday +2029-09-24,2029,9,24,monday +2029-09-26,2029,9,26,wednesday +2029-09-27,2029,9,27,thursday +2029-09-30,2029,9,30,sunday +2029-10-01,2029,10,1,monday +2029-10-03,2029,10,3,wednesday +2029-10-04,2029,10,4,thursday +2029-10-07,2029,10,7,sunday +2029-10-13,2029,10,13,saturday +2029-10-19,2029,10,19,friday +2029-10-20,2029,10,20,saturday +2029-10-23,2029,10,23,tuesday +2029-10-26,2029,10,26,friday +2029-10-30,2029,10,30,tuesday +2029-10-31,2029,10,31,wednesday +2029-11-04,2029,11,4,sunday +2029-11-05,2029,11,5,monday +2029-11-06,2029,11,6,tuesday +2029-11-09,2029,11,9,friday +2029-11-10,2029,11,10,saturday +2029-11-18,2029,11,18,sunday +2029-11-19,2029,11,19,monday +2029-11-20,2029,11,20,tuesday +2029-11-22,2029,11,22,thursday +2029-11-25,2029,11,25,sunday +2029-11-26,2029,11,26,monday +2029-11-28,2029,11,28,wednesday +2029-11-30,2029,11,30,friday +2029-12-03,2029,12,3,monday +2029-12-05,2029,12,5,wednesday +2029-12-07,2029,12,7,friday +2029-12-08,2029,12,8,saturday +2029-12-09,2029,12,9,sunday +2029-12-11,2029,12,11,tuesday +2029-12-15,2029,12,15,saturday +2029-12-16,2029,12,16,sunday +2029-12-18,2029,12,18,tuesday +2029-12-19,2029,12,19,wednesday +2029-12-20,2029,12,20,thursday +2029-12-21,2029,12,21,friday +2029-12-22,2029,12,22,saturday +2029-12-23,2029,12,23,sunday +2029-12-24,2029,12,24,monday +2029-12-25,2029,12,25,tuesday +2029-12-26,2029,12,26,wednesday +2029-12-30,2029,12,30,sunday +2030-01-01,2030,1,1,tuesday +2030-01-04,2030,1,4,friday +2030-01-05,2030,1,5,saturday +2030-01-07,2030,1,7,monday +2030-01-12,2030,1,12,saturday +2030-01-13,2030,1,13,sunday +2030-01-16,2030,1,16,wednesday +2030-01-18,2030,1,18,friday +2030-01-20,2030,1,20,sunday +2030-01-21,2030,1,21,monday +2028-11-15,2028,11,15,wednesday +2028-11-18,2028,11,18,saturday +2028-11-21,2028,11,21,tuesday +2028-11-23,2028,11,23,thursday +2028-11-26,2028,11,26,sunday +2028-11-27,2028,11,27,monday +2028-11-28,2028,11,28,tuesday +2028-12-01,2028,12,1,friday +2028-12-02,2028,12,2,saturday +2028-12-04,2028,12,4,monday +2028-12-05,2028,12,5,tuesday +2028-12-06,2028,12,6,wednesday +2028-12-07,2028,12,7,thursday +2028-12-08,2028,12,8,friday +2028-12-10,2028,12,10,sunday +2028-12-13,2028,12,13,wednesday +2028-12-14,2028,12,14,thursday +2028-12-15,2028,12,15,friday +2028-12-20,2028,12,20,wednesday +2028-12-23,2028,12,23,saturday +2028-12-24,2028,12,24,sunday +2028-12-25,2028,12,25,monday +2028-12-26,2028,12,26,tuesday +2028-12-28,2028,12,28,thursday +2028-12-30,2028,12,30,saturday +2028-12-31,2028,12,31,sunday +2029-01-02,2029,1,2,tuesday +2029-01-04,2029,1,4,thursday +2029-01-05,2029,1,5,friday +2029-01-07,2029,1,7,sunday +2029-01-09,2029,1,9,tuesday +2029-01-10,2029,1,10,wednesday +2029-01-11,2029,1,11,thursday +2029-01-12,2029,1,12,friday +2029-01-14,2029,1,14,sunday +2029-01-15,2029,1,15,monday +2029-01-16,2029,1,16,tuesday +2029-01-18,2029,1,18,thursday +2029-01-20,2029,1,20,saturday +2029-01-22,2029,1,22,monday +2029-01-24,2029,1,24,wednesday +2029-01-25,2029,1,25,thursday +2029-01-27,2029,1,27,saturday +2029-01-29,2029,1,29,monday +2029-01-30,2029,1,30,tuesday +2029-02-02,2029,2,2,friday +2029-02-06,2029,2,6,tuesday +2029-02-07,2029,2,7,wednesday +2029-02-09,2029,2,9,friday +2029-02-10,2029,2,10,saturday +2029-02-11,2029,2,11,sunday +2029-02-12,2029,2,12,monday +2029-02-13,2029,2,13,tuesday +2029-02-14,2029,2,14,wednesday +2029-02-15,2029,2,15,thursday +2029-02-16,2029,2,16,friday +2029-02-20,2029,2,20,tuesday +2029-02-21,2029,2,21,wednesday +2029-02-22,2029,2,22,thursday +2029-02-23,2029,2,23,friday +2029-02-24,2029,2,24,saturday +2029-02-25,2029,2,25,sunday +2029-02-26,2029,2,26,monday +2029-02-27,2029,2,27,tuesday +2029-02-28,2029,2,28,wednesday +2029-03-01,2029,3,1,thursday +2029-03-02,2029,3,2,friday +2029-03-03,2029,3,3,saturday +2029-03-04,2029,3,4,sunday +2029-03-05,2029,3,5,monday +2029-03-07,2029,3,7,wednesday +2029-03-08,2029,3,8,thursday +2029-03-09,2029,3,9,friday +2029-03-10,2029,3,10,saturday +2029-03-11,2029,3,11,sunday +2029-03-12,2029,3,12,monday +2029-03-17,2029,3,17,saturday +2029-03-19,2029,3,19,monday +2029-03-22,2029,3,22,thursday +2029-03-24,2029,3,24,saturday +2029-03-26,2029,3,26,monday +2029-03-27,2029,3,27,tuesday +2029-03-30,2029,3,30,friday +2029-03-31,2029,3,31,saturday +2029-04-02,2029,4,2,monday +2029-04-04,2029,4,4,wednesday +2029-04-05,2029,4,5,thursday +2029-04-07,2029,4,7,saturday +2029-04-09,2029,4,9,monday +2029-04-10,2029,4,10,tuesday +2029-04-11,2029,4,11,wednesday +2029-04-15,2029,4,15,sunday +2029-04-17,2029,4,17,tuesday +2029-04-18,2029,4,18,wednesday +2029-04-21,2029,4,21,saturday +2029-04-22,2029,4,22,sunday +2029-04-23,2029,4,23,monday +2029-04-24,2029,4,24,tuesday +2029-04-26,2029,4,26,thursday +2029-04-27,2029,4,27,friday +2029-04-30,2029,4,30,monday +2029-05-04,2029,5,4,friday +2029-05-07,2029,5,7,monday +2029-05-09,2029,5,9,wednesday +2029-05-11,2029,5,11,friday +2029-05-12,2029,5,12,saturday +2029-05-13,2029,5,13,sunday +2029-05-14,2029,5,14,monday +2029-05-15,2029,5,15,tuesday +2029-05-17,2029,5,17,thursday +2029-05-18,2029,5,18,friday +2029-05-20,2029,5,20,sunday +2029-05-21,2029,5,21,monday +2029-05-24,2029,5,24,thursday +2029-05-25,2029,5,25,friday +2029-05-28,2029,5,28,monday +2029-05-30,2029,5,30,wednesday +2029-05-31,2029,5,31,thursday +2029-06-01,2029,6,1,friday +2029-06-02,2029,6,2,saturday +2029-06-04,2029,6,4,monday +2029-06-06,2029,6,6,wednesday +2029-06-07,2029,6,7,thursday +2029-06-08,2029,6,8,friday +2029-06-10,2029,6,10,sunday +2029-06-14,2029,6,14,thursday +2029-06-15,2029,6,15,friday +2029-06-16,2029,6,16,saturday +2029-06-18,2029,6,18,monday +2029-06-20,2029,6,20,wednesday +2029-06-24,2029,6,24,sunday +2029-06-29,2029,6,29,friday +2029-07-05,2029,7,5,thursday +2029-07-06,2029,7,6,friday +2029-07-07,2029,7,7,saturday +2029-07-10,2029,7,10,tuesday +2029-07-13,2029,7,13,friday +2029-07-14,2029,7,14,saturday +2029-07-15,2029,7,15,sunday +2029-07-16,2029,7,16,monday +2029-07-21,2029,7,21,saturday +2029-07-22,2029,7,22,sunday +2029-07-24,2029,7,24,tuesday +2029-07-29,2029,7,29,sunday +2029-07-30,2029,7,30,monday +2029-07-31,2029,7,31,tuesday +2029-08-01,2029,8,1,wednesday +2029-08-03,2029,8,3,friday +2029-08-06,2029,8,6,monday +2029-08-07,2029,8,7,tuesday +2029-08-11,2029,8,11,saturday +2029-08-17,2029,8,17,friday +2029-08-21,2029,8,21,tuesday +2029-08-24,2029,8,24,friday +2029-08-25,2029,8,25,saturday +2029-08-27,2029,8,27,monday +2029-08-30,2029,8,30,thursday +2029-09-02,2029,9,2,sunday +2029-09-03,2029,9,3,monday +2029-09-05,2029,9,5,wednesday +2029-09-06,2029,9,6,thursday +2029-09-07,2029,9,7,friday +2029-09-10,2029,9,10,monday +2029-09-11,2029,9,11,tuesday +2029-09-13,2029,9,13,thursday +2029-09-14,2029,9,14,friday +2029-09-15,2029,9,15,saturday +2029-09-17,2029,9,17,monday +2029-09-18,2029,9,18,tuesday +2029-09-20,2029,9,20,thursday +2029-09-23,2029,9,23,sunday +2029-09-25,2029,9,25,tuesday +2029-09-28,2029,9,28,friday +2029-09-29,2029,9,29,saturday +2029-10-02,2029,10,2,tuesday +2029-10-05,2029,10,5,friday +2029-10-06,2029,10,6,saturday +2029-10-08,2029,10,8,monday +2029-10-09,2029,10,9,tuesday +2029-10-10,2029,10,10,wednesday +2029-10-11,2029,10,11,thursday +2029-10-12,2029,10,12,friday +2029-10-14,2029,10,14,sunday +2029-10-15,2029,10,15,monday +2029-10-16,2029,10,16,tuesday +2029-10-17,2029,10,17,wednesday +2029-10-18,2029,10,18,thursday +2029-10-21,2029,10,21,sunday +2029-10-22,2029,10,22,monday +2029-10-24,2029,10,24,wednesday +2029-10-25,2029,10,25,thursday +2029-10-27,2029,10,27,saturday +2029-10-28,2029,10,28,sunday +2029-10-29,2029,10,29,monday +2029-11-01,2029,11,1,thursday +2029-11-02,2029,11,2,friday +2029-11-03,2029,11,3,saturday +2029-11-07,2029,11,7,wednesday +2029-11-08,2029,11,8,thursday +2029-11-11,2029,11,11,sunday +2029-11-12,2029,11,12,monday +2029-11-13,2029,11,13,tuesday +2029-11-14,2029,11,14,wednesday +2029-11-15,2029,11,15,thursday +2029-11-16,2029,11,16,friday +2029-11-17,2029,11,17,saturday +2029-11-21,2029,11,21,wednesday +2029-11-23,2029,11,23,friday +2029-11-24,2029,11,24,saturday +2029-11-27,2029,11,27,tuesday +2029-11-29,2029,11,29,thursday +2029-12-01,2029,12,1,saturday +2029-12-02,2029,12,2,sunday +2030-01-22,2030,1,22,tuesday +2030-01-23,2030,1,23,wednesday +2030-01-27,2030,1,27,sunday +2030-01-31,2030,1,31,thursday +2030-02-02,2030,2,2,saturday +2030-02-05,2030,2,5,tuesday +2030-02-06,2030,2,6,wednesday +2030-02-09,2030,2,9,saturday +2030-02-11,2030,2,11,monday +2030-02-14,2030,2,14,thursday +2030-02-15,2030,2,15,friday +2030-02-16,2030,2,16,saturday +2030-02-17,2030,2,17,sunday +2030-02-20,2030,2,20,wednesday +2030-02-23,2030,2,23,saturday +2030-02-24,2030,2,24,sunday +2030-02-27,2030,2,27,wednesday +2030-03-01,2030,3,1,friday +2030-03-04,2030,3,4,monday +2030-03-05,2030,3,5,tuesday +2030-03-07,2030,3,7,thursday +2030-03-08,2030,3,8,friday +2030-03-10,2030,3,10,sunday +2030-03-11,2030,3,11,monday +2030-03-12,2030,3,12,tuesday +2030-03-13,2030,3,13,wednesday +2030-03-15,2030,3,15,friday +2030-03-16,2030,3,16,saturday +2030-03-17,2030,3,17,sunday +2030-03-18,2030,3,18,monday +2030-03-19,2030,3,19,tuesday +2030-03-22,2030,3,22,friday +2030-03-24,2030,3,24,sunday +2030-03-26,2030,3,26,tuesday +2030-03-27,2030,3,27,wednesday +2030-03-28,2030,3,28,thursday +2030-03-29,2030,3,29,friday +2030-03-30,2030,3,30,saturday +2030-04-02,2030,4,2,tuesday +2030-04-04,2030,4,4,thursday +2030-04-05,2030,4,5,friday +2030-04-11,2030,4,11,thursday +2030-04-13,2030,4,13,saturday +2030-04-15,2030,4,15,monday +2030-04-16,2030,4,16,tuesday +2030-04-18,2030,4,18,thursday +2030-04-19,2030,4,19,friday +2030-04-20,2030,4,20,saturday +2030-04-25,2030,4,25,thursday +2030-04-28,2030,4,28,sunday +2030-04-29,2030,4,29,monday +2030-05-01,2030,5,1,wednesday +2030-05-06,2030,5,6,monday +2030-05-07,2030,5,7,tuesday +2030-05-11,2030,5,11,saturday +2030-05-15,2030,5,15,wednesday +2030-05-17,2030,5,17,friday +2030-05-18,2030,5,18,saturday +2030-05-21,2030,5,21,tuesday +2030-05-22,2030,5,22,wednesday +2030-05-25,2030,5,25,saturday +2030-05-28,2030,5,28,tuesday +2030-05-29,2030,5,29,wednesday +2030-05-30,2030,5,30,thursday +2030-06-01,2030,6,1,saturday +2030-06-04,2030,6,4,tuesday +2030-06-05,2030,6,5,wednesday +2030-06-06,2030,6,6,thursday +2030-06-08,2030,6,8,saturday +2030-06-13,2030,6,13,thursday +2030-06-22,2030,6,22,saturday +2030-06-25,2030,6,25,tuesday +2030-06-26,2030,6,26,wednesday +2030-06-27,2030,6,27,thursday +2030-06-30,2030,6,30,sunday +2030-07-01,2030,7,1,monday +2030-07-02,2030,7,2,tuesday +2030-07-05,2030,7,5,friday +2030-07-06,2030,7,6,saturday +2030-07-09,2030,7,9,tuesday +2030-07-14,2030,7,14,sunday +2030-07-16,2030,7,16,tuesday +2030-07-20,2030,7,20,saturday +2030-07-23,2030,7,23,tuesday +2030-07-24,2030,7,24,wednesday +2030-07-25,2030,7,25,thursday +2030-07-26,2030,7,26,friday +2030-07-28,2030,7,28,sunday +2030-07-30,2030,7,30,tuesday +2030-07-31,2030,7,31,wednesday +2030-08-02,2030,8,2,friday +2030-08-06,2030,8,6,tuesday +2030-08-10,2030,8,10,saturday +2030-08-12,2030,8,12,monday +2030-08-14,2030,8,14,wednesday +2030-08-17,2030,8,17,saturday +2030-08-18,2030,8,18,sunday +2030-08-24,2030,8,24,saturday +2030-08-26,2030,8,26,monday +2030-08-28,2030,8,28,wednesday +2030-08-29,2030,8,29,thursday +2030-08-30,2030,8,30,friday +2030-09-01,2030,9,1,sunday +2030-09-02,2030,9,2,monday +2030-09-03,2030,9,3,tuesday +2030-09-05,2030,9,5,thursday +2030-09-07,2030,9,7,saturday +2030-09-08,2030,9,8,sunday +2030-09-11,2030,9,11,wednesday +2030-09-12,2030,9,12,thursday +2030-09-13,2030,9,13,friday +2030-09-15,2030,9,15,sunday +2030-09-17,2030,9,17,tuesday +2030-09-18,2030,9,18,wednesday +2030-09-23,2030,9,23,monday +2030-09-24,2030,9,24,tuesday +2030-09-26,2030,9,26,thursday +2030-09-27,2030,9,27,friday +2030-09-28,2030,9,28,saturday +2030-09-29,2030,9,29,sunday +2030-09-30,2030,9,30,monday +2030-10-01,2030,10,1,tuesday +2030-10-02,2030,10,2,wednesday +2030-10-03,2030,10,3,thursday +2030-10-07,2030,10,7,monday +2030-10-19,2030,10,19,saturday +2030-10-22,2030,10,22,tuesday +2030-10-23,2030,10,23,wednesday +2030-10-28,2030,10,28,monday +2030-10-29,2030,10,29,tuesday +2030-10-31,2030,10,31,thursday +2030-11-01,2030,11,1,friday +2030-11-04,2030,11,4,monday +2030-11-05,2030,11,5,tuesday +2030-11-07,2030,11,7,thursday +2030-11-11,2030,11,11,monday +2030-11-15,2030,11,15,friday +2030-11-16,2030,11,16,saturday +2030-11-17,2030,11,17,sunday +2030-11-19,2030,11,19,tuesday +2030-11-20,2030,11,20,wednesday +2030-11-21,2030,11,21,thursday +2030-11-22,2030,11,22,friday +2030-11-25,2030,11,25,monday +2030-11-27,2030,11,27,wednesday +2030-11-29,2030,11,29,friday +2030-11-30,2030,11,30,saturday +2030-12-11,2030,12,11,wednesday +2030-12-14,2030,12,14,saturday +2030-12-15,2030,12,15,sunday +2030-12-19,2030,12,19,thursday +2030-12-20,2030,12,20,friday +2030-12-21,2030,12,21,saturday +2030-12-23,2030,12,23,monday +2030-12-30,2030,12,30,monday +2029-12-04,2029,12,4,tuesday +2029-12-06,2029,12,6,thursday +2029-12-10,2029,12,10,monday +2029-12-12,2029,12,12,wednesday +2029-12-13,2029,12,13,thursday +2029-12-14,2029,12,14,friday +2029-12-17,2029,12,17,monday +2029-12-27,2029,12,27,thursday +2029-12-28,2029,12,28,friday +2029-12-29,2029,12,29,saturday +2029-12-31,2029,12,31,monday +2030-01-02,2030,1,2,wednesday +2030-01-03,2030,1,3,thursday +2030-01-06,2030,1,6,sunday +2030-01-08,2030,1,8,tuesday +2030-01-09,2030,1,9,wednesday +2030-01-10,2030,1,10,thursday +2030-01-11,2030,1,11,friday +2030-01-14,2030,1,14,monday +2030-01-15,2030,1,15,tuesday +2030-01-17,2030,1,17,thursday +2030-01-19,2030,1,19,saturday +2030-01-24,2030,1,24,thursday +2030-01-25,2030,1,25,friday +2030-01-26,2030,1,26,saturday +2030-01-28,2030,1,28,monday +2030-01-29,2030,1,29,tuesday +2030-01-30,2030,1,30,wednesday +2030-02-01,2030,2,1,friday +2030-02-03,2030,2,3,sunday +2030-02-04,2030,2,4,monday +2030-02-07,2030,2,7,thursday +2030-02-08,2030,2,8,friday +2030-02-10,2030,2,10,sunday +2030-02-12,2030,2,12,tuesday +2030-02-13,2030,2,13,wednesday +2030-02-18,2030,2,18,monday +2030-02-19,2030,2,19,tuesday +2030-02-21,2030,2,21,thursday +2030-02-22,2030,2,22,friday +2030-02-25,2030,2,25,monday +2030-02-26,2030,2,26,tuesday +2030-02-28,2030,2,28,thursday +2030-03-02,2030,3,2,saturday +2030-03-03,2030,3,3,sunday +2030-03-06,2030,3,6,wednesday +2030-03-09,2030,3,9,saturday +2030-03-14,2030,3,14,thursday +2030-03-20,2030,3,20,wednesday +2030-03-21,2030,3,21,thursday +2030-03-23,2030,3,23,saturday +2030-03-25,2030,3,25,monday +2030-03-31,2030,3,31,sunday +2030-04-01,2030,4,1,monday +2030-04-03,2030,4,3,wednesday +2030-04-06,2030,4,6,saturday +2030-04-07,2030,4,7,sunday +2030-04-08,2030,4,8,monday +2030-04-09,2030,4,9,tuesday +2030-04-10,2030,4,10,wednesday +2030-04-12,2030,4,12,friday +2030-04-14,2030,4,14,sunday +2030-04-17,2030,4,17,wednesday +2030-04-21,2030,4,21,sunday +2030-04-22,2030,4,22,monday +2030-04-23,2030,4,23,tuesday +2030-04-24,2030,4,24,wednesday +2030-04-26,2030,4,26,friday +2030-04-27,2030,4,27,saturday +2030-04-30,2030,4,30,tuesday +2030-05-02,2030,5,2,thursday +2030-05-03,2030,5,3,friday +2030-05-04,2030,5,4,saturday +2030-05-05,2030,5,5,sunday +2030-05-08,2030,5,8,wednesday +2030-05-09,2030,5,9,thursday +2030-05-10,2030,5,10,friday +2030-05-12,2030,5,12,sunday +2030-05-13,2030,5,13,monday +2030-05-14,2030,5,14,tuesday +2030-05-16,2030,5,16,thursday +2030-05-19,2030,5,19,sunday +2030-05-20,2030,5,20,monday +2030-05-23,2030,5,23,thursday +2030-05-24,2030,5,24,friday +2030-05-26,2030,5,26,sunday +2030-05-27,2030,5,27,monday +2030-05-31,2030,5,31,friday +2030-06-02,2030,6,2,sunday +2030-06-03,2030,6,3,monday +2030-06-07,2030,6,7,friday +2030-06-09,2030,6,9,sunday +2030-06-10,2030,6,10,monday +2030-06-11,2030,6,11,tuesday +2030-06-12,2030,6,12,wednesday +2030-06-14,2030,6,14,friday +2030-06-15,2030,6,15,saturday +2030-06-16,2030,6,16,sunday +2030-06-17,2030,6,17,monday +2030-06-18,2030,6,18,tuesday +2030-06-19,2030,6,19,wednesday +2030-06-20,2030,6,20,thursday +2030-06-21,2030,6,21,friday +2030-06-23,2030,6,23,sunday +2030-06-24,2030,6,24,monday +2030-06-28,2030,6,28,friday +2030-06-29,2030,6,29,saturday +2030-07-03,2030,7,3,wednesday +2030-07-04,2030,7,4,thursday +2030-07-07,2030,7,7,sunday +2030-07-08,2030,7,8,monday +2030-07-10,2030,7,10,wednesday +2030-07-11,2030,7,11,thursday +2030-07-12,2030,7,12,friday +2030-07-13,2030,7,13,saturday +2030-07-15,2030,7,15,monday +2030-07-17,2030,7,17,wednesday +2030-07-18,2030,7,18,thursday +2030-07-19,2030,7,19,friday +2030-07-21,2030,7,21,sunday +2030-07-22,2030,7,22,monday +2030-07-27,2030,7,27,saturday +2030-07-29,2030,7,29,monday +2030-08-01,2030,8,1,thursday +2030-08-03,2030,8,3,saturday +2030-08-04,2030,8,4,sunday +2030-08-05,2030,8,5,monday +2030-08-07,2030,8,7,wednesday +2030-08-08,2030,8,8,thursday +2030-08-09,2030,8,9,friday +2030-08-11,2030,8,11,sunday +2030-08-13,2030,8,13,tuesday +2030-08-15,2030,8,15,thursday +2030-08-16,2030,8,16,friday +2030-08-19,2030,8,19,monday +2030-08-20,2030,8,20,tuesday +2030-08-21,2030,8,21,wednesday +2030-08-22,2030,8,22,thursday +2030-08-23,2030,8,23,friday +2030-08-25,2030,8,25,sunday +2030-08-27,2030,8,27,tuesday +2030-08-31,2030,8,31,saturday +2030-09-04,2030,9,4,wednesday +2030-09-06,2030,9,6,friday +2030-09-09,2030,9,9,monday +2030-09-10,2030,9,10,tuesday +2030-09-14,2030,9,14,saturday +2030-09-16,2030,9,16,monday +2030-09-19,2030,9,19,thursday +2030-09-20,2030,9,20,friday +2030-09-21,2030,9,21,saturday +2030-09-22,2030,9,22,sunday +2030-09-25,2030,9,25,wednesday +2030-10-04,2030,10,4,friday +2030-10-05,2030,10,5,saturday +2030-10-06,2030,10,6,sunday +2030-10-08,2030,10,8,tuesday +2030-10-09,2030,10,9,wednesday +2030-10-10,2030,10,10,thursday +2030-10-11,2030,10,11,friday +2030-10-12,2030,10,12,saturday +2030-10-13,2030,10,13,sunday +2030-10-14,2030,10,14,monday +2030-10-15,2030,10,15,tuesday +2030-10-16,2030,10,16,wednesday +2030-10-17,2030,10,17,thursday +2030-10-18,2030,10,18,friday +2030-10-20,2030,10,20,sunday +2030-10-21,2030,10,21,monday +2030-10-24,2030,10,24,thursday +2030-10-25,2030,10,25,friday +2030-10-26,2030,10,26,saturday +2030-10-27,2030,10,27,sunday +2030-10-30,2030,10,30,wednesday +2030-11-02,2030,11,2,saturday +2030-11-03,2030,11,3,sunday +2030-11-06,2030,11,6,wednesday +2030-11-08,2030,11,8,friday +2030-11-09,2030,11,9,saturday +2030-11-10,2030,11,10,sunday +2030-11-12,2030,11,12,tuesday +2030-11-13,2030,11,13,wednesday +2030-11-14,2030,11,14,thursday +2030-11-18,2030,11,18,monday +2030-11-23,2030,11,23,saturday +2030-11-24,2030,11,24,sunday +2030-11-26,2030,11,26,tuesday +2030-11-28,2030,11,28,thursday +2030-12-01,2030,12,1,sunday +2030-12-02,2030,12,2,monday +2030-12-03,2030,12,3,tuesday +2030-12-04,2030,12,4,wednesday +2030-12-05,2030,12,5,thursday +2030-12-06,2030,12,6,friday +2030-12-07,2030,12,7,saturday +2030-12-08,2030,12,8,sunday +2030-12-09,2030,12,9,monday +2030-12-10,2030,12,10,tuesday +2030-12-12,2030,12,12,thursday +2030-12-13,2030,12,13,friday +2030-12-16,2030,12,16,monday +2030-12-17,2030,12,17,tuesday +2030-12-18,2030,12,18,wednesday +2030-12-22,2030,12,22,sunday +2030-12-24,2030,12,24,tuesday +2030-12-25,2030,12,25,wednesday +2030-12-26,2030,12,26,thursday +2030-12-27,2030,12,27,friday +2030-12-28,2030,12,28,saturday +2030-12-29,2030,12,29,sunday +2030-12-31,2030,12,31,tuesday diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv new file mode 100644 index 0000000..9383505 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv @@ -0,0 +1,19 @@ +"lesson_id","title","duration_min","course_id" +2,JOIN,15,1 +3,WHERE и фильтрация,12,1 +4,Агрегации и GROUP BY,18,1 +6,Pandas основы,20,2 +7,NumPy,15,2 +8,Визуализация данных,22,2 +9,Основы BI,12,3 +10,Метрики и дашборды,18,3 +13,Сводные таблицы,18,4 +16,Линейная регрессия,30,5 +18,Кластеризация,25,5 +1,SELECT и FROM,10,1 +5,Подзапросы,25,1 +11,Power BI введение,20,3 +12,Формулы в Excel,15,4 +14,Графики и диаграммы,14,4 +15,Введение в ML,20,5 +17,Классификация,28,5 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv new file mode 100644 index 0000000..529a745 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv @@ -0,0 +1,21 @@ +"user_id","name","age","email","registration_date" +1,Alice,25,alice@mail.com,2023-01-10 00:00:00.000 +0300 +5,Ethan,35,ethan@gmail.com,2023-03-10 00:00:00.000 +0300 +11,Kelly,23,kelly@gmail.com,2023-04-20 00:00:00.000 +0300 +12,Leo,31,leo@mail.com,2023-01-15 00:00:00.000 +0300 +14,Nina,28,nina@mail.com,2023-01-08 00:00:00.000 +0300 +15,Oscar,33,oscar@gmail.com,2023-01-10 00:00:00.000 +0300 +17,Quinn,24,quinn@gmail.com,2023-03-20 00:00:00.000 +0300 +20,Tina,25,tina@mail.com,2023-04-18 00:00:00.000 +0300 +2,Bob,30,bob@gmail.com,2023-02-05 00:00:00.000 +0300 +3,Charlie,22,charlie@mail.com,2023-02-20 00:00:00.000 +0300 +4,Diana,28,diana@mail.com,2023-03-01 00:00:00.000 +0300 +6,Frank,29,frank@mail.com,2023-01-05 00:00:00.000 +0300 +7,Grace,26,grace@gmail.com,2023-01-12 00:00:00.000 +0300 +8,Henry,32,henry@mail.com,2023-01-20 00:00:00.000 +0300 +9,Iris,24,iris@gmail.com,2023-02-10 00:00:00.000 +0300 +10,Jack,27,jack@mail.com,2023-04-15 00:00:00.000 +0300 +13,Mia,26,mia@gmail.com,2023-01-18 00:00:00.000 +0300 +16,Paul,29,paul@mail.com,2023-02-15 00:00:00.000 +0300 +18,Rita,27,rita@mail.com,2023-04-01 00:00:00.000 +0300 +19,Sam,30,sam@gmail.com,2023-04-10 00:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv new file mode 100644 index 0000000..38856eb --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv @@ -0,0 +1,46 @@ +"user_id","course_id","enrolled_at" +2,1,2023-01-20 00:00:00.000 +0300 +3,2,2023-03-05 00:00:00.000 +0300 +6,1,2023-01-06 00:00:00.000 +0300 +6,2,2023-01-10 00:00:00.000 +0300 +6,4,2023-02-05 00:00:00.000 +0300 +7,1,2023-01-13 00:00:00.000 +0300 +7,3,2023-03-10 00:00:00.000 +0300 +8,3,2023-01-22 00:00:00.000 +0300 +13,2,2023-01-19 00:00:00.000 +0300 +13,4,2023-02-20 00:00:00.000 +0300 +16,2,2023-02-16 00:00:00.000 +0300 +16,4,2023-02-16 00:00:00.000 +0300 +19,1,2023-04-11 00:00:00.000 +0300 +19,2,2023-04-11 00:00:00.000 +0300 +2,3,2023-03-15 00:00:00.000 +0300 +4,2,2023-03-10 00:00:00.000 +0300 +7,2,2023-02-01 00:00:00.000 +0300 +9,1,2023-02-15 00:00:00.000 +0300 +18,4,2023-04-05 00:00:00.000 +0300 +4,3,2023-04-01 00:00:00.000 +0300 +8,1,2023-01-22 00:00:00.000 +0300 +8,2,2023-01-22 00:00:00.000 +0300 +9,4,2023-02-12 00:00:00.000 +0300 +10,1,2023-04-16 00:00:00.000 +0300 +18,1,2023-04-02 00:00:00.000 +0300 +3,1,2023-02-25 00:00:00.000 +0300 +6,3,2023-03-08 00:00:00.000 +0300 +1,2,2023-02-01 00:00:00.000 +0300 +11,2,2023-04-21 00:00:00.000 +0300 +14,2,2023-01-09 00:00:00.000 +0300 +14,3,2023-01-09 00:00:00.000 +0300 +14,5,2023-03-20 00:00:00.000 +0300 +17,3,2023-03-22 00:00:00.000 +0300 +20,2,2023-04-19 00:00:00.000 +0300 +12,3,2023-03-10 00:00:00.000 +0300 +1,1,2023-01-15 00:00:00.000 +0300 +12,1,2023-01-16 00:00:00.000 +0300 +14,1,2023-01-09 00:00:00.000 +0300 +14,4,2023-02-01 00:00:00.000 +0300 +15,1,2023-01-11 00:00:00.000 +0300 +15,3,2023-03-05 00:00:00.000 +0300 +17,5,2023-03-25 00:00:00.000 +0300 +5,5,2023-03-20 00:00:00.000 +0300 +1,4,2023-02-10 00:00:00.000 +0300 +15,2,2023-02-01 00:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv new file mode 100644 index 0000000..3a43417 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv @@ -0,0 +1,59 @@ +"user_id","lesson_id","course_id","viewed_at" +1,2,1,2023-01-16 10:15:00.000 +0300 +1,3,1,2023-01-17 09:00:00.000 +0300 +1,4,1,2023-01-17 09:30:00.000 +0300 +1,4,1,2023-01-18 10:00:00.000 +0300 +1,6,2,2023-02-02 14:00:00.000 +0300 +1,13,4,2023-02-15 10:30:00.000 +0300 +5,16,5,2023-03-22 11:00:00.000 +0300 +12,2,1,2023-01-17 10:20:00.000 +0300 +12,3,1,2023-04-10 11:30:00.000 +0300 +14,2,1,2023-01-09 08:15:00.000 +0300 +14,4,1,2023-01-10 10:30:00.000 +0300 +14,6,2,2023-01-10 14:00:00.000 +0300 +14,9,3,2023-01-10 08:00:00.000 +0300 +1,4,1,2023-01-17 11:00:00.000 +0300 +12,9,3,2023-03-12 10:00:00.000 +0300 +14,3,1,2023-01-10 10:00:00.000 +0300 +14,7,2,2023-01-11 10:00:00.000 +0300 +14,8,2,2023-01-12 11:00:00.000 +0300 +1,12,4,2023-02-15 10:00:00.000 +0300 +12,1,1,2023-01-17 10:00:00.000 +0300 +14,5,1,2023-01-11 09:00:00.000 +0300 +1,1,1,2023-01-16 10:00:00.000 +0300 +5,15,5,2023-03-21 10:00:00.000 +0300 +12,1,1,2023-04-10 11:00:00.000 +0300 +14,1,1,2023-01-09 08:00:00.000 +0300 +3,6,2,2023-03-06 12:00:00.000 +0300 +4,9,3,2023-04-02 10:00:00.000 +0300 +4,6,2,2023-03-12 14:00:00.000 +0300 +4,7,2,2023-03-13 09:00:00.000 +0300 +6,3,1,2023-01-08 09:00:00.000 +0300 +6,6,2,2023-01-11 14:00:00.000 +0300 +6,7,2,2023-01-12 10:00:00.000 +0300 +6,8,2,2023-01-13 11:00:00.000 +0300 +6,9,3,2023-03-09 11:00:00.000 +0300 +7,9,3,2023-03-11 14:00:00.000 +0300 +2,9,3,2023-03-16 10:00:00.000 +0300 +2,10,3,2023-03-16 10:30:00.000 +0300 +3,2,1,2023-02-26 11:20:00.000 +0300 +4,10,3,2023-04-03 10:00:00.000 +0300 +6,2,1,2023-01-07 10:15:00.000 +0300 +6,4,1,2023-01-08 09:30:00.000 +0300 +6,13,4,2023-02-06 09:30:00.000 +0300 +7,2,1,2023-01-14 10:20:00.000 +0300 +7,3,1,2023-01-15 09:00:00.000 +0300 +7,10,3,2023-03-11 14:30:00.000 +0300 +9,13,4,2023-02-13 10:30:00.000 +0300 +13,6,2,2023-01-20 14:00:00.000 +0300 +13,7,2,2023-01-21 10:00:00.000 +0300 +2,1,1,2023-01-21 09:00:00.000 +0300 +6,1,1,2023-01-07 10:00:00.000 +0300 +6,14,4,2023-02-07 10:00:00.000 +0300 +7,1,1,2023-01-14 10:00:00.000 +0300 +9,12,4,2023-02-13 10:00:00.000 +0300 +9,1,1,2023-02-16 11:00:00.000 +0300 +3,1,1,2023-02-26 11:00:00.000 +0300 +6,5,1,2023-01-09 10:00:00.000 +0300 +6,12,4,2023-02-06 09:00:00.000 +0300 +13,12,4,2023-02-21 09:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql index f2045ef..62c94cb 100644 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql @@ -1,3 +1,5 @@ +-- DIM + --DROP TABLE IF EXISTS mnoskov.core_dim_user ON CLUSTER clickhouse_cluster SYNC; CREATE TABLE mnoskov.core_dim_user ON CLUSTER clickhouse_cluster @@ -58,4 +60,38 @@ ENGINE=ReplicatedMergeTree( '/clickhouse/tables/{shard}/mnoskov/core_dim_date', '{replica}' ) -ORDER BY date; \ No newline at end of file +ORDER BY date; + + +-- FACT + +--DROP TABLE IF EXISTS mnoskov.core_fact_lesson_views; +CREATE TABLE mnoskov.core_fact_lesson_views +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + lesson_id UInt64, + course_id UInt64, + viewed_at DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_fact_lesson_views', + '{replica}' +) +PARTITION BY toYYYYMM(viewed_at) +ORDER BY (viewed_at, user_id); -- я если честно не знаю как с этой таблицей поступить в ORDER BY. Наставьте меня на путь истиный. + +--DROP TABLE IF EXISTS mnoskov.core_enrollments; +CREATE TABLE mnoskov.core_enrollments +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + course_id UInt64, + enrolled_at DateTime +) +ENGINE=ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/core_enrollments', + '{replica}' +) +PARTITION BY toYYYYMM(enrolled_at) +ORDER BY (enrolled_at, user_id); \ No newline at end of file From 135ee82c14d9503b53e0aef44baba38c07963dbe Mon Sep 17 00:00:00 2001 From: mnoskov Date: Tue, 19 May 2026 15:50:11 +0300 Subject: [PATCH 111/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=202:=20=D0=9F=D0=BE=D0=B4=D0=B3?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B2=D1=8C=20=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=83=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D1=8B=20lesson=5Fpopularity=5Fsummary.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csv files/fact_lesson_views_202605191313.csv | 59 ----------- .../csv files/fact_lesson_views_202605191356.csv | 59 +++++++++++ .../task_2_lesson_popularity_summary.sql | 99 +++++++++++++++++++ 3 files changed, 158 insertions(+), 59 deletions(-) delete mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv deleted file mode 100644 index 3a43417..0000000 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191313.csv +++ /dev/null @@ -1,59 +0,0 @@ -"user_id","lesson_id","course_id","viewed_at" -1,2,1,2023-01-16 10:15:00.000 +0300 -1,3,1,2023-01-17 09:00:00.000 +0300 -1,4,1,2023-01-17 09:30:00.000 +0300 -1,4,1,2023-01-18 10:00:00.000 +0300 -1,6,2,2023-02-02 14:00:00.000 +0300 -1,13,4,2023-02-15 10:30:00.000 +0300 -5,16,5,2023-03-22 11:00:00.000 +0300 -12,2,1,2023-01-17 10:20:00.000 +0300 -12,3,1,2023-04-10 11:30:00.000 +0300 -14,2,1,2023-01-09 08:15:00.000 +0300 -14,4,1,2023-01-10 10:30:00.000 +0300 -14,6,2,2023-01-10 14:00:00.000 +0300 -14,9,3,2023-01-10 08:00:00.000 +0300 -1,4,1,2023-01-17 11:00:00.000 +0300 -12,9,3,2023-03-12 10:00:00.000 +0300 -14,3,1,2023-01-10 10:00:00.000 +0300 -14,7,2,2023-01-11 10:00:00.000 +0300 -14,8,2,2023-01-12 11:00:00.000 +0300 -1,12,4,2023-02-15 10:00:00.000 +0300 -12,1,1,2023-01-17 10:00:00.000 +0300 -14,5,1,2023-01-11 09:00:00.000 +0300 -1,1,1,2023-01-16 10:00:00.000 +0300 -5,15,5,2023-03-21 10:00:00.000 +0300 -12,1,1,2023-04-10 11:00:00.000 +0300 -14,1,1,2023-01-09 08:00:00.000 +0300 -3,6,2,2023-03-06 12:00:00.000 +0300 -4,9,3,2023-04-02 10:00:00.000 +0300 -4,6,2,2023-03-12 14:00:00.000 +0300 -4,7,2,2023-03-13 09:00:00.000 +0300 -6,3,1,2023-01-08 09:00:00.000 +0300 -6,6,2,2023-01-11 14:00:00.000 +0300 -6,7,2,2023-01-12 10:00:00.000 +0300 -6,8,2,2023-01-13 11:00:00.000 +0300 -6,9,3,2023-03-09 11:00:00.000 +0300 -7,9,3,2023-03-11 14:00:00.000 +0300 -2,9,3,2023-03-16 10:00:00.000 +0300 -2,10,3,2023-03-16 10:30:00.000 +0300 -3,2,1,2023-02-26 11:20:00.000 +0300 -4,10,3,2023-04-03 10:00:00.000 +0300 -6,2,1,2023-01-07 10:15:00.000 +0300 -6,4,1,2023-01-08 09:30:00.000 +0300 -6,13,4,2023-02-06 09:30:00.000 +0300 -7,2,1,2023-01-14 10:20:00.000 +0300 -7,3,1,2023-01-15 09:00:00.000 +0300 -7,10,3,2023-03-11 14:30:00.000 +0300 -9,13,4,2023-02-13 10:30:00.000 +0300 -13,6,2,2023-01-20 14:00:00.000 +0300 -13,7,2,2023-01-21 10:00:00.000 +0300 -2,1,1,2023-01-21 09:00:00.000 +0300 -6,1,1,2023-01-07 10:00:00.000 +0300 -6,14,4,2023-02-07 10:00:00.000 +0300 -7,1,1,2023-01-14 10:00:00.000 +0300 -9,12,4,2023-02-13 10:00:00.000 +0300 -9,1,1,2023-02-16 11:00:00.000 +0300 -3,1,1,2023-02-26 11:00:00.000 +0300 -6,5,1,2023-01-09 10:00:00.000 +0300 -6,12,4,2023-02-06 09:00:00.000 +0300 -13,12,4,2023-02-21 09:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv new file mode 100644 index 0000000..7be0742 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv @@ -0,0 +1,59 @@ +"user_id","lesson_id","course_id","viewed_at" +3,6,2,2023-03-06 12:00:00.000 +4,9,3,2023-04-02 10:00:00.000 +4,6,2,2023-03-12 14:00:00.000 +4,7,2,2023-03-13 09:00:00.000 +6,3,1,2023-01-08 09:00:00.000 +6,6,2,2023-01-11 14:00:00.000 +6,7,2,2023-01-12 10:00:00.000 +6,8,2,2023-01-13 11:00:00.000 +6,9,3,2023-03-09 11:00:00.000 +7,9,3,2023-03-11 14:00:00.000 +2,9,3,2023-03-16 10:00:00.000 +2,10,3,2023-03-16 10:30:00.000 +3,2,1,2023-02-26 11:20:00.000 +4,10,3,2023-04-03 10:00:00.000 +6,2,1,2023-01-07 10:15:00.000 +6,4,1,2023-01-08 09:30:00.000 +6,13,4,2023-02-06 09:30:00.000 +7,2,1,2023-01-14 10:20:00.000 +7,3,1,2023-01-15 09:00:00.000 +7,10,3,2023-03-11 14:30:00.000 +9,13,4,2023-02-13 10:30:00.000 +13,6,2,2023-01-20 14:00:00.000 +13,7,2,2023-01-21 10:00:00.000 +2,1,1,2023-01-21 09:00:00.000 +6,1,1,2023-01-07 10:00:00.000 +6,14,4,2023-02-07 10:00:00.000 +7,1,1,2023-01-14 10:00:00.000 +9,12,4,2023-02-13 10:00:00.000 +9,1,1,2023-02-16 11:00:00.000 +3,1,1,2023-02-26 11:00:00.000 +6,5,1,2023-01-09 10:00:00.000 +6,12,4,2023-02-06 09:00:00.000 +13,12,4,2023-02-21 09:00:00.000 +1,2,1,2023-01-16 10:15:00.000 +1,3,1,2023-01-17 09:00:00.000 +1,4,1,2023-01-17 09:30:00.000 +1,4,1,2023-01-18 10:00:00.000 +1,6,2,2023-02-02 14:00:00.000 +1,13,4,2023-02-15 10:30:00.000 +5,16,5,2023-03-22 11:00:00.000 +12,2,1,2023-01-17 10:20:00.000 +12,3,1,2023-04-10 11:30:00.000 +14,2,1,2023-01-09 08:15:00.000 +14,4,1,2023-01-10 10:30:00.000 +14,6,2,2023-01-10 14:00:00.000 +14,9,3,2023-01-10 08:00:00.000 +1,4,1,2023-01-17 11:00:00.000 +12,9,3,2023-03-12 10:00:00.000 +14,3,1,2023-01-10 10:00:00.000 +14,7,2,2023-01-11 10:00:00.000 +14,8,2,2023-01-12 11:00:00.000 +1,12,4,2023-02-15 10:00:00.000 +12,1,1,2023-01-17 10:00:00.000 +14,5,1,2023-01-11 09:00:00.000 +1,1,1,2023-01-16 10:00:00.000 +5,15,5,2023-03-21 10:00:00.000 +12,1,1,2023-04-10 11:00:00.000 +14,1,1,2023-01-09 08:00:00.000 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql new file mode 100644 index 0000000..35698e3 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql @@ -0,0 +1,99 @@ +-- Вариант 1(не работает): +CREATE TABLE mnoskov.lesson_popularity_summary +ON CLUSTER clickhouse_cluster +( + lesson_id UInt64, + lesson_title String, + course_id UInt64, + course_title String, + total_views UInt64, + unique_users UInt64, + first_view DateTime, + last_view DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/lesson_popularity_summary', + '{replica}' +) +ORDER BY lesson_id; + +CREATE MATERIALIZED VIEW mnoskov.mv_lesson_popularity_summary +ON CLUSTER clickhouse_cluster +TO mnoskov.lesson_popularity_summary +AS +WITH result_flv AS ( + SELECT + lesson_id, + count() AS total_views, + uniq(user_id) AS unique_users, + min(viewed_at) AS first_view, + max(viewed_at) AS last_view + FROM + mnoskov.core_fact_lesson_views flv + GROUP BY + lesson_id +) +SELECT + cdl.lesson_id AS lesson_id, + cdl.title AS lesson_title, + cdc.course_id AS course_id, + cdc.title AS course_title, + rflv.total_views AS total_views, + rflv.unique_users AS unique_users, + rflv.first_view AS first_view, + rflv.last_view AS last_view +FROM + mnoskov.core_dim_lesson cdl +JOIN + mnoskov.core_dim_course cdc ON cdl.course_id = cdc.course_id +JOIN + result_flv rflv ON cdl.lesson_id = rflv.lesson_id; + + +-- Вариант 2(не работает): +CREATE TABLE mnoskov.lesson_popularity_summary +ON CLUSTER clickhouse_cluster +( + lesson_id UInt64, + lesson_title String, + course_id UInt64, + course_title String, + total_views AggregateFunction(count), + unique_users AggregateFunction(uniq, UInt64), + first_view AggregateFunction(min, DateTime), + last_view AggregateFunction(max, DateTime) +) +ENGINE AggregatingMergeTree +ORDER BY lesson_id; + +CREATE MATERIALIZED VIEW mnoskov.mv_lesson_popularity_summary +ON CLUSTER clickhouse_cluster +TO mnoskov.lesson_popularity_summary +AS +WITH result_flv AS ( + SELECT + lesson_id, + countState() AS total_views, + uniqState(user_id) AS unique_users, + minState(viewed_at) AS first_view, + maxState(viewed_at) AS last_view + FROM + mnoskov.core_fact_lesson_views flv + GROUP BY + lesson_id +) +SELECT + cdl.lesson_id AS lesson_id, + cdl.title AS lesson_title, + cdc.course_id AS course_id, + cdc.title AS course_title, + rflv.total_views AS total_views, + rflv.unique_users AS unique_users, + rflv.first_view AS first_view, + rflv.last_view AS last_view +FROM + mnoskov.core_dim_lesson cdl +JOIN + mnoskov.core_dim_course cdc ON cdl.course_id = cdc.course_id +JOIN + result_flv rflv ON cdl.lesson_id = rflv.lesson_id; \ No newline at end of file From b95fc3ba9e8ec3ae57b50bbaa37994e8e12f0aea Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 20 May 2026 11:11:23 +0300 Subject: [PATCH 112/123] =?UTF-8?q?=D0=98=D0=97=D0=9C=D0=95=D0=9D=D0=95?= =?UTF-8?q?=D0=9D=D0=9E=20-=20=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=202:=20=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=D1=8C=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D1=83=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=8B?= =?UTF-8?q?=20lesson=5Fpopularity=5Fsummary.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_2_lesson_popularity_summary.sql | 173 ++++++++++-------- 1 file changed, 101 insertions(+), 72 deletions(-) diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql index 35698e3..046c023 100644 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql @@ -1,56 +1,4 @@ --- Вариант 1(не работает): -CREATE TABLE mnoskov.lesson_popularity_summary -ON CLUSTER clickhouse_cluster -( - lesson_id UInt64, - lesson_title String, - course_id UInt64, - course_title String, - total_views UInt64, - unique_users UInt64, - first_view DateTime, - last_view DateTime -) -ENGINE = ReplicatedMergeTree( - '/clickhouse/tables/{shard}/mnoskov/lesson_popularity_summary', - '{replica}' -) -ORDER BY lesson_id; - -CREATE MATERIALIZED VIEW mnoskov.mv_lesson_popularity_summary -ON CLUSTER clickhouse_cluster -TO mnoskov.lesson_popularity_summary -AS -WITH result_flv AS ( - SELECT - lesson_id, - count() AS total_views, - uniq(user_id) AS unique_users, - min(viewed_at) AS first_view, - max(viewed_at) AS last_view - FROM - mnoskov.core_fact_lesson_views flv - GROUP BY - lesson_id -) -SELECT - cdl.lesson_id AS lesson_id, - cdl.title AS lesson_title, - cdc.course_id AS course_id, - cdc.title AS course_title, - rflv.total_views AS total_views, - rflv.unique_users AS unique_users, - rflv.first_view AS first_view, - rflv.last_view AS last_view -FROM - mnoskov.core_dim_lesson cdl -JOIN - mnoskov.core_dim_course cdc ON cdl.course_id = cdc.course_id -JOIN - result_flv rflv ON cdl.lesson_id = rflv.lesson_id; - - --- Вариант 2(не работает): +--DROP TABLE mnoskov.lesson_popularity_summary ON CLUSTER clickhouse_cluster sync; CREATE TABLE mnoskov.lesson_popularity_summary ON CLUSTER clickhouse_cluster ( @@ -66,34 +14,115 @@ ON CLUSTER clickhouse_cluster ENGINE AggregatingMergeTree ORDER BY lesson_id; +--DROP VIEW mnoskov.mv_lesson_popularity_summary ON CLUSTER clickhouse_cluster SYNC; CREATE MATERIALIZED VIEW mnoskov.mv_lesson_popularity_summary ON CLUSTER clickhouse_cluster TO mnoskov.lesson_popularity_summary AS -WITH result_flv AS ( - SELECT - lesson_id, - countState() AS total_views, - uniqState(user_id) AS unique_users, - minState(viewed_at) AS first_view, - maxState(viewed_at) AS last_view - FROM - mnoskov.core_fact_lesson_views flv - GROUP BY - lesson_id -) SELECT cdl.lesson_id AS lesson_id, cdl.title AS lesson_title, cdc.course_id AS course_id, cdc.title AS course_title, - rflv.total_views AS total_views, - rflv.unique_users AS unique_users, - rflv.first_view AS first_view, - rflv.last_view AS last_view + countIfState(flv.user_id != 0) AS total_views, -- я вообще ахуел когда узнал что такое существует(я про If у агрегаций). Я наткнулся на это когда осознал что кликхаус в join не ставит null, а ставит значение по умолчанию + uniqIfState(flv.user_id, flv.user_id != 0) AS unique_users, + minIfState(flv.viewed_at, flv.user_id != 0) AS first_view, + maxIfState(flv.viewed_at, flv.user_id != 0) AS last_view FROM - mnoskov.core_dim_lesson cdl + mnoskov.core_fact_lesson_views flv +RIGHT JOIN + mnoskov.core_dim_lesson cdl ON flv.lesson_id = cdl.lesson_id -- поидее нужно большую таблицу ставить в левую часть JOIN, но если я тут так сделаю с фактами, то запрос кореектно не будет работать JOIN mnoskov.core_dim_course cdc ON cdl.course_id = cdc.course_id -JOIN - result_flv rflv ON cdl.lesson_id = rflv.lesson_id; \ No newline at end of file +GROUP BY + cdl.lesson_id, cdl.title, cdc.course_id, cdc.title; + + +SELECT * FROM mnoskov.core_fact_lesson_views; + +SELECT + lesson_id, + any(lesson_title), + any(course_id), + any(course_title), + countMerge(total_views) AS total_views, + uniqMerge(unique_users) AS uniq_users, + minMerge(first_view) AS first_view, + maxMerge(last_view) AS last_view +FROM + mnoskov.lesson_popularity_summary +GROUP BY + lesson_id +ORDER BY + lesson_id; +-- ЕБАТЬ КАК Я АХУЕЛ КОГДА ДОПЕР ДО ТОГО ЧТО ЭТО НУЖНО ЧИТАТЬ ТАКИМ СПОСОБОМ А НЕ ПРОСТО ЧЕРЕЗ ЗВЕЗДОЧКУ. У меня MV ебать сколько изменений потерпела чтобы заработать, а проблема возможно изначально была в чтении. +-- Правда оно читается через раз, то есть результат то нет, но думаю это проблема движка AggregatingMergeTree. +-- Это получается чтобы был удобный вывод, теперь поверх этого селекта можно сделать обычную VIEW? + +-- Заполненния и очищения: +TRUNCATE TABLE mnoskov.core_fact_lesson_views +ON CLUSTER clickhouse_cluster sync; + +TRUNCATE TABLE mnoskov.lesson_popularity_summary +ON CLUSTER clickhouse_cluster sync; + +INSERT INTO mnoskov.core_fact_lesson_views (user_id, lesson_id, course_id, viewed_at) +VALUES +(3, 6, 2, '2023-03-06 12:00:00'), +(4, 9, 3, '2023-04-02 10:00:00'), +(4, 6, 2, '2023-03-12 14:00:00'), +(4, 7, 2, '2023-03-13 09:00:00'), +(6, 3, 1, '2023-01-08 09:00:00'), +(6, 6, 2, '2023-01-11 14:00:00'), +(6, 7, 2, '2023-01-12 10:00:00'), +(6, 8, 2, '2023-01-13 11:00:00'), +(6, 9, 3, '2023-03-09 11:00:00'), +(7, 9, 3, '2023-03-11 14:00:00'), +(2, 9, 3, '2023-03-16 10:00:00'), +(2, 10, 3, '2023-03-16 10:30:00'), +(3, 2, 1, '2023-02-26 11:20:00'), +(4, 10, 3, '2023-04-03 10:00:00'), +(6, 2, 1, '2023-01-07 10:15:00'), +(6, 4, 1, '2023-01-08 09:30:00'), +(6, 13, 4, '2023-02-06 09:30:00'), +(7, 2, 1, '2023-01-14 10:20:00'), +(7, 3, 1, '2023-01-15 09:00:00'), +(7, 10, 3, '2023-03-11 14:30:00'), +(9, 13, 4, '2023-02-13 10:30:00'), +(13, 6, 2, '2023-01-20 14:00:00'), +(13, 7, 2, '2023-01-21 10:00:00'), +(2, 1, 1, '2023-01-21 09:00:00'), +(6, 1, 1, '2023-01-07 10:00:00'), +(6, 14, 4, '2023-02-07 10:00:00'), +(7, 1, 1, '2023-01-14 10:00:00'), +(9, 12, 4, '2023-02-13 10:00:00'), +(9, 1, 1, '2023-02-16 11:00:00'), +(3, 1, 1, '2023-02-26 11:00:00'), +(6, 5, 1, '2023-01-09 10:00:00'), +(6, 12, 4, '2023-02-06 09:00:00'), +(13, 12, 4, '2023-02-21 09:00:00'), +(1, 2, 1, '2023-01-16 10:15:00'), +(1, 3, 1, '2023-01-17 09:00:00'), +(1, 4, 1, '2023-01-17 09:30:00'), +(1, 4, 1, '2023-01-18 10:00:00'), +(1, 6, 2, '2023-02-02 14:00:00'), +(1, 13, 4, '2023-02-15 10:30:00'), +(5, 16, 5, '2023-03-22 11:00:00'), +(12, 2, 1, '2023-01-17 10:20:00'), +(12, 3, 1, '2023-04-10 11:30:00'), +(14, 2, 1, '2023-01-09 08:15:00'), +(14, 4, 1, '2023-01-10 10:30:00'), +(14, 6, 2, '2023-01-10 14:00:00'), +(14, 9, 3, '2023-01-10 08:00:00'), +(1, 4, 1, '2023-01-17 11:00:00'), +(12, 9, 3, '2023-03-12 10:00:00'), +(14, 3, 1, '2023-01-10 10:00:00'), +(14, 7, 2, '2023-01-11 10:00:00'), +(14, 8, 2, '2023-01-12 11:00:00'), +(1, 12, 4, '2023-02-15 10:00:00'), +(12, 1, 1, '2023-01-17 10:00:00'), +(14, 5, 1, '2023-01-11 09:00:00'), +(1, 1, 1, '2023-01-16 10:00:00'), +(5, 15, 5, '2023-03-21 10:00:00'), +(12, 1, 1, '2023-04-10 11:00:00'), +(14, 1, 1, '2023-01-09 08:00:00'); \ No newline at end of file From ba35f28721aa335c3a4d56cf417b72894c73d7ca Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 20 May 2026 11:47:19 +0300 Subject: [PATCH 113/123] =?UTF-8?q?Redact=20csv's=20for:=20=D0=9F=D0=B5?= =?UTF-8?q?=D1=82-=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BD=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../csv files/{dim_course_202605191311.csv => dim_course_202605201139.csv} | 10 ++-- .../csv files/dim_user_202605191313.csv | 21 --------- .../csv files/dim_user_202605201142.csv | 21 +++++++++ .../csv files/enrollments_202605191313.csv | 46 ------------------- .../csv files/enrollments_202605201134.csv | 46 +++++++++++++++++++ 5 files changed, 72 insertions(+), 72 deletions(-) rename mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/{dim_course_202605191311.csv => dim_course_202605201139.csv} (56%) delete mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv delete mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv similarity index 56% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv rename to mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv index 0bae8f3..e998233 100644 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605191311.csv +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv @@ -1,6 +1,6 @@ "course_id","title","category","created_at" -2,Python для анализа данных,programming,2023-01-15 00:00:00.000 +0300 -3,BI с нуля,business,2023-03-01 00:00:00.000 +0300 -4,Excel для аналитиков,business,2023-02-01 00:00:00.000 +0300 -1,SQL для начинающих,data,2023-01-01 00:00:00.000 +0300 -5,Основы машинного обучения,data,2023-03-15 00:00:00.000 +0300 +1,SQL для начинающих,data,2023-01-01 00:00:00.000 +5,Основы машинного обучения,data,2023-03-15 00:00:00.000 +2,Python для анализа данных,programming,2023-01-15 00:00:00.000 +3,BI с нуля,business,2023-03-01 00:00:00.000 +4,Excel для аналитиков,business,2023-02-01 00:00:00.000 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv deleted file mode 100644 index 529a745..0000000 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605191313.csv +++ /dev/null @@ -1,21 +0,0 @@ -"user_id","name","age","email","registration_date" -1,Alice,25,alice@mail.com,2023-01-10 00:00:00.000 +0300 -5,Ethan,35,ethan@gmail.com,2023-03-10 00:00:00.000 +0300 -11,Kelly,23,kelly@gmail.com,2023-04-20 00:00:00.000 +0300 -12,Leo,31,leo@mail.com,2023-01-15 00:00:00.000 +0300 -14,Nina,28,nina@mail.com,2023-01-08 00:00:00.000 +0300 -15,Oscar,33,oscar@gmail.com,2023-01-10 00:00:00.000 +0300 -17,Quinn,24,quinn@gmail.com,2023-03-20 00:00:00.000 +0300 -20,Tina,25,tina@mail.com,2023-04-18 00:00:00.000 +0300 -2,Bob,30,bob@gmail.com,2023-02-05 00:00:00.000 +0300 -3,Charlie,22,charlie@mail.com,2023-02-20 00:00:00.000 +0300 -4,Diana,28,diana@mail.com,2023-03-01 00:00:00.000 +0300 -6,Frank,29,frank@mail.com,2023-01-05 00:00:00.000 +0300 -7,Grace,26,grace@gmail.com,2023-01-12 00:00:00.000 +0300 -8,Henry,32,henry@mail.com,2023-01-20 00:00:00.000 +0300 -9,Iris,24,iris@gmail.com,2023-02-10 00:00:00.000 +0300 -10,Jack,27,jack@mail.com,2023-04-15 00:00:00.000 +0300 -13,Mia,26,mia@gmail.com,2023-01-18 00:00:00.000 +0300 -16,Paul,29,paul@mail.com,2023-02-15 00:00:00.000 +0300 -18,Rita,27,rita@mail.com,2023-04-01 00:00:00.000 +0300 -19,Sam,30,sam@gmail.com,2023-04-10 00:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv new file mode 100644 index 0000000..a31ef6a --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv @@ -0,0 +1,21 @@ +"user_id","name","age","email","registration_date" +2,Bob,30,bob@gmail.com,2023-02-05 00:00:00.000 +3,Charlie,22,charlie@mail.com,2023-02-20 00:00:00.000 +4,Diana,28,diana@mail.com,2023-03-01 00:00:00.000 +6,Frank,29,frank@mail.com,2023-01-05 00:00:00.000 +7,Grace,26,grace@gmail.com,2023-01-12 00:00:00.000 +8,Henry,32,henry@mail.com,2023-01-20 00:00:00.000 +9,Iris,24,iris@gmail.com,2023-02-10 00:00:00.000 +10,Jack,27,jack@mail.com,2023-04-15 00:00:00.000 +13,Mia,26,mia@gmail.com,2023-01-18 00:00:00.000 +16,Paul,29,paul@mail.com,2023-02-15 00:00:00.000 +18,Rita,27,rita@mail.com,2023-04-01 00:00:00.000 +19,Sam,30,sam@gmail.com,2023-04-10 00:00:00.000 +1,Alice,25,alice@mail.com,2023-01-10 00:00:00.000 +5,Ethan,35,ethan@gmail.com,2023-03-10 00:00:00.000 +11,Kelly,23,kelly@gmail.com,2023-04-20 00:00:00.000 +12,Leo,31,leo@mail.com,2023-01-15 00:00:00.000 +14,Nina,28,nina@mail.com,2023-01-08 00:00:00.000 +15,Oscar,33,oscar@gmail.com,2023-01-10 00:00:00.000 +17,Quinn,24,quinn@gmail.com,2023-03-20 00:00:00.000 +20,Tina,25,tina@mail.com,2023-04-18 00:00:00.000 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv deleted file mode 100644 index 38856eb..0000000 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605191313.csv +++ /dev/null @@ -1,46 +0,0 @@ -"user_id","course_id","enrolled_at" -2,1,2023-01-20 00:00:00.000 +0300 -3,2,2023-03-05 00:00:00.000 +0300 -6,1,2023-01-06 00:00:00.000 +0300 -6,2,2023-01-10 00:00:00.000 +0300 -6,4,2023-02-05 00:00:00.000 +0300 -7,1,2023-01-13 00:00:00.000 +0300 -7,3,2023-03-10 00:00:00.000 +0300 -8,3,2023-01-22 00:00:00.000 +0300 -13,2,2023-01-19 00:00:00.000 +0300 -13,4,2023-02-20 00:00:00.000 +0300 -16,2,2023-02-16 00:00:00.000 +0300 -16,4,2023-02-16 00:00:00.000 +0300 -19,1,2023-04-11 00:00:00.000 +0300 -19,2,2023-04-11 00:00:00.000 +0300 -2,3,2023-03-15 00:00:00.000 +0300 -4,2,2023-03-10 00:00:00.000 +0300 -7,2,2023-02-01 00:00:00.000 +0300 -9,1,2023-02-15 00:00:00.000 +0300 -18,4,2023-04-05 00:00:00.000 +0300 -4,3,2023-04-01 00:00:00.000 +0300 -8,1,2023-01-22 00:00:00.000 +0300 -8,2,2023-01-22 00:00:00.000 +0300 -9,4,2023-02-12 00:00:00.000 +0300 -10,1,2023-04-16 00:00:00.000 +0300 -18,1,2023-04-02 00:00:00.000 +0300 -3,1,2023-02-25 00:00:00.000 +0300 -6,3,2023-03-08 00:00:00.000 +0300 -1,2,2023-02-01 00:00:00.000 +0300 -11,2,2023-04-21 00:00:00.000 +0300 -14,2,2023-01-09 00:00:00.000 +0300 -14,3,2023-01-09 00:00:00.000 +0300 -14,5,2023-03-20 00:00:00.000 +0300 -17,3,2023-03-22 00:00:00.000 +0300 -20,2,2023-04-19 00:00:00.000 +0300 -12,3,2023-03-10 00:00:00.000 +0300 -1,1,2023-01-15 00:00:00.000 +0300 -12,1,2023-01-16 00:00:00.000 +0300 -14,1,2023-01-09 00:00:00.000 +0300 -14,4,2023-02-01 00:00:00.000 +0300 -15,1,2023-01-11 00:00:00.000 +0300 -15,3,2023-03-05 00:00:00.000 +0300 -17,5,2023-03-25 00:00:00.000 +0300 -5,5,2023-03-20 00:00:00.000 +0300 -1,4,2023-02-10 00:00:00.000 +0300 -15,2,2023-02-01 00:00:00.000 +0300 diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv new file mode 100644 index 0000000..e3a32cd --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv @@ -0,0 +1,46 @@ +"user_id","enrolled_at","course_id" +2,2023-01-20 00:00:00.000,1 +3,2023-03-05 00:00:00.000,2 +6,2023-01-06 00:00:00.000,1 +6,2023-01-10 00:00:00.000,2 +6,2023-02-05 00:00:00.000,4 +7,2023-01-13 00:00:00.000,1 +7,2023-03-10 00:00:00.000,3 +8,2023-01-22 00:00:00.000,3 +13,2023-01-19 00:00:00.000,2 +13,2023-02-20 00:00:00.000,4 +16,2023-02-16 00:00:00.000,2 +16,2023-02-16 00:00:00.000,4 +19,2023-04-11 00:00:00.000,1 +19,2023-04-11 00:00:00.000,2 +2,2023-03-15 00:00:00.000,3 +4,2023-03-10 00:00:00.000,2 +7,2023-02-01 00:00:00.000,2 +9,2023-02-15 00:00:00.000,1 +18,2023-04-05 00:00:00.000,4 +4,2023-04-01 00:00:00.000,3 +8,2023-01-22 00:00:00.000,1 +8,2023-01-22 00:00:00.000,2 +9,2023-02-12 00:00:00.000,4 +10,2023-04-16 00:00:00.000,1 +18,2023-04-02 00:00:00.000,1 +3,2023-02-25 00:00:00.000,1 +6,2023-03-08 00:00:00.000,3 +1,2023-02-01 00:00:00.000,2 +11,2023-04-21 00:00:00.000,2 +14,2023-01-09 00:00:00.000,2 +14,2023-01-09 00:00:00.000,3 +14,2023-03-20 00:00:00.000,5 +17,2023-03-22 00:00:00.000,3 +20,2023-04-19 00:00:00.000,2 +12,2023-03-10 00:00:00.000,3 +1,2023-01-15 00:00:00.000,1 +12,2023-01-16 00:00:00.000,1 +14,2023-01-09 00:00:00.000,1 +14,2023-02-01 00:00:00.000,4 +15,2023-01-11 00:00:00.000,1 +15,2023-03-05 00:00:00.000,3 +17,2023-03-25 00:00:00.000,5 +5,2023-03-20 00:00:00.000,5 +1,2023-02-10 00:00:00.000,4 +15,2023-02-01 00:00:00.000,2 From 1da82b68771bff4441a6c9e72887027743804e74 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 20 May 2026 13:41:30 +0300 Subject: [PATCH 114/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=203:=20=D0=A1=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=B9=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20ina?= =?UTF-8?q?ctive=5Fusers=5Fsummary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_3_inactive_users_summary.sql | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql new file mode 100644 index 0000000..95b7ac1 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql @@ -0,0 +1,75 @@ +--DROP TABLE mnoskov.inactive_users_summary ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.inactive_users_summary +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + name String, + email String, + age UInt8, + registered_courses_count UInt8, + registration_date DateTime +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/inactive_users_summary', + '{replica}' +) +ORDER BY user_id; + +--DROP VIEW mnoskov.mv_inactive_users_summary ON CLUSTER clickhouse_cluster sync; +CREATE MATERIALIZED VIEW mnoskov.mv_inactive_users_summary +ON CLUSTER clickhouse_cluster +TO mnoskov.inactive_users_summary +AS +SELECT + u.user_id AS user_id, + u.name AS name, + u.email AS email, + u.age AS age, + e.registered_courses_count AS registered_courses_count, + u.registration_date AS registration_date +FROM + mnoskov.core_dim_user u +LEFT JOIN ( + SELECT + user_id, + count() AS registered_courses_count + FROM + mnoskov.core_enrollments + GROUP BY + user_id +) e ON u.user_id = e.user_id +WHERE u.user_id NOT IN (SELECT lv.user_id FROM mnoskov.core_fact_lesson_views lv); -- я ломалася тут между NOT IN и NOT EXISTS, но решил оставить первый тк это уже витрины мы делаем, и NULL поидее быть не должно + + +-- Чтение очищение заполнение +SHOW TABLES FROM mnoskov; + +SELECT * FROM mnoskov.core_dim_user; +SELECT * FROM mnoskov.inactive_users_summary; + +TRUNCATE TABLE mnoskov.core_dim_user ON CLUSTER clickhouse_cluster SYNC; +TRUNCATE TABLE mnoskov.inactive_users_summary ON CLUSTER clickhouse_cluster SYNC; + +INSERT INTO mnoskov.core_dim_user (user_id, name, age, email, registration_date) +VALUES +(1, 'Alice', 25, 'alice@mail.com', '2023-01-09 23:00:00'), +(2, 'Bob', 30, 'bob@gmail.com', '2023-02-04 23:00:00'), +(3, 'Charlie', 22, 'charlie@mail.com', '2023-02-19 23:00:00'), +(4, 'Diana', 28, 'diana@mail.com', '2023-02-28 23:00:00'), +(5, 'Ethan', 35, 'ethan@gmail.com', '2023-03-09 23:00:00'), +(6, 'Frank', 29, 'frank@mail.com', '2023-01-04 23:00:00'), +(7, 'Grace', 26, 'grace@gmail.com', '2023-01-11 23:00:00'), +(8, 'Henry', 32, 'henry@mail.com', '2023-01-19 23:00:00'), +(9, 'Iris', 24, 'iris@gmail.com', '2023-02-09 23:00:00'), +(10, 'Jack', 27, 'jack@mail.com', '2023-04-14 23:00:00'), +(11, 'Kelly', 23, 'kelly@gmail.com', '2023-04-19 23:00:00'), +(12, 'Leo', 31, 'leo@mail.com', '2023-01-14 23:00:00'), +(13, 'Mia', 26, 'mia@mail.com', '2023-01-17 23:00:00'), +(14, 'Nina', 28, 'nina@mail.com', '2023-01-07 23:00:00'), +(15, 'Oscar', 33, 'oscar@gmail.com', '2023-01-09 23:00:00'), +(16, 'Paul', 29, 'paul@mail.com', '2023-02-14 23:00:00'), +(17, 'Quinn', 24, 'quinn@gmail.com', '2023-03-19 23:00:00'), +(18, 'Rita', 27, 'rita@mail.com', '2023-03-31 23:00:00'), +(19, 'Sam', 30, 'sam@gmail.com', '2023-04-09 23:00:00'), +(21, 'Tinanal', 22, 'tinanal@mail.com', '2023-04-19 23:02:00') +(22, 'Anal2', 24, 'tinanal222@mail.com', '2023-04-19 23:22:00'); \ No newline at end of file From 18cfc2a077e511b2fa2ff761a0a3129808a4cde0 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Wed, 20 May 2026 15:22:48 +0300 Subject: [PATCH 115/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=204:=20=D0=A1=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=B9=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20cou?= =?UTF-8?q?rse=5Fcompletion=5Frate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task_4_course_completion_rate.sql | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql new file mode 100644 index 0000000..5d53f29 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql @@ -0,0 +1,117 @@ +--DROP TABLE mnoskov.course_completion_rate ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.course_completion_rate +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + user_name String, + course_id UInt64, + course_title String, + lessons_in_course UInt16, + lesson_viewed UInt16, + completion_rate Float32 +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/course_completion_rate', + '{replica}' +) +ORDER BY (user_id, course_id); + +--DROP VIEW mnoskov.mv_course_completion_rate ON CLUSTER clickhouse_cluster sync; +CREATE MATERIALIZED VIEW mnoskov.mv_course_completion_rate +ON CLUSTER clickhouse_cluster +TO mnoskov.course_completion_rate +AS +SELECT + u.user_id AS user_id, + u.name AS user_name, + cl.course_id AS course_id, + cl.course_title AS course_title, + cl.lessons_in_course AS lessons_in_course, + lv.lesson_viewed AS lesson_viewed, + ifnull(round(toFloat32(ifnull(lv.lesson_viewed, 0)) / nullif(cl.lessons_in_course, 0), 2), 0) AS completion_rate -- вроде все логично +FROM + mnoskov.core_enrollments e +RIGHT JOIN + mnoskov.core_dim_user u ON e.user_id = u.user_id +JOIN ( + SELECT + c.course_id, + c.title AS course_title, + uniq(lesson_id) lessons_in_course + FROM + mnoskov.core_dim_course c + JOIN + mnoskov.core_dim_lesson l ON l.course_id = c.course_id + GROUP BY + c.course_id, c.title +) cl ON e.course_id = cl.course_id +LEFT JOIN ( + SELECT + user_id, + course_id, + uniq(lesson_id) AS lesson_viewed + FROM + mnoskov.core_fact_lesson_views + GROUP BY + user_id, course_id +) lv ON e.user_id = lv.user_id AND e.course_id = lv.course_id +ORDER BY + u.user_id, e.course_id; + +-- Чтение очищение заполнение +SHOW TABLES FROM mnoskov; + +SELECT * FROM mnoskov.core_enrollments; +SELECT * FROM mnoskov.course_completion_rate; + +TRUNCATE TABLE mnoskov.core_enrollments ON CLUSTER clickhouse_cluster sync; +TRUNCATE TABLE mnoskov.course_completion_rate ON CLUSTER clickhouse_cluster sync; + +INSERT INTO mnoskov.core_enrollments +(user_id, course_id, enrolled_at) +VALUES +(6, 1, '2023-01-05 23:00:00'), +(14, 2, '2023-01-08 23:00:00'), +(14, 3, '2023-01-08 23:00:00'), +(14, 1, '2023-01-08 23:00:00'), +(6, 2, '2023-01-09 23:00:00'), +(15, 1, '2023-01-10 23:00:00'), +(7, 1, '2023-01-12 23:00:00'), +(1, 1, '2023-01-14 23:00:00'), +(12, 1, '2023-01-15 23:00:00'), +(13, 2, '2023-01-18 23:00:00'), +(2, 1, '2023-01-19 23:00:00'), +(8, 3, '2023-01-21 23:00:00'), +(8, 1, '2023-01-21 23:00:00'), +(8, 2, '2023-01-21 23:00:00'), +(1, 2, '2023-01-31 23:00:00'), +(7, 2, '2023-01-31 23:00:00'), +(14, 4, '2023-01-31 23:00:00'), +(15, 2, '2023-01-31 23:00:00'), +(6, 4, '2023-02-04 23:00:00'), +(1, 4, '2023-02-09 23:00:00'), +(9, 4, '2023-02-11 23:00:00'), +(9, 1, '2023-02-14 23:00:00'), +(16, 2, '2023-02-15 23:00:00'), +(16, 4, '2023-02-15 23:00:00'), +(13, 4, '2023-02-19 23:00:00'), +(3, 1, '2023-02-24 23:00:00'), +(3, 2, '2023-03-04 23:00:00'), +(15, 3, '2023-03-04 23:00:00'), +(6, 3, '2023-03-07 23:00:00'), +(4, 2, '2023-03-09 23:00:00'), +(7, 3, '2023-03-09 23:00:00'), +(12, 3, '2023-03-09 23:00:00'), +(2, 3, '2023-03-14 23:00:00'), +(5, 5, '2023-03-19 23:00:00'), +(14, 5, '2023-03-19 23:00:00'), +(17, 3, '2023-03-21 23:00:00'), +(17, 5, '2023-03-24 23:00:00'), +(4, 3, '2023-03-31 23:00:00'), +(18, 1, '2023-04-01 23:00:00'), +(18, 4, '2023-04-04 23:00:00'), +(19, 1, '2023-04-10 23:00:00'), +(19, 2, '2023-04-10 23:00:00'), +(10, 1, '2023-04-15 23:00:00'), +(20, 2, '2023-04-18 23:00:00'), +(11, 2, '2023-04-20 23:00:00'); \ No newline at end of file From 9a2834586e5982a7bfea0217c6fb93edd0ce3349 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Thu, 21 May 2026 00:56:45 +0300 Subject: [PATCH 116/123] Add homework for JOIN --- vberetar/postgresql/sql_join.sql | 144 +++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 vberetar/postgresql/sql_join.sql diff --git a/vberetar/postgresql/sql_join.sql b/vberetar/postgresql/sql_join.sql new file mode 100644 index 0000000..e2b7c55 --- /dev/null +++ b/vberetar/postgresql/sql_join.sql @@ -0,0 +1,144 @@ +-- Задание 1 +SELECT o.id AS order_id, + o.order_date, + o.status, + u.name AS user_name, + u.country +FROM orders o +JOIN users u ON o.user_id = u.id; + +-- Ответ на вопросы +-- 1. Логичнее начать c orders, так как нам в первую очередь нужны все заказы, +-- а потом только пользователи которые их сделали. + +-- 2. Я думаю уместнее сделать LEFT. +-- Я пробовал сделать с INNER и LEFT и у меня не пропали строки. +-- Получается это из за того что у каждого заказа есть пользователь, не дуаю что должен создастся заказ без пользователя. + + +-- Задание 2 +SELECT o.id AS order_id, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM orders o +JOIN order_items oi ON oi.order_id = o.id +JOIN products p ON p.id = oi.product_id +WHERE o.id = 101; + +-- Ответ на вопрос +-- Выдаст дублированный состав заказа с id 101, +-- получается он будет обращаться к каждаму заказу, но отображать продукты заказа с id 101 + + +-- Задание 3 +SELECT o.id AS order_id, + u.name AS name, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM orders o +JOIN users u ON u.id = o.user_id +JOIN order_items oi ON oi.order_id = o.id +JOIN products p ON p.id = oi.product_id; + +-- Ответ на вопрос +-- 1. Появляется дубликат заказов потому что у 3 заказа есть несколько товаров, join превращает(размножает) их в несколько строк. +-- 2. Отличается тем что мы выводим все заказы с товарами а не один заказ. + + +-- Задание 4 +SELECT u.name AS user_name, + o.id AS order_id, + o.status +FROM users u +LEFT JOIN orders o ON o.user_id = u.id; + +-- Ответ на вопрос +-- INNER JOIN не выдаст пользователей без заказа. Только тех у кого есть пара с orders. + + +-- Задание 5 +SELECT u.id AS user_id, + u.name AS user_name +FROM users u +LEFT JOIN orders o ON o.user_id = u.id +WHERE o.id IS NULL; + +-- Ответ на вопрос +-- 1. Проверять IS NULL нужно по полю orders.id +-- 2. Мы должны в запросе получить всех пользователей, а потом в фильтре "узнать" есть ли у них заказы. + + +-- Задание 6 +SELECT o.id AS order_id, + u.name AS user_name, + p.name AS product_name, + oi.quantity +FROM orders o +JOIN users u ON u.id = o.user_id +JOIN order_items oi ON oi.order_id = o.id +JOIN products p ON p.id = oi.product_id +WHERE p.name ILIKE '%kindle%'; + +-- Ответ на вопрос +-- В заказе 2 есть только один товар с названием Kindle. + + +-- Задание 7 +SELECT p.name AS product_name, + oi.order_id, + oi.quantity +FROM products p +LEFT JOIN order_items oi ON oi.product_id = p.id; + +-- Ответ на вопрос +-- 1. order_id будет NULL +-- 2. Если использовать INNER JOIN то он не покажет товар который не заказывали. + + +-- Задание 8 +SELECT o.id AS order_id, + u.name AS user_name, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM products p +JOIN order_items oi ON oi.product_id = p.id +JOIN orders o ON o.id = oi.order_id +JOIN users u ON u.id = o.user_id +WHERE o.status = 'paid'; + +-- Ответ на вопрос +-- У меня получился тот же результат если написать вот так - JOIN orders o ON o.id = oi.order_id and o.status = 'paid' + + +-- Задание 9 +SELECT o.id AS order_id, + o.order_date, + p.name AS product_name, + oi.quantity, + oi.price_at_purchase +FROM products p +JOIN order_items oi ON oi.product_id = p.id +JOIN orders o ON o.id = oi.order_id +JOIN users u ON u.id = o.user_id +WHERE u.name = 'Alice'; + +-- Ответ на вопрос +-- Если у пользователя нет ни одного заказа, то этот запрос вернет пустую строку. +-- Не вернет потому что нечего отобразить таблице, если нет заказов то и нет купленных товаров. +-- Но если прописать RIGHT JOIN users u ON u.id = o.user_id то отобразит все строки NULL. + + +-- Задание 10 +SELECT o.id AS order_id, + o.user_id, + o.status +FROM orders o +LEFT JOIN order_items oi ON oi.order_id = o.id +WHERE oi.order_id IS NULL; + +-- Ответ на вопрос +-- Такой запрос выдаст пустые строки. +-- INNER JOIN должен выдать строки только если есть "пара". From af3293dc4e4b1a93b8a9b6365f80f34d52476d77 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 21 May 2026 14:37:36 +0300 Subject: [PATCH 117/123] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=205:=20=D0=9D=D0=B0=D0=BF=D0=B8=D1=88=D0=B8=20SQL-=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=8B=20=D0=BA=20=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BD=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql new file mode 100644 index 0000000..ebe46d6 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql @@ -0,0 +1,36 @@ +-- Топ 5 самых непросматреваемых уроков +SELECT + any(lesson_title), + any(course_title), + countMerge(total_views) AS total_views +FROM + mnoskov.lesson_popularity_summary +GROUP BY + lesson_id +ORDER BY + total_views +LIMIT 5; + + +-- Неактивные пользователи младше 26 +SELECT + name, + age, + email +FROM + mnoskov.inactive_users_summary +WHERE + age < 26; + + +-- 3 круса с самым низкми процентом завершения +SELECT + course_title, + avg(completion_rate) AS avg_completion_rate +FROM + mnoskov.course_completion_rate +GROUP BY + course_title +ORDER BY + avg_completion_rate +LIMIT 3; \ No newline at end of file From 7e41fd56857da0c0921f28f41775373b4bf174e0 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 21 May 2026 14:54:24 +0300 Subject: [PATCH 118/123] =?UTF-8?q?=D0=9F=D0=B5=D1=82-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BA=D1=82:=20=D0=92=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B9.=20=D0=97=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=206:=20=D0=A1=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BD=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=81=20?= =?UTF-8?q?Greenplum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt new file mode 100644 index 0000000..fa5fbd2 --- /dev/null +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt @@ -0,0 +1,2 @@ +Разница есть только в таблицах mv_lesson_popularity_summary +Где в Greenplum - NULL, в clickhouse - 0. Потому что clickhouser сам подставляет 0 вместо NULL. \ No newline at end of file From b613634f73367076b4ad47ac7bb1467eb700a081 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 21 May 2026 16:44:30 +0300 Subject: [PATCH 119/123] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=BA=D1=82=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20'Materialized=20View=20(MV)'=20-=20Clickhouse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice_materialized_view_MV.sql | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 mnoskov/clickhouse/Практика clickhouse/4. Streaming в Clickhouse/1. Materialized View (MV)/practice_materialized_view_MV.sql diff --git a/mnoskov/clickhouse/Практика clickhouse/4. Streaming в Clickhouse/1. Materialized View (MV)/practice_materialized_view_MV.sql b/mnoskov/clickhouse/Практика clickhouse/4. Streaming в Clickhouse/1. Materialized View (MV)/practice_materialized_view_MV.sql new file mode 100644 index 0000000..bfb6cd2 --- /dev/null +++ b/mnoskov/clickhouse/Практика clickhouse/4. Streaming в Clickhouse/1. Materialized View (MV)/practice_materialized_view_MV.sql @@ -0,0 +1,157 @@ +-- Задание 1 + +--DROP TABLE mnoskov.raw_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.raw_events +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/raw_events', + '{replica}' +) +ORDER BY ts; + +--DROP TABLE mnoskov.clean_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.clean_events +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/clean_events', + '{replica}' +) +ORDER BY ts; + +CREATE MATERIALIZED VIEW mnoskov.mv_clean +ON CLUSTER clickhouse_cluster +TO mnoskov.clean_events +AS +SELECT + user_id, + ts, + nullif(event_type, '') AS event_type -- это я сделать чтобы выводилось NULL а не '' - пустая строка. +FROM mnoskov.raw_events +WHERE user_id != 0 AND event_type IS NOT NULL +ORDER BY ts; + +-- Тесты: +SELECT * FROM raw_events; +SELECT * FROM mnoskov.mv_clean; + +TRUNCATE TABLE mnoskov.raw_events ON CLUSTER clickhouse_cluster sync; +INSERT INTO mnoskov.raw_events +(user_id, ts, event_type) +VALUES +(0, '2024-07-17 10:00:00', 'click'), +(1, '2024-07-17 10:01:00', 'view'), +(2, '2024-07-17 10:02:00', NULL); + + +-- Задание 2 + +--raw_events +--DROP TABLE mnoskov.raw_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.raw_events +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/raw_events', + '{replica}' +) +ORDER BY ts; + +--click_events +--DROP TABLE mnoskov.click_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.click_events +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/click_events', + '{replica}' +) +ORDER BY ts; + +--login_events +--DROP TABLE mnoskov.login_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.login_events1 +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/login_events1', + '{replica}' +) +ORDER BY ts; + +--purchase_events +--DROP TABLE mnoskov.purchase_events ON CLUSTER clickhouse_cluster sync; +CREATE TABLE mnoskov.purchase_events1 +ON CLUSTER clickhouse_cluster +( + user_id UInt64, + ts DateTime, + event_type String +) +ENGINE = ReplicatedMergeTree( + '/clickhouse/tables/{shard}/mnoskov/purchase_events1', + '{replica}' +) +ORDER BY ts; + +--mv_click +--DROP VIEW mnoskov.mv_click ON CLUSTER clickhouse_cluster sync; +CREATE MATERIALIZED VIEW mnoskov.mv_click +ON CLUSTER clickhouse_cluster +TO mnoskov.click_events +AS +SELECT * FROM mnoskov.raw_events WHERE event_type = 'click' +ORDER BY ts; + +--mv_login +--DROP VIEW mnoskov.mv_login ON CLUSTER clickhouse_cluster sync; +CREATE MATERIALIZED VIEW mnoskov.mv_login +ON CLUSTER clickhouse_cluster +TO mnoskov.login_events1 +AS +SELECT * FROM mnoskov.raw_events WHERE event_type = 'login' +ORDER BY ts; + +--mv_purchase +--DROP VIEW mnoskov.mv_purchase ON CLUSTER clickhouse_cluster sync; +CREATE MATERIALIZED VIEW mnoskov.mv_purchase +ON CLUSTER clickhouse_cluster +TO mnoskov.purchase_events1 +AS +SELECT * FROM mnoskov.raw_events WHERE event_type = 'purchase' +ORDER BY ts; + + +-- Тесты: +SELECT * FROM mnoskov.click_events; +SELECT * FROM mnoskov.login_events1; +SELECT * FROM mnoskov.purchase_events1; + +TRUNCATE TABLE mnoskov.raw_events ON CLUSTER clickhouse_cluster sync; +INSERT INTO mnoskov.raw_events +(user_id, ts, event_type) +VALUES +(1, '2024-07-17 10:00:00', 'click'), +(2, '2024-07-17 10:01:00', 'login'), +(3, '2024-07-17 10:02:00', 'purchase'); \ No newline at end of file From 24bf412aebb837e2269a136d3f08dbba8e9f7f52 Mon Sep 17 00:00:00 2001 From: mnoskov Date: Mon, 25 May 2026 10:15:32 +0300 Subject: [PATCH 120/123] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B4=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20-=203:=20=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=B9=20=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D1=83=20inactive=5Fusers=5Fsumma?= =?UTF-8?q?ry=20&=20=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=205:=20?= =?UTF-8?q?=D0=9D=D0=B0=D0=BF=D0=B8=D1=88=D0=B8=20SQL-=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D1=8B=20=D0=BA=20=D0=B2=D0=B8=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql | 4 ++-- .../Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql index 95b7ac1..25b333d 100644 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql @@ -6,7 +6,7 @@ ON CLUSTER clickhouse_cluster name String, email String, age UInt8, - registered_courses_count UInt8, + registered_courses_count UInt32, registration_date DateTime ) ENGINE = ReplicatedMergeTree( @@ -71,5 +71,5 @@ VALUES (17, 'Quinn', 24, 'quinn@gmail.com', '2023-03-19 23:00:00'), (18, 'Rita', 27, 'rita@mail.com', '2023-03-31 23:00:00'), (19, 'Sam', 30, 'sam@gmail.com', '2023-04-09 23:00:00'), -(21, 'Tinanal', 22, 'tinanal@mail.com', '2023-04-19 23:02:00') +(21, 'Tinanal', 22, 'tinanal@mail.com', '2023-04-19 23:02:00'), (22, 'Anal2', 24, 'tinanal222@mail.com', '2023-04-19 23:22:00'); \ No newline at end of file diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql index ebe46d6..f9b83f5 100644 --- a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql +++ b/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql @@ -1,7 +1,7 @@ -- Топ 5 самых непросматреваемых уроков SELECT - any(lesson_title), - any(course_title), + any(lesson_title) AS lesson_title, + any(course_title) AS course_title, countMerge(total_views) AS total_views FROM mnoskov.lesson_popularity_summary From f045be8a396b58696d4f51ab5aa5eb436eb2ed8f Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 28 May 2026 18:00:56 +0300 Subject: [PATCH 121/123] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20:=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=BF=D0=B0=D0=BF=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv | 0 .../Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv | 0 .../Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv | 0 .../Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv | 0 .../Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv | 0 .../Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv | 0 .../Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql | 0 .../Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql | 0 .../Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql | 0 .../Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql | 0 .../Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql | 0 .../Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt | 0 .../Step_1_project_Initialization.sql | 0 .../Step_2_Loading Data}/courses_202604212117.csv | 0 .../Step_2_Loading Data}/enrollments_202604212130.csv | 0 .../Step_2_Loading Data}/lesson_views_202604212133.csv | 0 .../Step_2_Loading Data}/lessons_202604212131.csv | 0 .../Step_2_Loading Data}/users_202604212132.csv | 0 .../{Пет проект: DWH => Пет проект DWH}/Step_3_core_layer.sql | 0 .../Step_4_preparing_display_cases_at_greenplum.sql | 0 .../{Пет проект: DWH => Пет проект DWH}/Step_5_verification.sql | 0 .../1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql | 0 .../1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql | 0 .../2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql | 0 .../3. Доп. задания/DQL_additional_tasks.sql | 0 30 files changed, 0 insertions(+), 0 deletions(-) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql (100%) rename mnoskov/clickhouse/{Пет-проект: Витринный слой => Пет-проект Витринный слой}/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt (100%) rename mnoskov/greenplum/{Пет проект: DWH => Пет проект DWH}/Step_1_project_Initialization.sql (100%) rename mnoskov/greenplum/{Пет проект: DWH/Step 2: Loading Data => Пет проект DWH/Step_2_Loading Data}/courses_202604212117.csv (100%) rename mnoskov/greenplum/{Пет проект: DWH/Step 2: Loading Data => Пет проект DWH/Step_2_Loading Data}/enrollments_202604212130.csv (100%) rename mnoskov/greenplum/{Пет проект: DWH/Step 2: Loading Data => Пет проект DWH/Step_2_Loading Data}/lesson_views_202604212133.csv (100%) rename mnoskov/greenplum/{Пет проект: DWH/Step 2: Loading Data => Пет проект DWH/Step_2_Loading Data}/lessons_202604212131.csv (100%) rename mnoskov/greenplum/{Пет проект: DWH/Step 2: Loading Data => Пет проект DWH/Step_2_Loading Data}/users_202604212132.csv (100%) rename mnoskov/greenplum/{Пет проект: DWH => Пет проект DWH}/Step_3_core_layer.sql (100%) rename mnoskov/greenplum/{Пет проект: DWH => Пет проект DWH}/Step_4_preparing_display_cases_at_greenplum.sql (100%) rename mnoskov/greenplum/{Пет проект: DWH => Пет проект DWH}/Step_5_verification.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql (100%) rename mnoskov/postgresql/{Пет-проект: Онлайн-курсы => Пет-проект Онлайн-курсы}/3. Доп. задания/DQL_additional_tasks.sql (100%) diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_course_202605201139.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_date_202605191312.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_lesson_202605191312.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/dim_user_202605201142.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/enrollments_202605201134.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/csv files/fact_lesson_views_202605191356.csv diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 1. Загрузка тестовых данных/task_1_upload_csv_files.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_2_lesson_popularity_summary.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_3_inactive_users_summary.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 2. Создание витринных таблиц/task_4_course_completion_rate.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 3. Анализ и запросы/Task_5_SQL_queries_for_dm.sql diff --git a/mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt b/mnoskov/clickhouse/Пет-проект Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt similarity index 100% rename from mnoskov/clickhouse/Пет-проект: Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt rename to mnoskov/clickhouse/Пет-проект Витринный слой/Этап 4. Тестирование и валидация/task_6_compare_data_with_Greenplum.txt diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql b/mnoskov/greenplum/Пет проект DWH/Step_1_project_Initialization.sql similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step_1_project_Initialization.sql rename to mnoskov/greenplum/Пет проект DWH/Step_1_project_Initialization.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv b/mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/courses_202604212117.csv similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/courses_202604212117.csv rename to mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/courses_202604212117.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv b/mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/enrollments_202604212130.csv similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/enrollments_202604212130.csv rename to mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/enrollments_202604212130.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv b/mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/lesson_views_202604212133.csv similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lesson_views_202604212133.csv rename to mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/lesson_views_202604212133.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv b/mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/lessons_202604212131.csv similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/lessons_202604212131.csv rename to mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/lessons_202604212131.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv b/mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/users_202604212132.csv similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step 2: Loading Data/users_202604212132.csv rename to mnoskov/greenplum/Пет проект DWH/Step_2_Loading Data/users_202604212132.csv diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql b/mnoskov/greenplum/Пет проект DWH/Step_3_core_layer.sql similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step_3_core_layer.sql rename to mnoskov/greenplum/Пет проект DWH/Step_3_core_layer.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql b/mnoskov/greenplum/Пет проект DWH/Step_4_preparing_display_cases_at_greenplum.sql similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step_4_preparing_display_cases_at_greenplum.sql rename to mnoskov/greenplum/Пет проект DWH/Step_4_preparing_display_cases_at_greenplum.sql diff --git a/mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql b/mnoskov/greenplum/Пет проект DWH/Step_5_verification.sql similarity index 100% rename from mnoskov/greenplum/Пет проект: DWH/Step_5_verification.sql rename to mnoskov/greenplum/Пет проект DWH/Step_5_verification.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/1. Создание таблиц и связей/DDL_create_tables_for_pet_project.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/1. Создание таблиц и связей/DML_insert_in_tables_for_pet_project.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_cohort_analysis_of_users.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_conversion_funnel.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_effectivity.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_courses_popularity.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_reporting_on_interactions.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/2. Создание отчетности (витрин)/DQL_materialized_view_user_activity_summary.sql diff --git a/mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql b/mnoskov/postgresql/Пет-проект Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql similarity index 100% rename from mnoskov/postgresql/Пет-проект: Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql rename to mnoskov/postgresql/Пет-проект Онлайн-курсы/3. Доп. задания/DQL_additional_tasks.sql From dd868283ed7b8c75022021b7ab5c4daed259aa0c Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 28 May 2026 18:13:59 +0300 Subject: [PATCH 122/123] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=B0=20=D0=B4=D0=B2=D0=BE=D0=B5=D1=82=D0=BE=D1=87=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DQL_functions_practice.sql | 0 .../{5. Индексы: Алгоритмы => 5. Индексы Алгоритмы}/practice_indexes_algorithms.sql | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename mnoskov/postgresql/SQL с нуля/{5. Функции: даты, строки, логика => 5. Функции даты, строки, логика}/DQL_functions_practice.sql (100%) rename mnoskov/Теория БД/1. Ускорение и масштабирование/{5. Индексы: Алгоритмы => 5. Индексы Алгоритмы}/practice_indexes_algorithms.sql (100%) diff --git a/mnoskov/postgresql/SQL с нуля/5. Функции: даты, строки, логика/DQL_functions_practice.sql b/mnoskov/postgresql/SQL с нуля/5. Функции даты, строки, логика/DQL_functions_practice.sql similarity index 100% rename from mnoskov/postgresql/SQL с нуля/5. Функции: даты, строки, логика/DQL_functions_practice.sql rename to mnoskov/postgresql/SQL с нуля/5. Функции даты, строки, логика/DQL_functions_practice.sql diff --git a/mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql b/mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы Алгоритмы/practice_indexes_algorithms.sql similarity index 100% rename from mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы: Алгоритмы/practice_indexes_algorithms.sql rename to mnoskov/Теория БД/1. Ускорение и масштабирование/5. Индексы Алгоритмы/practice_indexes_algorithms.sql From a547afe5dbaa0b52f7dd4bf3c736e1cc18c6738f Mon Sep 17 00:00:00 2001 From: mnoskov Date: Thu, 28 May 2026 18:17:25 +0300 Subject: [PATCH 123/123] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice_arrays_maps_and_nested_structures.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mnoskov/clickhouse/Практика clickhouse/2. Запросы/{3. Массивы, Map и вложенные структуры* => 3. Массивы, Map и вложенные структуры}/practice_arrays_maps_and_nested_structures.sql (100%) diff --git a/mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql b/mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры/practice_arrays_maps_and_nested_structures.sql similarity index 100% rename from mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры*/practice_arrays_maps_and_nested_structures.sql rename to mnoskov/clickhouse/Практика clickhouse/2. Запросы/3. Массивы, Map и вложенные структуры/practice_arrays_maps_and_nested_structures.sql