Merge branch 'main' into vberetar_sql_cte

This commit is contained in:
2026-05-28 20:06:21 +04:00
55 changed files with 1888 additions and 9 deletions
+38
View File
@@ -0,0 +1,38 @@
CREATE TABLE users (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age SMALLINT CHECK (age > 0),
email VARCHAR(100) NOT NULL UNIQUE,
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 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
price DECIMAL(10, 2) NOT NULL,
in_stock BOOLEAN DEFAULT true,
);
CREATE TABLE orders(
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id INT,
order_date TIMESTAMPTZ NOT NULL,
status VARCHAR(20),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- status - лучше прописать словами статус, так понятней будет. Я бы сюда поставил ENUM
CREATE TABLE order_items(
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price_at_purchase DECIMAL(10, 2) NOT NULL,
PRIMARY KEY (order_id, product_id)
);
+41
View File
@@ -0,0 +1,41 @@
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 (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 (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),
(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);
+56
View File
@@ -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.
+144
View File
@@ -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 должен выдать строки только если есть "пара".
+73
View File
@@ -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?
-- Ответ на вопрос:
-- Не знаю, возможно понадобится посмотреть несколько записей из середины списка.
+49
View File
@@ -0,0 +1,49 @@
-- Задание 1
SELECT id, name, 'active' AS user_status
FROM users
WHERE is_active = TRUE
UNION ALL
SELECT id, name, 'inactive' AS user_status
FROM users
WHERE is_active = FALSE
ORDER BY name;
-- Ответ на вопрос
-- 1. Количество строк не изменилось
-- 2. Предположу, что подача информации в таком виде более информативней(понятней).
-- Задание 2
SELECT id AS order_id, user_id, order_date, 'old' AS period
FROM orders
WHERE order_date < '2024-01-01'
UNION ALL
SELECT id AS order_id, user_id, order_date, 'new' AS period
FROM orders
WHERE order_date >= '2024-01-01'
ORDER BY order_date, order_id;
-- Ответ на вопрос
-- 1. В dbeaver в самой левой колонке отображается номерация строк, а снизу общее количество строк который выдал запрос.
-- На последней строке с old можно увидеть число в самой левой колонке, это и есть количество строк c old. Далее отнимаем общее кол-во строк от old и получаем кол-во new.
-- 2. Тяжело ответить на такой вопрос, не знаю. Если у меня были бы сомнения, я бы у новичка спросил.
-- Задание 3
SELECT DISTINCT p.id AS product_id,
p.name AS product_name,
'ordered' AS product_status
FROM products p
JOIN order_items oi ON oi.product_id = p.id
UNION ALL
SELECT DISTINCT p.id AS product_id,
p.name AS product_name,
'never_ordered' AS product_status
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.product_id IS NULL
ORDER BY product_name;
-- Ответ на вопрос
-- 1. Нам нужно чтобы товар(products) ни разу не заказывали(order_items), получается что мы проверяем есть ли ссылка в каком нибудь заказе на продукт.
-- 2. Дает сигнал бизнесу что товар не хотят покупать, возможно на него нужно сделать скидку или прорекламировать товар у блогера какого нибудь.