Как достичь наибольшей эффективности при формировании сырых SQL запросов?

Как лучше обработать опциональные аргументы для формирования фильтрации в функции, которая возвращает данные из базы данных? Я хочу избежать множества проверок и проработки всех возможных вариантов. ОРМ не подходит по некоторым причинам.

Пример кода:

```python
def get_user(*, username: str = None, email: str = None, id: int = None, is_blocked: bool = None):
    SQL = "SELECT * FROM USERS {} LIMIT %s OFFSET %s"
    additional_conditions = []
    
    if username:
        additional_conditions.append("username = '%s'" % username)
    if email:
        additional_conditions.append("email = '%s'" % email)
    if id:
        additional_conditions.append("id = '%s'" % id)
    
    condition_str = "WHERE " + " AND ".join(additional_conditions) if additional_conditions else ""
    query = SQL.format(condition_str)
    
    cursor.execute(query)
```
Мой вопрос состоит в том, как избежать множества проверок и проработки различных комбинаций аргументов при формировании фильтрации в данной функции, которая возвращает данные из базы данных? Основной критерий - не использовать ОРМ. 
  • 28 октября 2023 г. 16:48
Ответы на вопрос 4
Если вы хотите избежать множества проверок и проработки различных комбинаций аргументов при формировании фильтрации, можно воспользоваться следующим подходом:

1. Создайте словарь, где ключами будут имена аргументов, а значениями будут соответствующие значения аргументов.

```python
args = {
    "username": username,
    "email": email,
    "id": id,
    "is_blocked": is_blocked
}
```

2. Используйте функцию `filter()` для отфильтровывания пустых значений аргументов из словаря.

```python
filtered_args = {k: v for k, v in args.items() if v is not None}
```

3. Преобразуйте фильтрованные аргументы в список условий для SQL запроса, используя генератор списка.

```python
conditions = [f"{k} = '{v}'" for k, v in filtered_args.items()]
```

4. Создайте строку условия, объединив условия с помощью оператора `AND`.

```python
condition_str = "WHERE " + " AND ".join(conditions) if conditions else ""
```

5. Сформируйте финальный SQL запрос, вставив строку условия в шаблон запроса.

```python
query = SQL.format(condition_str)
```

6. Выполните SQL запрос.

```python
cursor.execute(query)
```

Такой подход позволит вам избежать множества проверок и ручной обработки различных комбинаций аргументов для формирования фильтрации. Вы будете использовать только одну проверку на пустое значение для каждого аргумента, а затем преобразовывать фильтрованные аргументы в условия для SQL запроса.
Отвечая на вопрос из заголовка, нет способа автоматически создать оптимизированный план исполнения запросов в СУБД. Вы можете вручную оптимизировать каждый запрос, используя индексы, джойны, оконные функции и другие средства. Однако, не существует универсального автоматического метода, который работал бы для всех случаев.


Один из способов упростить построение запросов - добавить условие 1=1 в базовый SQL шаблон. Тогда можно добавлять дополнительные условия, используя оператор AND. Например:

def get_user(*, username: str = None, email: str = None, id: int = None, is_blocked: bool = None):
    SQL = "SELECT * FROM USERS WHERE 1=1 {} LIMIT %s OFFSET %s"
    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)

Я рекомендую проверить этот код на валидность и вносить соответствующие исправления.


Часто, чтобы обработать ситуацию, когда в запросе может быть передано значение, а может и не быть, используют такую конструкцию: 

... AND (column = {parameter} OR {parameter} IS NULL) ..

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

При таком подходе не нужно заботиться о том, когда добавлять условие WHERE. Исходный шаблон SQL запроса выглядит следующим образом: 

SQL = "SELECT * FROM USERS WHERE 1=1 {} LIMIT %s OFFSET %s"

Дополнительные условия добавляются в виде AND {условие}. Тогда нет необходимости проверять, является ли это первым условием или нет. Само условие 1=1 преобразуется в TRUE и отсеивается на этапе построения плана.
Похожие вопросы