Python - Создание предложения SQL WHERE / IN из списка строк - PullRequest
0 голосов
/ 08 мая 2020

Мне дается Python Список произвольной длины, содержащий произвольные строки. В частности, он может содержать строки со встроенными одинарными и / или двойными кавычками. У меня нет контроля над вводом, поэтому я должен взять то, что мне дано.

Например:

    valueList = [ "hello'world", 'foo"bar', 'my\'name"is', "see\'you\"soon" ]

    Python shell:
        >>> valueList = [ "hello'world", 'foo"bar', 'my\'name"is', "see\'you\"soon" ]
        >>>
        >>> valueList
        ["hello'world", 'foo"bar', 'my\'name"is', 'see\'you"soon']
        >>>
        >>> valueList[0]
        "hello'world"
        >>>
        >>> valueList[1]
        'foo"bar'
        >>>
        >>> valueList[2]
        'my\'name"is'
        >>>
        >>> valueList[3]
        'see\'you"soon'

Отсюда мне нужно сгенерировать строку SQL, например:

    "SELECT * FROM myTable as mt
        WHERE mt."colName" IN ("hello'world", 'foo"bar', 'my\'name"is', 'see\'you"soon')

Любое решение должно работать как с SQLite, так и с Postgres.

Я попытался сгенерировать (...) часть предложения, используя соединение Python, но это просто заканчивается тем, что создается одна большая строка со всеми одинарными кавычками. Например:

    Python shell:
        >>> values = "','".join(valueList)
        >>> values
        'hello\'world\',\'foo"bar\',\'my\'name"is\',\'see\'you"soon'

        >>> values = "'" + "','".join(valueList) + "'"
        >>> values
        '\'hello\'world\',\'foo"bar\',\'my\'name"is\',\'see\'you"soon\''

Дополнительная информация: унаследованный мной код использует SQLAlchemy и Pandas.

        import pandas as pd
        ...cut...cut...cut...
        my_df = pd.read_sql(sql, my_conn);

Я НЕ хочу использовать Pandas для фильтрации. Фактически, моя назначенная задача - УДАЛИТЬ существующую фильтрацию Pandas и заменить ее SQL с явными фильтрами WHERE / IN для скорости.

Например, замените это:

    my_df = pd.read_sql("SELECT * FROM myTable", my_conn) <==== can return 10's of thousands of rows
    my_df = my_df[my_df.loc[:, 'colName'].isin(myList)] <==== ends up with a handful of rows

с этим:

    my_df = pd.read_sql("SELECT * FROM myTable as mt WHERE mt."colName" IN ("hello'world", 'foo"bar', ...)", my_conn)

SQL защита от инъекций является плюсом, но на данный момент я буду доволен любым решением, которое работает.

Ответы [ 2 ]

0 голосов
/ 09 июня 2020

Вот фрагменты кода из работающего решения моей проблемы.

Эта функция очень специфична c для моей проблемы, но демонстрирует технику внедрения параметров. Он также демонстрирует, как обрабатывать инъекцию параметров SQLite по сравнению с инъекцией параметров Postgres.

def whereInjection(valueList, sqlDict):
    # sqlDict starts with just a "paramCount" key set to an initial value (typically 0 but could be any number).
    # As this function generates parameter injection strings, it generates a key/value pair for each parameter
    # in the form {"p_#": value} where # in the current "paramCount" and value is the value of the associated parameter.
    #
    # The end result for a valueList containing ["aaa", "bbb", "ccc'ddd", 'eee"fff'] will be:
    #   injStr = "(:p_0, :p_1, :p_2, :p_3)"
    #       Note: For Postgres, it has to be "(%(p_0)s, %(p_1)s, etc.)"
    #   sqlDict = {
    #       "paramCount": 3,
    #       "p_0": "aaa",
    #       "p_1": "bbb",
    #       "p_2": "ccc'ddd",
    #       "p_3": 'eee"fff'
    #   }
    localDebugPrintingEnabled = False

    # take into account whether the item values are presented as a list, tuple, set, single int, single string, etc.
    if isinstance(valueList, list):
        vList = valueList
    elif isinstance(valueList, tuple):
        vList = list(valueList)
    elif isinstance(valueList, set):
        vList = list(valueList);
    elif isinstance(valueList, int) or isinstance(valueList, str):
        vList = [valueList]
    else:
        vList = valueList # unexpected type...

    sz = len(vList)
    pc = sqlDict["paramCount"]
    if (db_type == 'SQLite'):
        injectStr = "(" + ",".join((":p_" + str(i + pc)) for i in range(0, sz)) + ")"
    else: # assume Postgres
        injectStr = "(" + ",".join(("%(p_" + str(i + pc) + ")s") for i in range(0, sz)) + ")"
    valueDict = {('p_' + str(i + pc)): vList[i] for i in range(0, sz)}

    sqlDict.update(valueDict) # add the valueDict just generated
    sqlDict["paramCount"] += sz # update paramCount for all parameters just added

    return injectStr

Код вызова будет выглядеть следующим образом. Это предполагает, что вы знаете, как создать соединение движка с вашей БД.

sqlDict = {"paramCount": 0} # start with empty dictionary and starting count of 0
sql = """SELECT * FROM myTable as mt WHERE mt."aColName" IN {0}""".format(whereInjection(itemList, sqlDict));
my_df = pd.read_sql(sql, engine_connection, params=sqlDict); # does the actual parameter injection
0 голосов
/ 09 мая 2020

Ну, на основе спецификации SQL, в которой строковый литерал определяется как разделенный одинарными кавычками, и для включения одинарной кавычки в строковый литерал необходимо его удвоить (вы можете обратиться к спецификации синтаксиса Sqlite и PostgreSQL, чтобы убедиться, что они соответствуют этой спецификации) вот моя попытка:

value_list = [ "hello'world", 'foo"bar', """my'name"is""", """see'you"soon""" ]
value_list_escaped = [f"""'{x.replace("'", "''")}'""" for x in value_list]
query_template = "SELECT * FROM myTable as mt WHERE mt.colName IN ({})"
query = query_template.format(", ".join(value_list_escaped))
print(query)

Это то, что вы хотели?

...