Используйте
Если вам нужно проверить, существует ли конкретная запись в данных, не стоит использовать
Вот наивный вариант:
База данных должна посчитать все заказы для каждого клиента, прежде чем проверить условие.
Вместо
Кроме того, если по столбцу, который участвует в условии, есть индекс, база данных сможет находить подходящие строки ещё быстрее, что дополнительно улучшает производительность. Поэтому
Когда индекс уже есть, база данных может быстро найти подходящие строки в таблице
👉 @SQLPortal
EXISTS вместо COUNT(*) для проверки существованияЕсли вам нужно проверить, существует ли конкретная запись в данных, не стоит использовать
COUNT() с фильтром (WHERE).COUNT(*) заставляет базу данных посчитать все подходящие строки, прежде чем определить, есть ли вообще хоть одна строка. Из-за этого база делает лишнюю работу, особенно на больших таблицах. Как однажды пошутили: использовать COUNT() для проверки существования, это как проверять, есть ли кто-нибудь дома, по данным переписи населения.Вот наивный вариант:
SELECT *
FROM Customers c
WHERE (
SELECT COUNT(*)
FROM Orders o
WHERE o.CustomerID = c.CustomerID
) > 0;
База данных должна посчитать все заказы для каждого клиента, прежде чем проверить условие.
Вместо
COUNT() правильнее использовать EXISTS. Оператор EXISTS прекращает поиск сразу после того, как находит первую подходящую строку. Это позволяет движку БД сделать short-circuit при поиске, а сам запрос становится эффективнее и лучше масштабируется.Кроме того, если по столбцу, который участвует в условии, есть индекс, база данных сможет находить подходящие строки ещё быстрее, что дополнительно улучшает производительность. Поэтому
EXISTS считается стандартным подходом в случаях, когда нужно ответить только на один вопрос: существует ли хотя бы одна подходящая строка. Пример:-- Создайте индекс, чтобы ускорить поиск
CREATE INDEX idx_orders_customerid
ON Orders(CustomerID);
-- Используйте EXISTS, чтобы проверить, есть ли хотя бы один заказ
SELECT *
FROM Customers c
WHERE EXISTS (
SELECT 1
FROM Orders o
WHERE o.CustomerID = c.CustomerID
);
Когда индекс уже есть, база данных может быстро найти подходящие строки в таблице
Orders и остановить поиск сразу после первого совпадения. Это делает запрос заметно эффективнее, особенно при работе с большими наборами данных.👉 @SQLPortal
👍12
Можно ли восстановить данные после DELETE?
В PostgreSQL операция DELETE не стирает данные с диска мгновенно. Механизм MVCC сохраняет удалённые строки в виде dead tuples, и чтение этих dead tuples является одним из рабочих способов восстановления данных.
Однако у этого подхода есть очевидное ограничение по времени: как только autovacuum завершает очистку, dead tuples физически удаляются, и методы восстановления, основанные на файлах данных, перестают работать.
В этот момент альтернативный путь восстановления даёт WAL (Write-Ahead Log). В частности, основой этого подхода служит механизм FPW (Full Page Write) внутри WAL. Инструменты восстановления после DELETE в PostgreSQL, включая PDU (PostgreSQL Data Unloader), опираются именно на эту технику. Её ключевое свойство такое: пока файлы WAL, созданные в период удаления, всё ещё существуют, данные можно полностью восстановить независимо от того, сколько времени прошло.
В этой статье этот путь восстановления разбирается по функциям, шаг за шагом, на основе исходного кода PostgreSQL 18.
👉 @SQLPortal
В PostgreSQL операция DELETE не стирает данные с диска мгновенно. Механизм MVCC сохраняет удалённые строки в виде dead tuples, и чтение этих dead tuples является одним из рабочих способов восстановления данных.
Однако у этого подхода есть очевидное ограничение по времени: как только autovacuum завершает очистку, dead tuples физически удаляются, и методы восстановления, основанные на файлах данных, перестают работать.
В этот момент альтернативный путь восстановления даёт WAL (Write-Ahead Log). В частности, основой этого подхода служит механизм FPW (Full Page Write) внутри WAL. Инструменты восстановления после DELETE в PostgreSQL, включая PDU (PostgreSQL Data Unloader), опираются именно на эту технику. Её ключевое свойство такое: пока файлы WAL, созданные в период удаления, всё ещё существуют, данные можно полностью восстановить независимо от того, сколько времени прошло.
В этой статье этот путь восстановления разбирается по функциям, шаг за шагом, на основе исходного кода PostgreSQL 18.
Please open Telegram to view this post
VIEW IN TELEGRAM
Pduzc
PDU - PostgreSQL Data Unloader
Professional PostgreSQL data recovery tool for corrupted databases
👍3
Аргумент «SQL не масштабируется» — это ленивое и абсурдное обобщение.
Настоящий вопрос не в том, масштабируется SQL или NoSQL. Вопрос в том, соответствует ли база данных вашим требованиям. Если какая-то функция критична для вашего кейса и есть только в одной базе данных, будете ли вы выбирать что-то другое? Конечно нет.
Любая база данных и масштабируется, и не масштабируется одновременно. Всё зависит от того, какой use case вы на ней строите. Если у вас 100 запросов в секунду, зачем вообще заморачиваться с шардингом? Одного инстанса на одном узле будет более чем достаточно.
Масштабируемость — лишь один из факторов при выборе базы данных, а не единственный. Всегда смотрите на ключевые свойства, потребности и требования вашего кейса, а затем выбирайте подходящую БД. Но будьте готовы жить с её недостатками и ограничениями.
Если кто-то говорит вам, что SQL не масштабируется, спросите, что именно он имеет в виду.
👉 @SQLPortal
Настоящий вопрос не в том, масштабируется SQL или NoSQL. Вопрос в том, соответствует ли база данных вашим требованиям. Если какая-то функция критична для вашего кейса и есть только в одной базе данных, будете ли вы выбирать что-то другое? Конечно нет.
Любая база данных и масштабируется, и не масштабируется одновременно. Всё зависит от того, какой use case вы на ней строите. Если у вас 100 запросов в секунду, зачем вообще заморачиваться с шардингом? Одного инстанса на одном узле будет более чем достаточно.
Масштабируемость — лишь один из факторов при выборе базы данных, а не единственный. Всегда смотрите на ключевые свойства, потребности и требования вашего кейса, а затем выбирайте подходящую БД. Но будьте готовы жить с её недостатками и ограничениями.
Если кто-то говорит вам, что SQL не масштабируется, спросите, что именно он имеет в виду.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3
Твой индекс в MySQL не медленный, он просто фрагментирован :)
Базы данных вроде MySQL, которые хранят данные в B+-деревьях, страдают от фрагментации индексов, и это серьезно влияет на производительность. Сейчас объясню.
Фрагментация индекса возникает, когда страницы индекса B+-дерева содержат значительное количество свободного пространства вместо плотного размещения данных. Но почему так происходит?
В MySQL движок InnoDB хранит данные в кластеризованных индексах (организованных по первичному ключу). Когда кластеризованный индекс фрагментируется из-за случайных вставок по первичному ключу (например, UUID), производительность чтения данных ухудшается.
Поскольку движок старается сохранять порядок листовых узлов, при вставке строки в середину происходит split страницы. Разделение страницы выполняется, когда в ней недостаточно свободного места или превышен порог split’а. Со временем повторяющиеся случайные вставки вызывают всё больше таких разделений, из-за чего тратится лишнее пространство.
Фрагментация индекса напрямую влияет на производительность запросов и использование памяти. Когда индексы фрагментированы, базе данных приходится читать больше страниц с диска для выполнения того же запроса, что увеличивает количество I/O-операций и снижает пропускную способность.
Кстати, при последовательных вставках фрагментация минимальна, поскольку InnoDB просто создаёт новые страницы без разделения существующих и размещает их вдоль крайнего правого пути дерева. Это помогает сохранять оптимальную плотность страниц.
Управлять фрагментацией индексов можно, настроив параметр
Надеюсь, было интересно :)
👉 @SQLPortal
Базы данных вроде MySQL, которые хранят данные в B+-деревьях, страдают от фрагментации индексов, и это серьезно влияет на производительность. Сейчас объясню.
Фрагментация индекса возникает, когда страницы индекса B+-дерева содержат значительное количество свободного пространства вместо плотного размещения данных. Но почему так происходит?
В MySQL движок InnoDB хранит данные в кластеризованных индексах (организованных по первичному ключу). Когда кластеризованный индекс фрагментируется из-за случайных вставок по первичному ключу (например, UUID), производительность чтения данных ухудшается.
Поскольку движок старается сохранять порядок листовых узлов, при вставке строки в середину происходит split страницы. Разделение страницы выполняется, когда в ней недостаточно свободного места или превышен порог split’а. Со временем повторяющиеся случайные вставки вызывают всё больше таких разделений, из-за чего тратится лишнее пространство.
Фрагментация индекса напрямую влияет на производительность запросов и использование памяти. Когда индексы фрагментированы, базе данных приходится читать больше страниц с диска для выполнения того же запроса, что увеличивает количество I/O-операций и снижает пропускную способность.
Кстати, при последовательных вставках фрагментация минимальна, поскольку InnoDB просто создаёт новые страницы без разделения существующих и размещает их вдоль крайнего правого пути дерева. Это помогает сохранять оптимальную плотность страниц.
Управлять фрагментацией индексов можно, настроив параметр
innodb_fill_factor и/или выполнив следующий запрос:ALTER TABLE tbl_name FORCE;
Надеюсь, было интересно :)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3
Если номер телефона (`phone`) равен `NULL`, а `backup_phone` равен нулю,
что вернёт этот запрос?
A)
B) ноль (
C)
D) ошибка
👉 @SQLPortal
что вернёт этот запрос?
SELECT
COALESCE(phone, backup_phone, 'Not provided') AS primary_contact
FROM customers;
A)
NULLB) ноль (
0)C)
'Not provided'D) ошибка
Please open Telegram to view this post
VIEW IN TELEGRAM
Совет по Data Engineering: различайте трансформации и действия в Apache Spark
При работе с Apache Spark понимание разницы между transformations и actions имеет ключевое значение.
Transformations (трансформации) — это операции над DataFrame или Dataset, которые создают новый распределённый набор данных на основе существующего.
Обычно они ленивые (lazy) — то есть выполняются не сразу, а лишь формируют логический план выполнения.
Примеры трансформаций из кода:
👉
👉
👉
👉
👉
Эти операции описывают, что должен сделать Spark, но фактически выполняются только после вызова действия.
Actions (действия), в свою очередь, запускают выполнение трансформаций и возвращают результат в driver-программу или выводят его.
Примеры действий:
👉
👉
👉 @SQLPortal
При работе с Apache Spark понимание разницы между transformations и actions имеет ключевое значение.
Transformations (трансформации) — это операции над DataFrame или Dataset, которые создают новый распределённый набор данных на основе существующего.
Обычно они ленивые (lazy) — то есть выполняются не сразу, а лишь формируют логический план выполнения.
Примеры трансформаций из кода:
👉
spark.read.option()👉
withColumn()👉
filter()👉
groupBy()👉
count()Эти операции описывают, что должен сделать Spark, но фактически выполняются только после вызова действия.
Actions (действия), в свою очередь, запускают выполнение трансформаций и возвращают результат в driver-программу или выводят его.
Примеры действий:
👉
show(5)👉
collect()Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3
Много NULL-ов или неактивных строк в PostgreSQL? Настройте частичный индекс (partial index), чтобы индексировались только те данные, по которым вы выполняете запросы. Такие индексы меньше по размеру и уменьшают нагрузку на операции записи.
👉 @SQLPortal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Причины, почему SELECT * плохо влияет на производительность SQL: читать
Старый блог (с примерами, специфичными для Oracle), где приводится целый ряд причин. Дело не только в сетевом трафике и выполнении плана SQL-запроса, но также в обработке данных на стороне клиента и использовании памяти.
👉 @SQLPortal
Старый блог (с примерами, специфичными для Oracle), где приводится целый ряд причин. Дело не только в сетевом трафике и выполнении плана SQL-запроса, но также в обработке данных на стороне клиента и использовании памяти.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4
Никогда не предполагайте, что SQL вычисляет условия слева направо
Если вы пишете код так:
Можно предположить, что запрос будет вычисляться слева направо, и это предотвратит ошибку деления на ноль. Однако это не всегда так. Большинство крупных SQL-баз данных НЕ гарантируют short-circuit-вычисление условий AND в WHERE. И даже если в простых случаях кажется, что short-circuit работает, оптимизатор часто перестраивает или вычисляет выражения вне исходного порядка по соображениям производительности.
Это означает, что база данных может попытаться вычислить:
раньше, чем:
То есть, если какое-либо значение в
Таким образом, даже несмотря на то, что логически условие
Чтобы обезопасить себя, можно использовать оператор
Никогда не используйте деление в
👉 @SQLPortal
Если вы пишете код так:
SELECT * FROM products
WHERE price > 100
AND (total_sales / price) > 10;
Можно предположить, что запрос будет вычисляться слева направо, и это предотвратит ошибку деления на ноль. Однако это не всегда так. Большинство крупных SQL-баз данных НЕ гарантируют short-circuit-вычисление условий AND в WHERE. И даже если в простых случаях кажется, что short-circuit работает, оптимизатор часто перестраивает или вычисляет выражения вне исходного порядка по соображениям производительности.
Это означает, что база данных может попытаться вычислить:
total_sales / priceраньше, чем:
price > 100.То есть, если какое-либо значение в
price равно нулю, может возникнуть ошибка деления на ноль.Таким образом, даже несмотря на то, что логически условие
price > 100 должно предотвращать деление на ноль, фактический порядок выполнения не гарантирует этого.Чтобы обезопасить себя, можно использовать оператор
NULLIF в WHERE:SELECT *
FROM products
WHERE price > 100
AND (total_sales / NULLIF(price, 0)) > 10;
Никогда не используйте деление в
WHERE, если знаменатель не защищён через NULLIF(), не переписан в умноженную форму (например, numerator > denominator * constant) или не обёрнут в CASE.Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🏆2🤔1
Представили pg_duckpipe: CDC в реальном времени для вашего Lakehouse
TL;DR:
pg_duckpipe — это новое расширение для PostgreSQL, которое непрерывно синхронизирует обычные heap-таблицы с колонночными таблицами в DuckLake, используя CDC на основе WAL.
Чтобы запустить синхронизацию, достаточно одного SQL-вызова — без внешней инфраструктуры.
https://pgducklake.select/blog/introducing-pg-duckpipe/
👉 @SQLPortal
TL;DR:
pg_duckpipe — это новое расширение для PostgreSQL, которое непрерывно синхронизирует обычные heap-таблицы с колонночными таблицами в DuckLake, используя CDC на основе WAL.
Чтобы запустить синхронизацию, достаточно одного SQL-вызова — без внешней инфраструктуры.
https://pgducklake.select/blog/introducing-pg-duckpipe/
Please open Telegram to view this post
VIEW IN TELEGRAM
pgducklake.select
Introducing pg_duckpipe: Real-Time CDC for Your Lakehouse
Automatically keep a fast, analytical copy of your PostgreSQL tables, updated in real time with no external tools needed.
👍4❤1
Вам нужно выполнить запрос по иерархии? Рекурсивные CTE могут быть очень полезны. Они начинают с якорной точки (anchor point) и проходят через CTE до конца.
Рекурсивный CTE состоит из двух частей:
1/ Якорный запрос (Anchor Query) — это верхний уровень / начальная точка.
2/ Рекурсивный запрос (Recursive Query) — он ссылается на CTE и продолжает выполняться, пока не будет выполнено условие остановки.
Рекурсивный запрос будет продолжать вызывать сам себя, пока больше не будут возвращаться строки.
👉 @SQLPortal
Рекурсивный CTE состоит из двух частей:
1/ Якорный запрос (Anchor Query) — это верхний уровень / начальная точка.
2/ Рекурсивный запрос (Recursive Query) — он ссылается на CTE и продолжает выполняться, пока не будет выполнено условие остановки.
Рекурсивный запрос будет продолжать вызывать сам себя, пока больше не будут возвращаться строки.
WITH RECURSIVE EmployeeHierarchy AS (
-- Якорь / начальная точка
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE id = 1
UNION ALL
-- Рекурсивный случай: найти элементы, которые относятся к предыдущему уровню
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
INNER JOIN EmployeeHierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM EmployeeHierarchy;
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Как мы даём каждому пользователю доступ к SQL в общем кластере ClickHouse
Как позволить пользователям писать произвольный SQL для общей мультиарендной (multi-tenant) аналитической базы данных — не раскрывая данные других арендаторов и не давая «плохому» запросу уронить кластер?
Именно эту задачу нам нужно было решить для Query & Dashboards. Решение — TRQL (Trigger Query Language), язык в стиле SQL, который компилируется в безопасные, изолированные по арендаторам запросы для ClickHouse. Пользователи пишут привычный SQL, а TRQL берёт на себя безопасность, абстракцию и трансляцию.
В этом посте — подробный разбор того, как всё устроено. Мы рассмотрим дизайн языка, пайплайн компиляции, систему схем и фичи, которые делают TRQL чем-то большим, чем просто проксирование SQL-запросов.
👉 @SQLPortal
Как позволить пользователям писать произвольный SQL для общей мультиарендной (multi-tenant) аналитической базы данных — не раскрывая данные других арендаторов и не давая «плохому» запросу уронить кластер?
Именно эту задачу нам нужно было решить для Query & Dashboards. Решение — TRQL (Trigger Query Language), язык в стиле SQL, который компилируется в безопасные, изолированные по арендаторам запросы для ClickHouse. Пользователи пишут привычный SQL, а TRQL берёт на себя безопасность, абстракцию и трансляцию.
В этом посте — подробный разбор того, как всё устроено. Мы рассмотрим дизайн языка, пайплайн компиляции, систему схем и фичи, которые делают TRQL чем-то большим, чем просто проксирование SQL-запросов.
Please open Telegram to view this post
VIEW IN TELEGRAM
X (formerly Twitter)
Matt – Trigger.dev (@mattaitken) on X
How we give every user SQL access to a shared ClickHouse cluster
❤3👍3
Знаете ли вы, что
👉 @SQLPortal
EXPLAIN может выводить результат в формате JSON? Это удобно для использования специализированных инструментов для просмотра планов выполнения или для их самостоятельного анализа.EXPLAIN (ANALYZE, FORMAT JSON, BUFFERS) SELECT * FROM employees;
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
В PostgreSQL 18 появились виртуальные вычисляемые столбцы (virtual generated columns). Generated columns позволяют создавать новый столбец на основе других данных. Такие столбцы не хранятся на диске и вычисляются на лету.
Это работает только для иммутабельных функций, без подзапросов.
👉 @SQLPortal
Это работает только для иммутабельных функций, без подзапросов.
CREATE TABLE products (
id serial PRIMARY KEY,
price numeric,
tax_rate numeric DEFAULT 0.099995,
total_price numeric GENERATED ALWAYS AS (price * (1 + tax_rate)) VIRTUAL
);
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2😁2👍1💊1
Решение медленных запросов часто сводится к добавлению ещё одного индекса. Но переиндексация — вполне реальная проблема, и есть менее известная фича в Postgres получше: CREATE STATISTICS 💡
Я часто ловил себя на том, что пытаюсь починить медленный запрос или плохой execution plan, добавляя ещё один, более специфичный индекс. В большинстве случаев это работало, но… чёрт, это было дорого. Не только по дисковому пространству, но и по стоимости обновления индекса на пути записи (write path).
Малоизвестная возможность в Postgres — это расширенная статистика (extended statistics). Это то, о чём должен знать каждый, кто работает с PG. И я сам слишком долго был по другую сторону — жаль, что не узнал об этом раньше🫣
PostgreSQL уже из коробки имеет немало информации о ваших таблицах. Однако эти данные в основном по отдельным колонкам (есть исключения) и не отражают взаимосвязи между двумя и более колонками.
CREATE STATISTICS (или extended statistics) позволяет PG собирать дополнительную многоколоночную статистику, чтобы учитывать зависимости между колонками, понимать вероятные, маловероятные и невозможные комбинации значений, а также улучшать оценку количества строк (row estimates). Причём улучшения могут быть больше чем на порядок
Это важно, потому что в реальных данных столбцы обычно не независимы друг от друга. Как кофе без кружки — это просто чёрная вода на столе.
Самое приятное — это почти ничего не стоит. Практически не требует памяти или дискового пространства и намного легче поддерживается в актуальном состоянии при изменениях данных.
И использовать это очень просто:
Требование по хранению — около 2 KiB. Аналогичный индекс занял бы ~30 MiB для того же набора данных. И execution plans при этом, как правило, получаются лучше.
Полный разбор — в этом блоге, там же есть бенчмарк (с сгенерированными execution plan’ами и отчётами) на GitHub.👇
- Benchmark code + reports
- Blog post
👉 @SQLPortal
Я часто ловил себя на том, что пытаюсь починить медленный запрос или плохой execution plan, добавляя ещё один, более специфичный индекс. В большинстве случаев это работало, но… чёрт, это было дорого. Не только по дисковому пространству, но и по стоимости обновления индекса на пути записи (write path).
Малоизвестная возможность в Postgres — это расширенная статистика (extended statistics). Это то, о чём должен знать каждый, кто работает с PG. И я сам слишком долго был по другую сторону — жаль, что не узнал об этом раньше
PostgreSQL уже из коробки имеет немало информации о ваших таблицах. Однако эти данные в основном по отдельным колонкам (есть исключения) и не отражают взаимосвязи между двумя и более колонками.
CREATE STATISTICS (или extended statistics) позволяет PG собирать дополнительную многоколоночную статистику, чтобы учитывать зависимости между колонками, понимать вероятные, маловероятные и невозможные комбинации значений, а также улучшать оценку количества строк (row estimates). Причём улучшения могут быть больше чем на порядок
Это важно, потому что в реальных данных столбцы обычно не независимы друг от друга. Как кофе без кружки — это просто чёрная вода на столе.
Самое приятное — это почти ничего не стоит. Практически не требует памяти или дискового пространства и намного легче поддерживается в актуальном состоянии при изменениях данных.
И использовать это очень просто:
CREATE STATISTICS my_stats (mcv, dependencies)
ON region, plan_tier, billing_status
FROM tenants;
Требование по хранению — около 2 KiB. Аналогичный индекс занял бы ~30 MiB для того же набора данных. И execution plans при этом, как правило, получаются лучше.
Полный разбор — в этом блоге, там же есть бенчмарк (с сгенерированными execution plan’ами и отчётами) на GitHub.
- Benchmark code + reports
- Blog post
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Концепции SQL, которые действительно нужно знать:
• CRUD → SELECT, INSERT, UPDATE, DELETE
• Ключи → PRIMARY KEY, FOREIGN KEY
• Ограничения → NOT NULL, UNIQUE, CHECK, DEFAULT
• Джоины → INNER JOIN, LEFT JOIN, RIGHT JOIN
• Агрегация → COUNT, SUM, AVG, MIN, MAX
• Группировка → GROUP BY, HAVING
• Фильтрация → WHERE, BETWEEN, IN, LIKE
• Сортировка → ORDER BY
• Подзапросы → SELECT (SELECT …)
• Индексы → CREATE INDEX
• Представления → CREATE VIEW
• Транзакции → BEGIN, COMMIT, ROLLBACK
• Пагинация → LIMIT, OFFSET
• Оптимизация → EXPLAIN
👉 @SQLPortal
• CRUD → SELECT, INSERT, UPDATE, DELETE
• Ключи → PRIMARY KEY, FOREIGN KEY
• Ограничения → NOT NULL, UNIQUE, CHECK, DEFAULT
• Джоины → INNER JOIN, LEFT JOIN, RIGHT JOIN
• Агрегация → COUNT, SUM, AVG, MIN, MAX
• Группировка → GROUP BY, HAVING
• Фильтрация → WHERE, BETWEEN, IN, LIKE
• Сортировка → ORDER BY
• Подзапросы → SELECT (SELECT …)
• Индексы → CREATE INDEX
• Представления → CREATE VIEW
• Транзакции → BEGIN, COMMIT, ROLLBACK
• Пагинация → LIMIT, OFFSET
• Оптимизация → EXPLAIN
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3