如果提供了空的元素列表,如何使 IN 表达式成为可选参数?

How to make IN expression an optional parameter if empty list of elements was provided?

如果提供了空列表作为参数,但我没有这样做,我想让 IN 语句成为我的 sql 查询的可选部分。我可以解决问题并在代码中使用一些默认值而不是空列表(所有 cam_ids),但我想知道如何正确地做到这一点。

我有以下sql表达式(真正的表达式要长得多):

SELECT 
 id, cam_id
 FROM sometable
 WHERE id > %(_id)s
 // if use = instead of IN, it works well (of course if cameras is just one value, not array)
 AND (%(camera)s is NULL OR cam_id IN %(camera)s)  

在 python 中,我通过以下方式为查询提供参数:

values = {"_id": 10, camera: tuple(1, 2, 3)]}
curs.execute(query, values)

如果元组不为空则一切正常,否则:

如果相机=None,我得到以下错误:

psycopg2.errors.SyntaxError:“NULL”处或附近的语法错误 第 6 行:WHERE(NULL 为 NULL 或 cam_id IN NULL)

如果camera = tuple(),我得到以下错误:

psycopg2.errors.SyntaxError: ")" 处或附近的语法错误 第 6 行:WHERE (() 为 NULL 或 cam_id IN ())

为了更清楚:

如果提供了空数组,我想获得所有可能cam_ids的所有结果,所以结果应该与SELECT * FROM tablename;[=相同14=]

编辑:为 Maurice Meyer

我在尝试进行更大的查询时发现了以下问题

select * from vworker_tracks
where
    // still need some default value: cam_id != ''
    (cam_id = any('{}') or cam_id != '') 
and
    // unexpected results when both are true, provides all tracks  > 0.0
    (track_duration_seconds = 2.5 or track_duration_seconds > 0.0) 
and 
    id < 100

order by id desc limit 10;

您可以在 PostgreSQL 中使用 =ANY(),其中 ANY 将数组作为输入。而且那个数组可以为空,没问题。 Simpel 示例:

SELECT  1 = ANY('{}') -- false
    ,   2 = ANY('{2,3,4}'); -- true

您可以在 WHERE 条件中使用 OR 到 'emulate' if/else:

query = """
with cameras as (
    select
        %(ids)s::int[] as ids,
        %(idsLen)s as count /* simplify */
)
select
count(conversations.*)
from
conversations,
cameras
where
(
(cameras.count >= 1 and id = ANY(cameras.ids))
or
(cameras.count < 1 and id > %(_id)s)
)
"""

for ids in [[81, 60], []]:
    values = {"_id": 10, "ids": ids, "idsLen": len(ids)}
    curs.execute(query, values)
    print(curs.fetchone())

输出:

(2,)    # 2 ids given, returns 2 records
(118,)  # empty array, returns ALL records

答案不提供任何原生sql解决方案,它基于https://use-the-index-luke.com/sql/myth-directory/dynamic-sql-is-slow,遵循KISS原则:

from aiopg.cursor import Cursor


async def my_test_query(curs: Cursor, params: dict) -> list:
    """
    the snippet demonstrates how to create IN filters
    The idea is build on the article
    https://use-the-index-luke.com/sql/myth-directory/dynamic-sql-is-slow
    follows KISS principle
    """
    query = f"""
        SELECT 
          id,
          cam_id,
          track_duration_seconds,  
          top_color, 
          bottom_color,
          crossed_lines->>'entrance',
          crossed_lines->>'cross_direction'
        FROM vworker_tracks 
        WHERE id < %(_id)s
    """
    if params.get("camera", None) is not None:
        query += " AND cam_id IN %(camera)s"

    if params.get("bottom_color", None) is not None:
        query += " AND bottom_color IN %(bottom_color)s"

    if params.get("top_color", None) is not None:
        query += " AND top_color IN %(top_color)s"

    if params.get("cross_direction", None) is not None:
        query += " AND crossed_lines->>'cross_direction' IN %(cross_direction)s"

    if params.get("entrance", None) is not None:
        query += " AND crossed_lines->>'entrance' IN %(entrance)s"

    query += " ORDER BY id DESC LIMIT 50;"

    await curs.execute(query, params)
    res = await curs.fetchall()
    return res