Базы данных (Data Base)
8.16K subscribers
595 photos
473 videos
19 files
577 links
Базы данных (Data Base). По всем вопросам @evgenycarter
Download Telegram
💡 Универсальная шпаргалка по SQL

📲 Мы в MAX

#db

👉 @database_info
👍8🔥1
🔥 Индексы в PostgreSQL: когда они реально помогают, а когда мешают

Многие ставят индексы “на всё подряд”, а потом удивляются, почему БД тормозит.

Типичные ошибки:

- Индекс на колонке, которая почти всегда уникальна (например, id с PK — он уже индексирован).
- Индекс на поле с низкой селективностью (is_active = true/false) — толку ноль, только замедляет вставки.
- Создание 5+ индексов на таблицу без анализа запросов.

Best practices:

- Делайте индекс под конкретный частый запрос.
- Для WHERE field LIKE 'abc%' — используйте btree.
- Для поиска по JSONB → GIN индекс.
- Для геоданных → GiST.
- Для частого условия — partial index:


CREATE INDEX idx_orders_active
ON orders(status)
WHERE status = 'active';


⚡️ Помните: каждый индекс ускоряет SELECT, но замедляет INSERT/UPDATE/DELETE.

👉 Итог: индекс — это инструмент, а не магическая таблетка. Всегда проверяйте план выполнения (EXPLAIN ANALYZE).

Сохрани, чтобы не забыть 🚀

📲 Мы в MAX

#db

👉 @database_info
4👍3👎1
🚨 Антипаттерн: Почему OFFSET убивает твою базу (и как делать пагинацию правильно)

Привет! Если вы когда-нибудь реализовывали каталог товаров или ленту новостей, то наверняка писали запрос с LIMIT и OFFSET. Для небольших таблиц это работает отлично, но как только проект взлетает и данных становится много, база начинает задыхаться. Давайте разберем, почему так происходит и как это лечить.

Как мы делаем обычно:


SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
LIMIT 50 OFFSET 100000;



В чем подвох? База данных не умеет «магически» прыгать на 100 000-ю строку. Ей придется прочитать, отсортировать (если нет подходящего индекса) и отбросить первые 100 000 строк, чтобы вернуть вам всего 50. Чем глубже пользователь листает страницы, тем медленнее работает запрос. Нагрузка на CPU и диски растет экспоненциально.

Как делать правильно:

Вместо того чтобы говорить базе «пропусти N строк», мы говорим ей «дай мне 50 записей, которые идут сразу после последней записи, которую я уже видел».


SELECT id, title, created_at
FROM articles
WHERE created_at < '2023-10-25 14:00:00' -- дата из последней записи на предыдущей странице
ORDER BY created_at DESC
LIMIT 50;



Этот запрос мгновенно найдет нужное место по индексу (B-Tree) и прочитает ровно 50 строк. Никакой лишней работы!

🛠 Важный нюанс:
Если поле created_at не уникально (две статьи вышли в одну секунду), предыдущий запрос может пропустить данные. Используйте уникальный «тайбрейкер» - например, id. В PostgreSQL это можно сделать очень элегантно с помощью кортежей (Row Values):


SELECT id, title, created_at
FROM articles
WHERE (created_at, id) < ('2023-10-25 14:00:00', 10543)
ORDER BY created_at DESC, id DESC
LIMIT 50;



(Не забудьте создать составной индекс: `CREATE INDEX idx_articles_created_id ON articles (created_at DESC, id DESC);`)

📌 Итог:

OFFSET / LIMIT: Ок для админок с небольшим трафиком и малым объемом данных (до ~10-50к строк).

Keyset Pagination: Must-have для бесконечных скроллов (infinite scroll), публичных API и таблиц на миллионы записей.

👇 Скинь ссылку на этот пост фронтендеру, который просит «просто добавить номер страницы» в API. А какой метод пагинации чаще всего используете вы в своих текущих проектах? Делитесь в комментариях!

📲 Мы в MAX

#db

👉 @database_info
👍71
🚀 Подборка полезных IT каналов в Max


Системное администрирование, DevOps 📌

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С

Программирование C++📌

https://max.ru/cpp_lib Библиотека C/C++ разработчика

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

Шутки программистов 📌
https://max.ru/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free

Книги, статьи для дизайнеров 📌

https://max.ru/odesigners Статьи, книги для дизайнеров

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии 📌
https://max.ru/progjob Вакансии в IT

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных


Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
👎5🖕1
🔥 Неправильные типы данных в БД — тихий убийца производительности

Одна из самых частых ошибок — выбирать тип “на всякий случай побольше”.

Примеры антипаттернов:

- VARCHAR(255) для всего подряд, даже если поле — код из 10 символов.
- TEXT для email-адресов.
- BIGINT для счётчика, где максимум 1000 записей.
- FLOAT для денег (теряешь точность).

Как лучше:

- Размер строки под задачу: VARCHAR(50) для email, CHAR(2) для кода страны.
- Для денег → NUMERIC(10,2) или DECIMAL.
- Для булевых значений → BOOLEAN, а не INT.
- Для дат → DATE или TIMESTAMP, а не строка.

📌 Пример:


-- Плохо
price FLOAT;

-- Хорошо
price NUMERIC(10,2);


⚡️ Итог: грамотный выбор типов = меньше места, быстрее запросы, меньше багов.

Сохрани, чтобы не забыть 😉

📲 Мы в MAX

#db

👉 @database_info
👍51
⚔️ SQL vs NoSQL: Что выбрать для вашего проекта?

Выбор базы данных - одно из ключевых архитектурных решений. Нет универсальной "серебряной пули", есть инструменты под разные задачи. Давайте разберем основные отличия и когда что использовать.

🐘 SQL (Реляционные БД): Порядок и Транзакции

Примеры: PostgreSQL, MySQL, MS SQL, Oracle.
Основа: жесткая схема данных, таблицы, строки, отношения, поддержка ACID транзакций (атомарность, согласованность, изолированность, долговечность).


-- SQL Пример: JOIN трех таблиц
SELECT u.name, o.order_date, p.product_name
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 = 'KZ';



Выбирайте SQL, если:

• У вас четко структурированные данные.
• Критична согласованность данных и сложные транзакции (например, финтех).
• Требуются сложные выборки и отчеты (мощный диалект SQL).

🍃 NoSQL: Гибкость и Масштабирование

Примеры: MongoDB (документная), Redis (ключ-значение), Cassandra (ширококолоночная), Neo4j (графовая).
Основа: гибкая схема или ее отсутствие, различные модели данных, фокус на горизонтальном масштабировании. Часто жертвуют полной ACID согласованностью ради производительности (Eventual Consistency).


// NoSQL Пример (MongoDB): вставка документа пользователя
db.users.insertOne({
name: "Aliya",
age: 28,
address: {
city: "Almaty",
street: "Abay Ave"
},
tags: ["dev", "database"]
});



Выбирайте NoSQL, если:

• Данные неструктурированы или их структура часто меняется.
• Требуется огромная пропускная способность записи/чтения (лайки, логи, чаты).
• Необходимо простое горизонтальное масштабирование (добавление новых узлов).
• Допустима отложенная согласованность (Eventual Consistency).

Подводные камни NoSQL:

Сложность JOIN-ов: В документных БД (как MongoDB) JOIN-ы (lookup) дорогие и не так эффективны, как в реляционных. Модель данных часто приходится "денормализовать" (дублировать данные).
Отложенная согласованность: Если вам критично видеть самое последнее записанное значение сразу после записи, будьте осторожны с NoSQL конфигурациями по умолчанию.

Итог:
Выбирайте инструмент под задачу. SQL - для транзакций и структуры, NoSQL - для масштаба и гибкости. Часто в современном мире используют полиглот-архитектуру: SQL для ядра данных, а NoSQL для кэширования (Redis) или хранения логов/аналитики.

Сохрани пост, чтобы освежить в памяти перед проектированием следующей системы! 👇

📲 Мы в MAX

#db

👉 @database_info
👍5
Антипаттерн: N+1 запросов — как заметить и починить

Вы берёте список сущностей, а потом в цикле для каждой тянете связанные данные. В итоге - 1 запрос за «родителями» + N запросов за «детьми». Латентность растёт линейно от размера выборки.

Симптомы

- В логах много одинаковых коротких запросов.
- Кол-во запросов ≈ размеру списка.
- Страница/endpoint сильно «замедляется» при росте данных.

Плохой пример (SQL + псевдокод)


-- Берём пользователей
SELECT id, name FROM users WHERE active = true;

-- Потом в цикле по каждому:
SELECT count(*) FROM orders WHERE user_id = :id;


Правильно (SQL, PostgreSQL) — сетевое мышление:


SELECT u.id,
u.name,
count(o.*) AS orders_cnt
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true
GROUP BY u.id, u.name;


Django ORM


# Плохо: в шаблоне/цикле обращаемся к user.orders -> N+1
users = User.objects.filter(active=True)

# Хорошо: подгрузим связи заранее
users = (User.objects
.filter(active=True)
.prefetch_related('orders')) # для 1:N
# для 1:1 / ForeignKey используйте select_related('profile')

# Агрегация без цикла
from django.db.models import Count
users = (User.objects.filter(active=True)
.annotate(orders_cnt=Count('orders')))


SQLAlchemy


from sqlalchemy.orm import selectinload, joinedload

# 1:N — безопаснее selectinload (батчирует IN (...))
users = (session.query(User)
.options(selectinload(User.orders))
.filter(User.active.is_(True))
.all())

# 1:1 — joinedload
user = (session.query(User)
.options(joinedload(User.profile))
.get(user_id))


Практические советы

- Логируйте кол-во запросов на эндпойнт/страницу. В Django - django-debug-toolbar, assertNumQueries в тестах; в SQLAlchemy - echo/интеграция с логгером.
- Индексы: обязательно orders(user_id); если фильтруете по статусу - составной (user_id, status).
- Батчинг вместо циклов: тяните детей одним запросом WHERE user_id IN (...), затем мапьте в памяти.
- Осторожно с joinedload для 1:N на больших выборках - риск «взрыва» строк. Для 1:N чаще выбирайте selectinload.
- Колонки по делу: не тащите SELECT *, берите только нужные поля.
- Пагинация: уменьшает N и давление на сеть/память.
- EXPLAIN (ANALYZE, BUFFERS) - проверяйте планы и кардинальности.


💡Думайте наборами, а не циклами. Eager loading + агрегаты закрывают 90% случаев N+1. Настройте мониторинг количества запросов - и ловите проблему до продакшена.

Сохрани, чтобы не наступить снова. Поделись с коллегами. А как вы ловите N+1 у себя?

📲 Мы в MAX

#db

👉 @database_info
👍6
Типы JOIN в SQL и когда их применять

- INNER JOIN - пересечение множеств (только совпавшие строки).
- LEFT JOIN - все слева + совпавшие справа (несовпавшие → NULL).
- RIGHT JOIN - симметричен LEFT, лучше переворачивать под LEFT.
- FULL OUTER JOIN - все слева и справа (где нет пары → NULL).
- CROSS JOIN — декартово произведение (каждая со всеми).
- SELF JOIN - таблица соединяется сама с собой.
- SEMI / ANTI JOIN - “есть/нет соответствия” (через EXISTS / NOT EXISTS).
- LATERAL / APPLY - зависимая подзапросная таблица на строку слева.


1️⃣ INNER JOIN - «строго есть пара»

«Покажи оплаченные заказы с данными клиента».


SELECT o.id, c.name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid';


Используйте, когда отсутствие пары - повод исключить строку.

2️⃣ LEFT JOIN - «все слева, даже без пары»

«Список клиентов и количество их заказов (включая с нулём)».


SELECT c.id, c.name, COALESCE(COUNT(o.id), 0) AS orders_cnt
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;


По умолчанию для «обогащения» справочниками и опциональных связей.

3️⃣ RIGHT JOIN - почти не нужен

Заменяйте на LEFT, поменяв стороны:


-- было:
-- SELECT ... FROM A RIGHT JOIN B ON ...
-- стало:
SELECT ...
FROM B
LEFT JOIN A ON ...


4️⃣ FULL OUTER JOIN - «объединить всё»

«Свод по всем клиентам и всем заказам, даже если без пары».


SELECT COALESCE(c.id, o.customer_id) AS customer_key, c.name, o.id AS order_id
FROM customers c
FULL JOIN orders o ON o.customer_id = c.id;


Редко нужен в отчётах/сверках. Поддержка зависит от СУБД.

5️⃣ CROSS JOIN - «все комбинации»

«Собрать сетку метрик по всем регионам и кварталам».


SELECT r.region, q.quarter
FROM regions r
CROSS JOIN quarters q;


Осторожно: взрыв строк.

6️⃣ SELF JOIN - «сравнить строки внутри таблицы»

«Найти менеджера и его подчинённого».


SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.id = e.manager_id;


7️⃣ SEMI JOIN (EXISTS) - «фильтрация по факту наличия»

«Клиенты, у кого были заказы за 30 дней».


SELECT c.*
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
AND o.created_at >= CURRENT_DATE - INTERVAL '30 day'
);


Не размножает строки, часто быстрее, чем JOIN + DISTINCT.

8️⃣ ANTI JOIN (NOT EXISTS) - «кто без соответствий»

«Товары, которые ни разу не покупали в этом году».


SELECT p.*
FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE oi.product_id = p.id
AND o.created_at >= date_trunc('year', CURRENT_DATE)
);


Избегайте NOT IN с NULL - может дать пустой результат.

9️⃣ LATERAL / APPLY - «топ-N на строку»

«Последний заказ на клиента» (PostgreSQL: LATERAL, SQL Server: APPLY).


SELECT c.id, c.name, o_last.id AS last_order_id
FROM customers c
LEFT JOIN LATERAL (
SELECT o.id
FROM orders o
WHERE o.customer_id = c.id
ORDER BY o.created_at DESC
LIMIT 1
) o_last ON true;



💡Подводные камни

- LEFT JOIN + фильтр в WHERE ⇒ превращается в INNER.
Если нужно оставить «без пары», переносите условие в ON:


-- неверно
SELECT ... FROM c LEFT JOIN o ON o.customer_id = c.id
WHERE o.status = 'paid';

-- верно
SELECT ... FROM c LEFT JOIN o
ON o.customer_id = c.id AND o.status = 'paid';


- Дубликаты из «один-ко-многим». Перед JOIN делайте агрегацию в подзапросе/CTE.
- Индексы на ключах соединений (FK и соответствующие PK/UK) - must.
- Сопоставимость типов/колляций. Функции на ключе (LOWER(col)) ломают sargability - лучше нормализовать данные заранее.
- EXISTS чаще лучше, чем JOIN + DISTINCT для фильтрации.
- Проверяйте план. EXPLAIN (ANALYZE, BUFFERS) и сравнение альтернатив.

Сохрани, чтобы не забыть. А как вы чаще фильтруете - через JOIN+DISTINCT или EXISTS?


📲 Мы в MAX

#db #SQL

👉 @database_info
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41🔥1
Как оптимизировать Redis для высоких нагрузок?

Selectel приглашает на практический вебинар, где разберут целостный инженерный подход к оптимизации Redis под high-load — от памяти и клиентских запросов до мониторинга и нагрузочного тестирования.

Покажут, как настройки и паттерны использования Redis влияют на вытеснение ключей, p95/p99 задержки и стабильность системы.

📅 26 марта, 12:00
📍 Онлайн
👥 Для инженеров DevOps и DBA, бэкенд-разработчиков, системных администраторов и архитекторов

👉Смотрите полную программу и регистрируйтесь: https://slc.tl/gk6xp

Чтобы не пропустить вебинар и узнавать о других событиях и бесплатных курсах Selectel, подписывайтесь на @selectel_events

Реклама. АО "Селектел". erid:2W5zFJ1CQVP
👍1🔥1
Многопользовательская игра, похожая на DOOM, написанная на чистом SQL

DOOMQL - это экспериментальный проект, который позволяет играть в DOOM, используя SQL-запросы.
Идея проста: управление игрой происходит не через клавиатуру или мышь, а через выполнение SQL-команд, которые интерпретируются как действия внутри движка.

🔹 Например, можно отправить INSERT или UPDATE запрос, чтобы двигаться, стрелять или поворачивать персонажа.
🔹 Вся логика игрового процесса завязана на базу данных, превращая DOOM в своеобразный SQL-интерфейс.
🔹 Это скорее арт-проект, чем практичный инструмент, но отличный пример того, как базы данных можно использовать в самых неожиданных сценариях.

https://github.com/cedardb/DOOMQL

📲 Мы в MAX

#db #SQL

👉 @database_info
👍31👎1🔥1
🚀 Подборка полезных IT каналов в Max


Системное администрирование, DevOps 📌

https://max.ru/i_odmin Все для системного администратора
https://max.ru/bash_srv Bash Советы
https://max.ru/sysadminof Книги для админов, полезные материалы
https://max.ru/i_odmin_book Библиотека Системного Администратора
https://max.ru/i_devops DevOps: Пишем о Docker, Kubernetes и др.

1C разработка 📌
https://max.ru/odin1c_rus Cтатьи, курсы, советы, шаблоны кода 1С

Программирование C++📌

https://max.ru/cpp_lib Библиотека C/C++ разработчика

Программирование Python 📌
https://max.ru/python_of Python академия.
https://max.ru/BookPython Библиотека Python разработчика

Java разработка 📌
https://max.ru/bookjava Библиотека Java разработчика

GitHub Сообщество 📌
https://max.ru/githublib Интересное из GitHub

Базы данных (Data Base) 📌
https://max.ru/database_info Все про базы данных

Фронтенд разработка 📌
https://max.ru/frontend_1 Подборки для frontend разработчиков

Библиотеки 📌
https://max.ru/programmist_of Книги по программированию
https://max.ru/proglb Библиотека программиста
https://max.ru/bfbook Книги для программистов

Программирование 📌
https://max.ru/bookflow Лекции, видеоуроки, доклады с IT конференций
https://max.ru/itmozg Программисты, дизайнеры, новости из мира IT
https://max.ru/php_lib Библиотека PHP программиста 👨🏼‍💻👩‍💻

Шутки программистов 📌
https://max.ru/itumor Шутки программистов

Защита, взлом, безопасность 📌
https://max.ru/thehaking Канал о кибербезопасности
https://max.ru/xakkep_1 Хакер Free

Книги, статьи для дизайнеров 📌

https://max.ru/odesigners Статьи, книги для дизайнеров

Математика 📌
https://max.ru/Pomatematike Канал по математике
https://max.ru/phismat_1 Обучающие видео, книги по Физике и Математике

Вакансии 📌
https://max.ru/progjob Вакансии в IT

Мир технологий 📌
https://max.ru/mir_teh Канал для любознательных


Бонус 📌
https://max.ru/piterspb_78 Свежие новости Санкт-Петербурга
https://max.ru/mockva_life Свежие новости Москвы
👎2
Media is too big
VIEW IN TELEGRAM
Продвинутый курс SQL за час - проще некуда

Сегодня я продолжу рассказывать про SQL и мы погрузимся уже в чуть более интересные запросы, связи и я попробую рассказать максимально просто о связях join и о группировках, на мой взгляд две не самые простые темы.

Содержание:
00:00 - Поехали
01:14 - Сортировка по номеру
03:23 - Ограничение вывода limit
06:30 - Уникальность данных distinct
08:18 - Сложение колонок
11:00 - Псевдонимы
15:35 - join - связи таблиц
27:45 - Left join
29:38 - Right join
35:00 - Быть или не быть (exists)
39:32 - Объединения union
41:42 - Глобальный поиск
43:45 - Агрегатные функции
54:00 - Группировка данных group by

источник

📲 Мы в MAX

#db #SQL

👉 @database_info
👍51
⚠️ Вы уверенно используете JOIN и GROUP BY, но в какой-то момент понимаете: запрос становится громоздким, подзапросов всё больше, логика отчётов усложняется?

Оконные функции — инструмент, который позволяет считать без группировки, ранжировать строки, сравнивать значения и строить накопительные суммы без лишних соединений таблиц. На открытом уроке разберём, чем оконные функции отличаются от агрегатных, как работают PARTITION BY и ORDER BY в окне, и как с помощью ROW_NUMBER(), LAG(), LEAD() и SUM() OVER() упростить сложные запросы.

🚀 Вы научитесь писать более чистые и понятные выражения и поднимете свой уровень работы с данными.

➡️ Открытый урок проходит 31 марта в 20:00 МСК в преддверии старта курса «SQL для разработчиков и аналитиков».

Подробности и регистрация: https://vk.cc/cVMvzq

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
🔥1
7 SQL-запросов, которые решают 90% всех задач на работе

Каждый день одно и то же. Открываешь клиент базы данных, чтобы что-то проверить, посчитать или найти. И снова пишешь почти тот же SELECT, что и вчера, с тем же WHERE и JOIN. Знакомо?

SQL в большинстве случаях не требует сложные 100-строчные запросы с вложенными подзапросами на три уровня глубины. Чаще всего нам нужны простые, отточенные и, главное, эффективные конструкции.

В этой статье я собрал 7 таких запросов-«рабочих лошадок». Это не какой-то там справочник, а готовая шпаргалка для реальных задач.

https://habr.com/ru/companies/timeweb/articles/943298/

📲 Мы в MAX

#db

👉 @database_info
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥1
Media is too big
VIEW IN TELEGRAM
🖥 Основы SQL за час - бесплатно и проще некуда

Содержание:
00:00 - подготавливаем базу
04:36 - выбираем данные SELECT
08:30 - указываем текущую базу данных
09:58 - чувствительность к регистру
12:14 - выбор определенных колонок
15:28 - фильтрация с помощью WHERE
20:59 - фильтрация с И и ИЛИ
26:38 - поиск строки по шаблону
29:18 - свой среди чужих
31:26 - NULL когда данные отсутствуют
34:27 - сортировка данных в SQL
38:37 - добавление записей с помощью SQL запроса
45:36 - обновление данных с помощью SQL
52:32 - удаляем данные из базы с помощью SQL

источник

📲 Мы в MAX

#db

👉 @database_info
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1