LINUX.ORG.RU

Как добавить данные в БД только если БД пустая?

 ,


0

1

База данных PostgreSQL 9.6.

Имеется две SQL-команды.

Первая создает таблицу в БД только если такой таблицы еще нет (if not exists):

create table roles if not exists (
 id serial primary key,
 name varchar(20)
); 


Вторая должна добавлять данные в эту таблицу только, если таблица пустая. Вот заготовка для такой команды:
insert into roles (name) values
 ('user'),
 ('security'),
 ('admin');

Вопрос: как можно доработать эту команду, чтобы добавление произошло только если в таблице ничего нет? Видимо, можно получить COUNT() на SELECT, но как сравнить значение COUNT() с нулем, и если ноль - то выполнить команду? Возможно ли такое сделать только лишь синтаксисом SQL?

★★★★★

Ответ на: комментарий от vvn_black

INSERT ON CONFLICT DO NOTHING

Я думал про это, но непонятно по какому полю конфликт отслеживать. В примерах обычно написано по уникальному ID, но у меня есть таблицы, в которых даже ID нет.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

Структура не моя.

Тогда, принудительный TRUNCATE roles; перед вставкой. Или вообще сначала дропнуть таблицу, потом уже создавать ))

vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Как-то так

CREATE TABLE roles (
    id INT PRIMARY KEY,
    name TEXT UNIQUE
);
\d+ roles
                                          Table "public.roles"
 Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
 id     | integer |           | not null |         | plain    |             |              | 
 name   | text    |           |          |         | extended |             |              | 
Indexes:
    "roles_pkey" PRIMARY KEY, btree (id)
    "roles_name_key" UNIQUE CONSTRAINT, btree (name)
vvn_black ★★★★★
()
Ответ на: комментарий от vvn_black

Нет, ты мне что-то не то насоватовал.

Задача была - сделать INSERT если база данных пуста. Вместо этого ты говоришь проверять записи на уникальность. База же может быть отредактирована, вместо security можно прописать superuser. И получается что при повторном срабатывании скрипта в базе окажется и security и superuser. А нам этого не нужно.

Xintrea ★★★★★
() автор топика

Категорически советую не пихать такую логику в БД. Пиши на нормальном языке транзакцию с локом таблицы. Если таблица пустая — вставить, закоммитить. Это же можно написать и как sql процедуру, хотя, если очень надо.

filosofia
()
Ответ на: комментарий от Xintrea

Я не понимаю этого синтаксиса

INSERT INTO roles (id, name)
    SELECT * FROM (
        VALUES (1, 'admin'), (2, 'user')    
    ) AS t (id, name)
    WHERE NOT EXISTS (SELECT * FROM roles);
vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 1)
Ответ на: комментарий от vvn_black

INSERT INTO roles (id, name)
SELECT * FROM (
VALUES (1, 'admin'), (2, 'user')
) AS t (id, name)
WHERE NOT EXISTS (SELECT * FROM roles);

Но здесь же нет типов полей.

В общем, я решил задачу так:

create temp table temp_roles (
 id serial primary key,
 name varchar(20)
); 

insert into temp_roles (name) values
 ('user'),
 ('security'),
 ('admin');

create table if not exists roles as table temp_roles;

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

сделать INSERT если база данных пуста. Вместо этого ты говоришь проверять записи на уникальность

Всё правильно у тебя типичная x/y проблема. Тебе не надо чтобы таблица была пуста, тебе надо чтобы у тебя не было дублей ролей и вставка была идемпотентной (повторяешь сколько угодно раз, результат одинаковый). Логично, что для этого нужен уникальный ключ.

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

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Всё правильно у тебя типичная x/y проблема. Тебе не надо чтобы таблица была пуста, тебе надо чтобы у тебя не было дублей ролей и вставка была идемпотентной (повторяешь сколько угодно раз, результат одинаковый). Логично, что для этого нужен уникальный ключ.

Я уже написал: Как добавить данные в БД только если БД пустая? (комментарий).

То есть, нужно чтобы в таблицах были либо заранее заданные данные, либо какие-то другие. Смешивания данных не нужно.

Xintrea ★★★★★
() автор топика

А вообще я бы сделал на plpgsql и не заморачивал бы себе голову.

create table if not exists roles (id serial primary key, name varchar(20)); 

do $$begin
  if not exists (select from roles) then
    insert into roles (name) values ('user'), ('security'), ('admin');
  end if;
end$$;

как-то так.

vbr ★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vvn_black

Тогда, принудительный TRUNCATE roles; перед вставкой. Или вообще сначала дропнуть таблицу, потом уже создавать ))

// Проверка наличия базы данных:
if (mysql_query('DROP DATABASE `xxx`;')) echo 'База существовала';

(c) допотопный боян

pr849
()
Последнее исправление: pr849 (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Похоже, что для того, чтобы индексы и всякие ограничения не терялись, надо вместо:

create table if not exists roles as table temp_roles;

Сделать так:
CREATE TABLE IF NOT EXISTS roles (LIKE temp_roles INCLUDING ALL);

Народ еще говорит, то таким образом индексы будут «шариться» между таблицами. Но ежели копирование идет с временной таблицы, то по идее индекс расшарен не будет?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

индексы будут «шариться» между таблицами

Не, postgres так не умеет :-)

sequence так будет шарится (serial который), да, и после дисконнекта ты потеряешь default у id, но не индекс.

А чем тебе вариант vvn_black не нравится? Он же тебе написал всё:

create table if not exists roles (
 id serial primary key,
 name varchar(20)
);


insert into roles (name) select * from (values
 ('user'),
 ('security'),
 ('admin')
) _
where not exists (select * from roles);
Eshkin_kot ★★
()
Ответ на: комментарий от Eshkin_kot

А чем тебе вариант vvn_black не нравится? Он же тебе написал всё

Я не до конца понимаю эту конструкцию, а точнее where not exists (select * from roles);.

Она удовлетворяет вот этому: Как добавить данные в БД только если БД пустая? (комментарий) ?

Смешивания данных не будет?

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Xintrea

where not exists (select * from roles)

not exists — это означает «нет ни одной строчки в результате выполнения подзапроса select * from roles»

Смешивания данных не будет?

нет не будет, можно в explain (analyze) посмотреть как он работает:

explain (analyze,verbose) insert into roles (name) select * from (values
 ('user'),
 ('security'),
 ('admin')
) _
where not exists (select * from roles);
   InitPlan 1 (returns $0)
     ->  Seq Scan on roles roles_1  (cost=0.00..19.00 rows=900 width=0) (actual time=0.009..0.009 rows=0 loops=1)

он получил вывод select * from roles и сохранил в $0

         One-Time Filter: (NOT $0)
         ->  Values Scan on "*VALUES*"  (cost=0.00..0.04 rows=3 width=32) (actual time=0.003..0.005 rows=3 loops=1)

дальше посмотрел что $0 пустой (NOT $0 = true) и вернул все 3 строки из VALUES (rows=3) в INSERT

а это когда уже есть данные:

   InitPlan 1 (returns $0)
     ->  Seq Scan on roles roles_1  (cost=0.00..19.00 rows=900 width=0) (actual time=0.010..0.010 rows=1 loops=1)

получил одну строчку, понял что exists уже выполняется и дальше не смотрел

         One-Time Filter: (NOT $0)
         ->  Values Scan on "*VALUES*"  (cost=0.00..0.04 rows=3 width=32) (never executed)

never executed — не стал выполнять VALUES и соответствено INSERT

Eshkin_kot ★★
()
Последнее исправление: Eshkin_kot (всего исправлений: 1)
Ответ на: комментарий от Xintrea

Похоже, что для того, чтобы индексы и всякие ограничения не терялись, надо вместо:
create table if not exists roles as table temp_roles;
Сделать так:
CREATE TABLE IF NOT EXISTS roles (LIKE temp_roles INCLUDING ALL);

Нет, так тоже нельзя.

Первый вариант скопирует структуру и данные, но превратит индексные поля в обычные.

Второй вариант скопирует структуру и индексы, но не скопирует данные.

Xintrea ★★★★★
() автор топика
Ответ на: комментарий от Eshkin_kot

В общем, в итоге вот так получилось правильно:

-- Создается таблица если она еще не существует
CREATE TABLE IF NOT EXISTS roles (
    id SERIAL PRIMARY KEY,   
    name VARCHAR(20)               
);

-- Таблица наполняется только если она только что была создна (и поэтому пустая)
-- Конструкция WHERE NOT EXISTS (SELECT * FROM roles) разрешит выполнение только 
-- если в таблице roles нет ни одной записи
-- Наполняемые данные VALUES для вставки в FROM обязательно должы
-- иметь алиас, и он задается через AS
INSERT INTO roles (name) SELECT * FROM ( VALUES
    ('user'),
    ('security'),
    ('admin') ) AS temp_roles
    WHERE NOT EXISTS (SELECT * FROM roles);

Если алиас существует только в пределах одной команды, то, видимо, можно не придумывать уникальное имя, а писать AS t. Но пока с этим не разобрался.

Xintrea ★★★★★
() автор топика