Nano Hash - криптовалюты, майнинг, программирование

PL/pgSQL для универсального динамического запроса

Я использую PostigreSQL10. У меня есть универсальный запрос, в котором пользователь может выбрать нестандартную комбинацию стиля категории, события, области для поиска конструкций. Имейте в виду, что стиль категории, событие, область находятся в разных таблицах.

Я хочу избежать множественных IF и JOIN. Я также хочу, чтобы планировщик запросов не кэшировал неправильный план для неправильной комбинации параметров и последующей перекомпиляции запроса каждый раз. Поэтому я должен использовать динамический SQL.

Чтобы получить динамический SQL в PostgreSQL, я должен использовать PL/pgSQL. Но, согласно его документации

для создания динамических команд внутри ваших функций PL/pgSQL, то есть команд, которые будут использовать разные таблицы или разные типы данных при каждом их выполнении. Обычные попытки PL/pgSQL кэшировать планы для команд в таких сценариях не работают. Для решения такого рода проблем предусмотрен оператор EXECUTE. Также отсутствует кэширование плана для команд, выполняемых через EXECUTE. Вместо этого команда всегда планируется при каждом выполнении инструкции. Если возвращается несколько строк, только первая будет назначена переменной INTO — здесь

и

Команды SQL, которые появляются непосредственно в функции PL/pgSQL, должны ссылаться на одни и те же таблицы и столбцы при каждом выполнении; то есть вы не можете использовать параметр в качестве имени таблицы или столбца в команде SQL. Чтобы обойти это ограничение, вы можете создавать динамические команды с помощью инструкции PL/pgSQL EXECUTE — за счет выполнения нового анализа синтаксического анализа и построения нового плана выполнения при каждом выполнении — здесь

Итак, я предполагаю, что PL/pgSQL не подходит для моего случая, так как у меня несколько таблиц.

Мой вопрос: действительно ли PL/pgSQL не подходит для моего случая или я что-то упускаю? Подвопрос: если это не подходит, как мне синтаксис динамического sql для postgreSQL, я не могу найти никакого учебника.

Спасибо


  • Несколько лет назад я сделал именно то, о чем вы спрашиваете, для гигантской (1500 строк) функции plpgsql, которую мы использовали для создания пользовательских отчетов. Мы назвали ее функцией бога, потому что она имеет около 30 таблиц, к которым она может присоединяться, и множество столбцов (все они были динамически выбраны пользователем. Кроме того, у нее были различные проверки для таблиц, которые могут быть или не быть доступными определенным классы пользователей. Писать было тяжело, добавлять новые отчеты было мучительно, но в целом это работало на удивление хорошо. По моим оценкам, это экономило мне 2-3 часа в день, поскольку мне не нужно было постоянно запускать настраиваемые отчеты. 12.01.2018
  • Запросы, сгенерированные огромной функцией, были полностью динамическими, и в запросе была некоторая отладка для отправки сгенерированного запроса в таблицу с пользователем, отметкой времени, uuid и т. д., связанными с ним. 12.01.2018
  • Еще кое-что. Поскольку при построении динамического SQL использовались команды EXECUTE и FORMAT, вероятность уязвимостей SQL-инъекций была снижена. Обратите внимание, что функция была выполнена пользователем с правами only только на чтение для всех таблиц и представлений, доступных для отчетов. База данных Postgres, которую мы использовали в то время, весила около 2 ТБ, так что на самом деле это была не игрушечная БД. 12.01.2018
  • @bma Спасибо. Итак, то, что я спрашиваю, возможно с PL/pgSQL? Что меня смущает, так это документация PL/pgSQL, в которой объявлены все эти ограничения. Кстати, можете ли вы предложить хороший учебник по PL/pgSQL и динамическому SQL? Я не могу найти. Я только что нашел некоторые, о PL/pgSQL, но они не предлагают ничего нового, они просто повторяют документацию в более простом виде. 12.01.2018
  • Абсолютно возможно. У меня нет руководств, которые можно было бы порекомендовать (я узнал об этом из документации и методом проб и ошибок на протяжении многих лет), но у меня есть несколько замечательных примеров здесь в SO и DBA-SO от Эрвин Брандштеттер и Крейг Рингер (например, это ). В приведенных ниже ответах также есть несколько примеров для работы. 12.01.2018
  • @bma Что странно, так это то, что код в pl/pgsql и код просто запроса имеют одинаковые IF и JOIN. Также к моему случаю применимы ограничения, которые я нашел в документации pl/pgsql (плохое кэширование плана запроса, сложность обработки серии IF), поскольку у меня несколько таблиц. Так почему бы в конце концов не использовать простой запрос? Посмотрите на этот действительно простой динамический SQL, насколько аккуратно он обрабатывает ЕСЛИ. И планировщик запросов кэширует план для каждого случая. Это не связано с postgre, но я ищу что-то подобное. Спасибо 12.01.2018
  • Возможно, я неправильно понимаю, но ничто не указывает на то, что plpgsql не выполнит эту работу. Использование EXECUTE приведет к перепланированию для каждого выполнения функции (это то, что вам нужно, если вы действительно создаете динамический SQL), вы можете создать свою функцию с динамическим вводом (массивы IN, HSTORE, VARIADIC и т. д.), мульти вывод строки возвращается через ВОЗВРАТНЫЙ ЗАПРОС или RETURNS SETOF и т. д. Конечно, если вы можете создать кучу простого SQL и выполнить его, он, вероятно, будет работать лучше, поскольку планировщик сможет принимать более правильные решения. 12.01.2018
  • @bma Привет еще раз. Благодаря вам и другим участникам этой темы мне удалось создать несколько динамических запросов в pl/pgsql. Планирование запросов все еще смущает меня - отметьте здесь, если хотите - но в любом случае, спасибо после всего. Думаю, я все-таки буду использовать EXECUTE из-за постоянного планирования запросов 16.01.2018
  • @bma О, кроме того, вы увидите там мой динамический запрос и то, как легко я могу добавлять IF .. 16.01.2018

Ответы:


1

Не могли бы вы опубликовать некоторые определения таблиц и пример запроса того, что вы пытаетесь сделать? Я не уверен на 100%, что вам нужно, но есть пара форм «динамического» SQL с использованием хранимых процедур/функций:

  1. Создайте функцию, которая принимает входные параметры (например, categoryType, styleId, eventName, areaId) и вставляйте эти значения в «статический» SQL-запрос. Вот пример фрагмента запроса для вашего случая:
SELECT *
FROM category cat
INNER JOIN style st ON cat.styleid = style.id
WHERE (cat.categoryType = pCategoryType OR pCategoryType IS NULL)
AND (st.id = pStyleId OR pStyleId IS NULL)

Вот реальный пример:

CREATE OR REPLACE FUNCTION SP_IGLGetItem(
    pItemId INTEGER
) 
RETURNS TABLE(
    ItemId INTEGER,
    ItemName VARCHAR(100),
    ItemCategory CHAR(2) 
AS
$$
BEGIN
    RETURN QUERY
    SELECT i.ItemId, i.ItemName, i.ItemCategory
    FROM Item i
    WHERE (i.ItemId = pItemId OR pItemId IS NULL) -- Return single item (if specified, otherwise return all)
    ;
END;
$$
LANGUAGE 'plpgsql';
  1. Создайте строку, содержащую SQL, который вы хотите выполнять динамически на основе различных условий, значений параметров и т. д. Это настолько динамично, насколько это возможно.

  2. Условно запускайте различные «статические» операторы SQL на основе значений ваших входных параметров.

Что-то из этого соответствует вашей ситуации?

PL/PGSQL — это просто язык, используемый для написания хранимых процедур/функций в Postgres. Если вам действительно нужна динамическая генерация SQL, то лучше всего написать функцию с использованием PL/PGSQL.

Другой вариант — динамически генерировать SQL, который вы хотите, в своем клиентском приложении, а затем просто отправлять этот SQL непосредственно для выполнения.

11.01.2018
  • Привет. Спасибо за код. 1 и 2 соответствуют моему случаю. Вот проблема, которую я пытаюсь решить. В основном много JOIN и IF, в зависимости от того, что выбирают пользователи. 12.01.2018
  • Я спрашивал других, и я спрошу ваше мнение. Код в pl/pgsql и код простого запроса имеют одинаковые IF и JOIN. Ограничения, которые я нашел в документации по pl/pgsql (плохое кэширование планов запросов, сложность обработки серии IF), применимы и к моему случаю, поскольку у меня есть несколько таблиц. Так почему бы в конце концов не использовать простой запрос? Посмотрите на этот действительно простой динамический SQL, насколько аккуратно он обрабатывает ЕСЛИ. И планировщик запросов кэширует план для каждого случая. Это не связано с postgre, но я ищу что-то подобное. Спасибо 12.01.2018
  • Это сводится к двум вещам: обслуживание и производительность. Я бы попробовал все возможные способы и посмотрел, как производительность упадет для вас. Если разница незначительна и вы не ожидаете, что характеристики данных (# строк, # уникальных значений и т. д.) изменятся слишком сильно, выберите тот, который для вас проще. Но обычно производительность превыше всего, поэтому, если это означает написание уродливого и кошмарного в обслуживании кода, иногда это то, что вам нужно сделать. 12.01.2018
  • Привет еще раз. Благодаря вам и другим участникам этой темы мне удалось создать несколько динамических запросов в pl/pgsql. Планирование запросов все еще смущает меня - отметьте здесь, если хотите - но в любом случае, спасибо после всего. Думаю, я все-таки буду использовать EXECUTE из-за постоянного планирования запросов. О, также вы увидите там мой динамический запрос и то, как легко я могу добавлять ЕСЛИ. 16.01.2018

  • 2

    Вы можете выполнять большинство запросов внутри pl/pgsql execute.

    Например, этот выбор объединения двух таблиц будет работать нормально:

    drop table if exists dyn_tab1;
    create table dyn_tab1 (id int primary key, value text);
    insert into dyn_tab1 values (1, 'test1'), (2, 'test2');
    
    drop table if exists dyn_tab2;
    create table dyn_tab2
      (id serial primary key, fk_id int references dyn_tab1(id), value text);
    insert into dyn_tab2 (fk_id, value)
    values (1, 'blahblah'), (1, 'blahblah3'), (1, 'foobar'), (2, 'asdf');
    
    select *
    from dyn_tab1 as t1
    join dyn_tab2 as t2 on t2.fk_id = t1.id;
    
    --I'm mixing here both format and USING (prepared statement).
    --You can use format for everything tho. Or just concat strings.
    do $$
    declare
      l_row record;
    begin
    for l_row in 
    execute format($query$ select *
                           from %I as t1
                           join %I as t2 on t2.fk_id = t1.id
                           where t1.id = $1;
                   $query$, 'dyn_tab1', 'dyn_tab2')
    using 2
    loop
    raise notice 'record: %', l_row;
    end loop;
    end;
    $$;
    
    create function dyn_test()
    returns setof record
    as $$
    begin
    return query execute format($query$ select *
                           from %I as t1
                           join %I as t2 on t2.fk_id = t1.id
                           where t1.id = $1;
                   $query$, 'dyn_tab1', 'dyn_tab2')
    using 2;
    end;
    $$ language plpgsql;
    
    select * from dyn_test() as (id int, value text, id2 int, fk int, value2 text);
    
    12.01.2018
  • Действительно полезный код, спасибо. Хотя вот это большой вопрос. Упомянутые ограничения верны, потому что я нашел их в документации. Итак, они применимы к моему случаю, поскольку я использую несколько таблиц. Итак, зачем на самом деле использовать pl/pgsql, а не просто запрос, если я буду использовать пару IF и JOIN, а ограничения одинаковы? 12.01.2018
  • Перефразируя: ограничения, которые я нашел в документации, также применимы к моему случаю, поскольку я использую несколько таблиц. Итак, зачем на самом деле использовать pl/pgsql, а не просто запрос пары IF и JOIN, если ограничения одинаковы (плохое планирование запросов, одинаковое количество IF)? Посмотрите на этот действительно простой динамический SQL, насколько аккуратно он обрабатывает ЕСЛИ. И планировщик запросов кэширует план для каждого случая. Это не связано с postgre, но я ищу что-то подобное. Спасибо 12.01.2018
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..