Postgres ВЫБРАТЬ или ВСТАВИТЬ в CTE? - PullRequest
0 голосов
/ 26 мая 2020

Я использую Postgres 12.2.

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

В примере Я создал, события хранятся в таблице purchases, а id должен быть идентификатором компании, взятым из таблицы companies. Поскольку фиксированного набора компаний не существует, таблица компаний должна увеличиваться «по мере роста I go»;).

Итак, настройка следующая:

CREATE TABLE 
    companies 
    ( 
        company_id  SERIAL NOT NULL, 
        NAME        CHARACTER VARYING(256) NOT NULL, 
        PRIMARY KEY (company_id), 
        CONSTRAINT companies_unique_name UNIQUE (NAME) 
    )
;
CREATE TABLE 
    purchases
    ( 
        purchase_id    SERIAL NOT NULL, 
        purchase_date  DATE DEFAULT CURRENT_DATE NOT NULL, 
        amount         INTEGER NOT NULL,
        company_id     INTEGER NOT NULL, 
        PRIMARY KEY (purchase_id) 
    )
;

Итак, когда я хочу чтобы вставить событие «Купил 5 товаров у 'company1'», мне нужно получить идентификатор «company1», либо посмотрев его в таблице companies, либо создав новую запись для «company1» в companies.

Я могу сделать это так:

WITH EXISTING_COMPANY AS (
        SELECT company_id FROM companies WHERE name = 'company1'
)
   , NEW_COMPANY AS (
        INSERT INTO companies (name) VALUES ('company1')
        ON CONFLICT(name) DO NOTHING
        RETURNING company_id
)
   , GET_COMPANY_ID AS (
        SELECT COALESCE(
            (SELECT company_id FROM EXISTING_COMPANY),
            (SELECT company_id FROM NEW_COMPANY)
   ) AS company_id
)
INSERT INTO purchases(amount, company_id)
VALUES (5, (select company_id from GET_COMPANY_ID))
;

CTE EXISTING_COMPANY даст мне идентификатор существующей «компании1» или null.

CTE NEW_COMPANY даст мне идентификатор вновь созданной компании «company1» или null

CTE GET_COMPANY_ID, наконец, с помощью coalesce попытается получить существующий идентификатор и, если это не удается, новый идентификатор.

Хотя это работает, у него есть недостатки: мне нужно дважды указывать название компании и что мне нужны новые CTE для каждой компании, поскольку я не знаю, как передать название компании в мои CTE.

  • Есть ли способ передать название компании моим CTE?
  • Есть ли другие способы достижения моих цель?

Ответы [ 2 ]

1 голос
/ 26 мая 2020

Вы можете указать новое имя с предложением values() в другом CTE. Вам также действительно не нужен existing_company CTE, так как это также можно сделать внутри get_company cte:

WITH input(name) as (
  values ('company1')
), new_company AS (

  INSERT INTO companies (name) 
  select i.name 
  from input i
  ON CONFLICT (name) DO NOTHING
  RETURNING company_id

), get_company_id AS (
  select company_id
  from new_company

  union all

  select company_id
  from companies
  where name in (select name from input)
    and not exists (select * from new_company)
)
INSERT INTO purchases(amount, company_id)
select 5, company_id 
from get_company_id
;

Его также можно расширить для обработки нескольких компаний и сумм:

WITH input(amount, name) as (
  values 
     (5, 'company1'), 
     (6, 'company2')
), new_company AS (
  INSERT INTO companies (name) 
  select name 
  from input
  ON CONFLICT (name) DO NOTHING
  RETURNING company_id, name
), get_company_id AS (

  select company_id, name
  from new_company
  union all

  select c.company_id, c.name
  from companies c
  where c.name in (select i.name from input i)
  and not exists (select * 
                  from new_company nc
                  where nc.company_id = c.company_id)
)
INSERT INTO purchases(amount, company_id)
select i.amount, g.company_id 
from get_company_id g
  join input i on i.name = g.name
;
0 голосов
/ 26 мая 2020

Я изучил функции и экспериментирую с этим:

CREATE OR REPLACE FUNCTION companyID (IN company_name VARCHAR(256))
RETURNS integer AS
'
DECLARE
        cid integer;
BEGIN
        SELECT company_id INTO cid FROM companies WHERE name = company_name;
        IF cid IS NULL THEN
                INSERT INTO companies (name) VALUES(company_name)
                ON CONFLICT(name) DO NOTHING
                RETURNING company_id INTO cid;
        END IF;
        -- In case of a concurrent insert, cid could be null
        IF cid IS NULL THEN
                SELECT company_id INTO cid FROM companies WHERE name = company_name;
        END IF;
        RETURN cid;
END;
'
LANGUAGE plpgsql
;

Затем вставка работает следующим образом:

INSERT INTO purchases (amount, company_id) VALUES
(10, companyID('company2')),
(11, companyID('company1')),
(42, companyID('company2'))
;

Есть ли недостатки в этом подходе?

...