Как формировать сырые SQL запросы максимально эффективно?

Всем привет, возник вопрос. Вот у меня есть функция которая возвращает данные из бд, в эту функцию опционально приходят от 1 до 5(Условно) аргументов, по которым(при наличии) должна формироваться фильтрация. Как правыильно сделать подстановку аргументов, чтобы не мучаться с бесконечными проверками и прорабатыванием всех вариантов. (ОРМ не подходит, по некоторым причинам).
Пример кода

def get_user(*, username: str = None, email: str = None, id: int = None, is_blocked: bool = None):
    SQL = "SELECT * FROM USERS {} LIMIT %s OFFSET %s"
    more = []
    if username:
        more.append("WHERE username = '%s'" % username)
    if email:
        if any([username]):
            more.append("& email = '%s'" % email)
        else:
            more.append("WHERE email = '%s'" % email)
    if id:
        if any([username, email]):
            more.append("& id = '%s'" % id)
        else:
            more.append("WHERE id = '%s'" % id)
    ...
    cursor.execute(SQL.format(" ".join(more)))

  • Вопрос задан
  • 233 просмотра
Решения вопроса 2
mayton2019
@mayton2019
Bigdata Engineer
Я в самодельных билдерах запросов добавлял фейковый предикат 1=1 который всегда был. Тогда добавление новых предикатов делается проще. Вот как-то так.

def get_user(*, username: str = None, email: str = None, id: int = None, is_blocked: bool = None):
    SQL = "SELECT * FROM USERS {} LIMIT %s OFFSET %s WHERE 1=1 "
    more = []
    if username:
        more.append("AND username = '%s'" % username)
    if email:
        if any([username]):
            more.append("AND email = '%s'" % email)
    if id:
        if any([username, email]):
            more.append("AND id = '%s'" % id)

Я проверял этот код на валидность. Это лишь идея как сделать. Поэтому исправляй дальше сам.
Ответ написан
Комментировать
@Akina
Сетевой и системный админ, SQL-программист.
Обычно если в запрос может быть передано значение, а может и нет, делают так:
... AND (column = {parameter} OR {parameter} IS NULL) ..

При подстановке параметра сервер уже на этапе построения плана выполнения запроса, зная значение {parameter}, получит либо
... AND (column = {parameter} OR FALSE) .. ==> ... AND (column = {parameter}) ..
либо
... AND (column = {parameter} OR TRUE) .. ==> ... AND (TRUE) ...

PS. Чтобы не геморроиться с определением, когда ставить WHERE, начальный шаблон делают такой:
SQL = "SELECT * FROM USERS WHERE 1=1 {} LIMIT %s OFFSET %s"

и все дополнительные условия формируют в виде
AND {условие}
Тогда не надо оглядываться, первое это условие или нет.

Само же условие 1=1 будет обращено в TRUE и отброшено опять-таки на этапе построения плана.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
dimonchik2013
@dimonchik2013
non progredi est regredi
params = {"id": "Fuck",   "email":"You",   "username": "Spielberg"}

paramkeys = list(params.keys())

where_clause = "WHERE " + " AND ".join([f"{col} = %s" for col in paramkeys])

sql = f"SELECT * FROM users {where_clause} LIMIT %s OFFSET %s"
Ответ написан
Комментировать
delphinpro
@delphinpro
frontend developer
Нафига столько лишних условий. Просто закидываем все условия в массив и объедияем его в строку

where = []
if username:
    where.append("username = '%s'" % username)
if email:
    where.append("email = '%s'" % email)
if id:
    where.append("id = '%s'" % id)
...
whereString = "WHERE " + " AND ".join(where);


Ну там еще добавить проверку в конце на пустоту where и тогда whereString оставить пустым.
Или как выше отметили, добавить условие 1=1
where = []
where.append("1 = 1")
if username:
...
Ответ написан
Stalker_RED
@Stalker_RED
Отвечая на вопрос из заголовка - никак.

План исполнения запроса делается самим движком СУБД. Вы можете оптимизировать конкретный запрос проиграв с индексами, джоинами, оконными функциями и т.п.
Но вы не можете написать "универсальный автоматический построитель оптимизированных запросов на все случаи жизни".

ОРМ даже не пытается в производительность, они пытаются сделать "удобненько для людей слабо знакомых с sql".
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы