Как выбрать несколько значений с помощью postgreSQL INTO нескольких переменных в зависимости от случая? - PullRequest
1 голос
/ 12 марта 2020

Мне было интересно, можно ли использовать выражение CASE внутри оператора SELECT в postgres, где я записываю несколько значений сразу.

Так, например:

DO
$$
DECLARE
    var int := 1;
    val1 int;
BEGIN
    SELECT
    CASE var
    WHEN 1 THEN
        variable1, variable2
    WHEN 2 THEN
        variable3, variable4
    INTO 
        val1, val2
    FROM mytable;
$$

Дело в том, что это хорошо работает при использовании этого:

DO
$$
DECLARE
    var int := 1;
    val1 int;
    val2 int;
BEGIN
    SELECT
    CASE var
    WHEN 1 THEN
        variable1
    WHEN 2 THEN
        variable3
    INTO 
        val1
    FROM mytable;
$$

я знаю, что могу просто переместить CASE за пределы оператора SELECT и просто написать два оператора SELECT, но это, кажется, больше, чем написать решение, которое я хотеть.

Еще одна вещь, которую я попробовал, была:

DO
$$
DECLARE
    var int := 1;
    val1 int;
BEGIN
    SELECT
    CASE var
    WHEN 1 THEN
        (variable1, variable2)
    WHEN 2 THEN
        (variable3, variable4)
    INTO 
        val1, val2
    FROM mytable;
$$

, но это, похоже, записывает (variable1, variable2) в val1 и завершается неудачно из-за неправильного типа данных.

Я думаю, с последним решением я мог бы записать значения в массив, но они мне нужны в отдельных переменных.

Любые советы приветствуются.

1 Ответ

1 голос
/ 14 марта 2020

Есть три способа, о которых я мог подумать. Два очень просты, один сложный, но относится к (variable1, variable2).

  1. CASE использованию (легко)
  2. JSONB использованию (легко)
  3. TYPE использование (сложное)

ПЕРВЫЙ ВАРИАНТ: CASE использование

Это самый простой, но не очень оптимизированный вариант.

Когда вы используете SELECT vals INTO vars, вы должны иметь одинаковое количество значений и переменных. Таким образом, в этом варианте вам понадобится CASE для каждого значения.

-- CASE QUERY
DO $$
DECLARE
-- control var
    var integer := 1;
-- result vars
    var1 integer := 1;
    var2 integer := 2;
    var3 integer := 3;
    var4 integer := 4;
-- final values
    val1 integer;
    val2 integer;
BEGIN
    SELECT
        CASE var -- CASE for val1
            WHEN 1 THEN var1
            WHEN 2 THEN var3 END,
        CASE var -- CASE for val2
            WHEN 1 THEN var2 
            WHEN 2 THEN var4 END
    INTO val1,val2;

    RAISE NOTICE '%',val1; -- outputs: 1
    RAISE NOTICE '%',val2; -- outputs: 2
END;$$

Если переменная изменится на '2', то результат будет 3 и 4.

Здесь Вы изменяете свой код

CASE var
    WHEN 1 THEN
        variable1, variable2
    WHEN 2 THEN
        variable3, variable4

на

CASE var -- CASE for val1
    WHEN 1 THEN variable1
    WHEN 2 THEN variable3 END,
CASE var -- CASE for val2
    WHEN 1 THEN variable2
    WHEN 2 THEN variable4 END

Если вы добавляете больше переменных, вы добавляете больше случаев.

ВТОРОЙ ВАРИАНТ: JSONB использование

Этот вариант является наилучшим подходом, поскольку вам не нужно кодировать многие предложения CASE и не нужно создавать дополнительные шаги для процесса.

По сути, вы используете переменную JSONB, в которой есть все необходимые переменные, например:

{
   "val1": 1,
   "val2": 2
}

И вот как вы это делаете:

-- USING JSONB
DO $$
DECLARE
-- control var
    var integer := 1;
-- result vars
    var1 integer := 1;
    var2 integer := 2;
    var3 integer := 3;
    var4 integer := 4;
-- JSON var that will have val1 and val2 data
    jsonvar jsonb;
BEGIN
    SELECT CASE var 
            WHEN 1 THEN CAST('{"val1":'||var1||',"val2":'||var2||'}' as jsonb)
            WHEN 2 THEN CAST('{"val1":'||var3||',"val2":'||var4||'}' as jsonb) END
    INTO jsonvar;

    RAISE NOTICE '%',jsonvar->>'val1'; -- outputs: 1
    RAISE NOTICE '%',jsonvar->>'val2'; -- outputs: 2
END;$$

Обратите внимание, что здесь вы вставляете JSON объект в переменную типа JSONB. Одинаковое количество переменных в vals.

Поскольку JSON действительно гибкий объект, для добавления дополнительных переменных вам просто нужно настроить JSON соответственно.

В PostgreSQL лучше всего использовать JSONB вместо простого JSON. В руководстве говорится:

Тип данных json хранит точную копию входного текста, функции обработки которого должны обрабатываться при каждом выполнении; в то время как данные jsonb хранятся в разложенном двоичном формате, что делает их немного медленнее для ввода из-за дополнительных издержек преобразования, но значительно быстрее для обработки, так как повторный анализ не требуется. jsonb также поддерживает индексирование, что может быть существенным преимуществом.

ТРЕТИЙ ВАРИАНТ: TYPE использование

Этот параметр является более сложным, поскольку мы вводим в RECORD местности. Да, (data1,data2) в PostgreSQL - это RECORD. Что такое запись? Проще говоря, ROW не имеет структуры данных.

Что означает RECORD? Ну, чтобы было понятно, когда вы создаете таблицу, например:

CREATE TABLE data(val1 integer,val2 integer);

Если вы хотите вставить данные в таблицу «данные», вы должны вставить запись , поэтому, когда вы делаете:

INSERT INTO data(val1,val2) VALUES (1,2);

Вы вставляете RECORD (1,2) в данные.

ПРИМЕЧАНИЕ , что 1 и 2 заключены в скобки (1,2). Итак, когда вы пишете (variable1,variable2), вы создаете RECORD, который содержит переменную 1 и переменную 2.

В соответствии с руководством

Переменные записи аналогичны переменным типа строки, но они не имеют предопределенной структуры ...

RECORD не является истинным типом данных, а только заполнителем.

Вот проблема. У RECORD нет структуры, поэтому PostgreSQL не знает, как ее анализировать. Когда вы используете INSERT INTO ваше говорящее PostgreSQL структуру, поэтому запись принимает структуру таблицы (предыдущий пример).

Когда вы делаете:

SELECT
    CASE var
    WHEN 1 THEN
        (variable1, variable2)
    WHEN 2 THEN
        (variable3, variable4)

Вы выбираете RECORD.

Чтобы упростить его, если вы сделаете SELECT (1,2), вы получите:

row (record)
------------
(1,2)

Как назначить структуру для ROW? ну, вы используете SELECT, FOR или TYPE. SELECT и FOR используются аналогично INSERT INTO, вы знаете структуру данных. В этом случае нет ссылки, поэтому использование TYPE обязательно.

В PostgreSQL вы можете создавать свои персональные типы данных. Следовательно, вы можете сделать:

CREATE TYPE mytype AS (val1 integer, val2 integer);-- execute only once

Подобно таблице, вы можете создать ТИП один раз, и ТИП станет доступным во всей базе данных. Чтобы удалить тип, который вы просто делаете DROP TYPE mytype;

mytype имеет два значения, необходимые для переменных, например, таблицу, TYPE может иметь любые "столбцы" с любыми типами данных.

Теперь, если вы сделаете SELECT (1,2)::mytype, вы получите:

row (record)
------------
(1,2)

Все еще ряд, потому что PostgreSQL не знает, как его проанализировать.

Но если вы сделаете SELECT * FROM (VALUES(1,2)) AS mytype(val1,val2); вы получите

 val1 | val2
------+------
    1 |    2

Это потому, что вы говорите PostgreSQL, как его анализировать (обратите внимание на использование VALUES).

Это показывает, что не так просто присвоить структуру строке записи. Но это возможно, как показано ниже:

-- USING TYPES
-- Requires mytype created
DO $$
DECLARE
-- control var
    var integer := 1;
-- result vars
    var1 integer := 1;
    var2 integer := 2;
    var3 integer := 3;
    var4 integer := 4;
-- final values
    val1 integer;
    val2 integer;
BEGIN
    SELECT x[1].val1,x[1].val2
    FROM(
        SELECT ARRAY(
            SELECT CASE var
                WHEN 1 THEN (var1,var2)::mytype 
                WHEN 2 THEN (var3,var4)::mytype END
        )::mytype[] AS x
    )dataset
    INTO val1,val2;

    RAISE NOTICE '%',val1; -- outputs: 1
    RAISE NOTICE '%',val2; -- outputs: 2
END;$$

Ключевая часть использует ARRAY.

При выполнении:

SELECT x.val1,x.val2
        FROM(
            SELECT CASE 1
                WHEN 1 THEN (1,2)::mytype
                WHEN 2 THEN (3,4)::mytype END AS X
        )dataset

Вы получаете эту ошибку:

ERROR:  missing FROM-clause entry for table "x"

PostgreSQL не знает, как его анализировать, поэтому вы говорите PostgreSQL, чтобы получить его через массив.

SELECT
x[1].val1,x[1].val2
FROM(
    SELECT ARRAY(
        SELECT CASE 1
           WHEN 1 THEN (1,2)::mytype
           WHEN 2 THEN (4,5)::mytype END
    )::mytype[] AS x
) dataset

Это выводит:

 val1 | val2
------+------
    1 |    2

Проблема с этой опцией заключается в том, что TYPE, который вы создаете, является stati c, поэтому, если вам придется изменить его, вам придется отбросить тип и создать его снова.

Но вот как ты делаешь это. Второй вариант - лучший, более современный подход.

...