← Назад к вопросам

Как делать смещение выборки в SQL запросе?

1.0 Junior🔥 201 комментариев
#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Смещение выборки в SQL (OFFSET и LIMIT)

Смещение выборки — это техника пагинации и получения подмножества результатов из таблицы. Используются два ключевых предложения: LIMIT (количество строк) и OFFSET (смещение от начала).

Синтаксис

Стандартный подход (PostgreSQL, MySQL, SQLite)

SELECT * FROM users
ORDER BY id
LIMIT 10
OFFSET 20;

Выборка начинается со строки 21 и получает 10 результатов (строки 21-30).

Альтернативный синтаксис (PostgreSQL, MySQL 8+)

SELECT * FROM users
ORDER BY id
LIMIT 10 OFFSET 20;

MSSQL синтаксис (OFFSET ... ROWS ... FETCH)

SELECT * FROM users
ORDER BY id
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY;

Практические примеры

Пример 1: Пагинация

-- Первая страница (10 элементов)
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 0;

-- Вторая страница
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10;

-- Третья страница
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20;

Пример 2: Пропуск последних n записей

SELECT * FROM orders
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT 100 OFFSET 50;

Пример 3: Получение n-ой записи

-- Получить 5-ю запись
SELECT * FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 4;

В Python с использованием ORM

SQLAlchemy (Session Query API)

from sqlalchemy import select

# LIMIT + OFFSET
query = select(User).order_by(User.id).limit(10).offset(20)
result = session.execute(query).scalars().all()

SQLAlchemy 2.0 (Core)

from sqlalchemy import select, func

# Пагинация
page = 2
page_size = 10
offset = (page - 1) * page_size

stmt = select(User).order_by(User.id).limit(page_size).offset(offset)
users = session.execute(stmt).scalars().all()

Django ORM

# LIMIT + OFFSET
users = User.objects.all().order_by('id')[20:30]  # Эквивалент LIMIT 10 OFFSET 20

Важные особенности и оптимизация

1. ВСЕГДА используй ORDER BY

Без ORDER BY результаты непредсказуемы:

-- Плохо
SELECT * FROM users LIMIT 10 OFFSET 20;

-- Хорошо
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;

2. Проблема производительности при больших OFFSET

Основная проблема: база читает и выбрасывает все строки до OFFSET. Для больших смещений это неэффективно:

-- Может быть медленно при OFFSET 1000000
SELECT * FROM huge_table ORDER BY id LIMIT 10 OFFSET 1000000;

Решение 1: Keyset Pagination (Cursor-based)

SELECT * FROM users
WHERE id > LAST_SEEN_ID
ORDER BY id
LIMIT 10;

Решение 2: Кэширование ID или временные таблицы

# Кэш последнего ID
last_id = cache.get('pagination_key')
query = f'SELECT * FROM users WHERE id > {last_id} ORDER BY id LIMIT 10'

3. Сложность с COUNT при пагинации

-- Дорогая операция для больших таблиц
SELECT COUNT(*) FROM users;  -- Полный скан

-- Альтернатива: использовать приблизительные значения
SELECT COALESCE(reltuples, 0) as estimate
FROM pg_class WHERE relname = 'users';  -- только PostgreSQL

Практический паттерн пагинации в Python

from typing import Tuple, List
from sqlalchemy import select, func

class UserRepository:
    def paginate(
        self,
        page: int = 1,
        page_size: int = 10
    ) -> Tuple[List[dict], int]:
        """Пагинированная выборка с подсчётом"""
        offset = (page - 1) * page_size
        
        # Выборка
        stmt = select(User).order_by(User.id).limit(page_size).offset(offset)
        users = session.execute(stmt).scalars().all()
        
        # Подсчёт (кэшируй эту операцию!)
        count_stmt = select(func.count()).select_from(User)
        total = session.execute(count_stmt).scalar()
        
        return users, total

# Использование
users, total = repo.paginate(page=2, page_size=20)
total_pages = (total + page_size - 1) // page_size

Заключение

OFFSET/LIMIT — это стандартный способ пагинации, но для больших таблиц (>1M записей) лучше использовать Keyset Pagination. Всегда помни про ORDER BY и оптимизацию с индексами.