Самая быстрая проверка, если строка существует в PostgreSQL

177

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

Так что это не проверка первичного ключа, но это не должно иметь большого значения. Я хотел бы проверить только одну строку, так что, count(*)вероятно, это не хорошо, так что это что-то вроде, existsя думаю.

Но так как я довольно новичок в PostgreSQL, я бы лучше спросил людей, которые знают.

Моя партия содержит строки со следующей структурой:

userid | rightid | remaining_count

Таким образом, если таблица содержит какие-либо строки при условии, useridэто означает, что они все присутствуют там.

Валентин Кузуб
источник
Вы хотите увидеть, есть ли в таблице какие-либо строки или какие-либо строки из вашего пакета?
JNK
любые строки из моей партии да. все они имеют одно и то же поле и немного редактируют.
Валентин Кузуб
Пожалуйста, уточните свой вопрос. Вы хотите добавить пакет записей, все или ничего? В графе есть что-то особенное? (Кстати, зарезервированное слово, непрактичное в качестве названия столбца)
wildplasser
хорошо, я пытался немного упростить реальную ситуацию, но мы все ближе и ближе к реальной реализации. Как только эти строки вставлены (есть другое поле for_date), я начинаю уменьшать права для указанного пользователя, поскольку они используют определенные права, как только права становятся равными 0, они больше не могут выполнять эти действия для этой даты. вот настоящая история
Валентин Кузуб,
1
Просто покажите (соответствующую часть) определения таблиц и расскажите, что вы собираетесь делать.
wildplasser

Ответы:

345

Используйте ключевое слово EXISTS для возврата ИСТИНА / ЛОЖЬ:

select exists(select 1 from contact where id=12)
StartupGuy
источник
21
Расширение по этому, вы можете назвать возвращаемый столбец для удобства ссылки. Напримерselect exists(select 1 from contact where id=12) AS "exists"
Rowan
3
Это лучше, потому что он всегда будет возвращать значение (true или false) вместо иногда None (в зависимости от вашего языка программирования), которое может не расширяться так, как вы ожидаете.
Исаакль
1
У меня есть Seq Scan с использованием этого метода. Я что то не так делаю?
Пятница,
2
@ Michael.MI имеет таблицу БД с 30 миллионами строк, и когда я использую existsили limit 1у меня сильное падение производительности, потому что Postgres использует Seq Scan вместо Index Scan. И analyzeне помогает.
Пятница,
2
@maciek, пожалуйста, поймите, что «id» является первичным ключом, поэтому «LIMIT 1» будет бессмысленным, поскольку существует только одна запись с этим идентификатором
StartupGuy
34

Как насчет просто:

select 1 from tbl where userid = 123 limit 1;

где 123идентификатор пользователя пакета, который вы собираетесь вставить.

Приведенный выше запрос вернет либо пустой набор, либо одну строку, в зависимости от того, существуют ли записи с данным идентификатором пользователя.

Если это окажется слишком медленным, вы можете посмотреть на создание индекса tbl.userid.

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

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

NPE
источник
11
Иногда может быть гораздо проще "выбрать count (*) из (выберите 1 ... limit 1)", так как гарантированно всегда будет возвращать строку со значением count (*), равным 0 или 1.
Дэвид Олдридж,
@DavidAldridge count (*) по-прежнему означает, что все строки должны быть прочитаны, тогда как предел 1 останавливается на первой записи и возвращается
Imraan
3
@Imraan Думаю, вы неверно истолковали запрос. COUNTДействует на вложенной , SELECTчто имеет не более 1 строку (потому что LIMITэто в подзапросе).
jpmc26
9
INSERT INTO target( userid, rightid, count )
  SELECT userid, rightid, count 
  FROM batch
  WHERE NOT EXISTS (
    SELECT * FROM target t2, batch b2
    WHERE t2.userid = b2.userid
    -- ... other keyfields ...
    )       
    ;

Кстати: если вы хотите, чтобы весь пакет потерпел неудачу в случае дублирования, тогда (с учетом ограничения первичного ключа)

INSERT INTO target( userid, rightid, count )
SELECT userid, rightid, count 
FROM batch
    ;

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

wildplasser
источник
Это проверит каждый ряд. Он хочет сделать одну проверку.
JNK
1
Нет, это делает одну проверку. Подзапрос некоррелирован. Он выручит, как только одна подходящая пара будет найдена.
wildplasser
Да, я думал, что это относится к внешнему запросу. +1 тебе
JNK
Кстати: поскольку запрос находится внутри транзакции, ничего не произойдет, если будет вставлен дублирующий идентификатор, поэтому подзапрос можно опустить.
wildplasser
хм, я не уверен, что понимаю. После того, как права вставлены, я начинаю уменьшать счетчик столбца. (только некоторые детали для изображения) Если строки уже существуют и подзапрос пропущен, я думаю, что получит ошибки с дублированием уникального ключа или? (идентификатор пользователя и правильная форма этого уникального ключа)
Валентин Кузуб,
1
select true from tablename where condition limit 1;

Я считаю, что это запрос, который Postgres использует для проверки внешних ключей.

В вашем случае вы могли бы сделать это также за один раз:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);
Royce
источник
1

как указал @MikeM.

select exists(select 1 from contact where id=12)

с индексом на контакте, это обычно может сократить затраты времени до 1 мс.

CREATE INDEX index_contact on contact(id);
hcnak
источник
0
SELECT 1 FROM user_right where userid = ? LIMIT 1

Если ваш набор результатов содержит строку, то вам не нужно вставлять. В противном случае вставьте свои записи.

Фабиан Барни
источник
если группа содержит 100 строк, она вернет мне 100 строк, как вы думаете, это хорошо?
Валентин Кузуб
Вы можете ограничить его до 1 строки. Должен работать лучше. Посмотрите на отредактированный ответ от @aix для этого.
Фабиан Барни
0

Если вы думаете о производительности, возможно, вы можете использовать «PERFORM» в такой функции:

 PERFORM 1 FROM skytf.test_2 WHERE id=i LIMIT 1;
  IF FOUND THEN
      RAISE NOTICE ' found record id=%', i;  
  ELSE
      RAISE NOTICE ' not found record id=%', i;  
 END IF;
франки
источник
не работает со мной: я получаю синтаксическую ошибку рядом с перформансом
Simon
1
это pl / pgsql, а не SQL, следовательно, синтаксическая ошибка для «PERFORM» при попытке запустить его как SQL
Марк К Коуэн,
-1

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

Вы делаете вещи эффективными, вставляя «партии», но затем проводите проверки существования по одной записи за раз? Это кажется мне нелогичным. Поэтому, когда вы говорите « вставки всегда выполняются партиями », я понимаю, что вы имеете в виду, что вы вставляете несколько записей одним оператором вставки . Вы должны понимать, что Postgres совместим с ACID. Если вы вставляете несколько записей (пакет данных) с одним оператором вставки , нет необходимости проверять, были ли некоторые вставлены или нет. Утверждение либо проходит, либо провалится. Все записи будут вставлены или нет.

С другой стороны, если ваш код на C # просто «устанавливает» отдельные операторы вставки, например, в цикле, и, по вашему мнению, это «пакет» ... тогда вам не следует описывать это как " вкладыши всегда делаются партиями ». Тот факт, что вы ожидаете, что часть того, что вы называете «партией», может быть фактически не вставлен, и, следовательно, чувствуете необходимость проверки, настоятельно подтверждает, что это именно тот случай, и в этом случае у вас есть более фундаментальная проблема. Вам нужно изменить свою парадигму, чтобы фактически вставить несколько записей одной вставкой, и отказаться от проверки, сделали ли отдельные записи это.

Рассмотрим этот пример:

CREATE TABLE temp_test (
    id SERIAL PRIMARY KEY,
    sometext TEXT,
    userid INT,
    somethingtomakeitfail INT unique
)
-- insert a batch of 3 rows
;;
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 1, 1),
('bar', 2, 2),
('baz', 3, 3)
;;
-- inspect the data of what we inserted
SELECT * FROM temp_test
;;
-- this entire statement will fail .. no need to check which one made it
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 2, 4),
('bar', 2, 5),
('baz', 3, 3)  -- <<--(deliberately simulate a failure)
;;
-- check it ... everything is the same from the last successful insert ..
-- no need to check which records from the 2nd insert may have made it in
SELECT * FROM temp_test

На самом деле это парадигма для любой ACID-совместимой БД ... не только для Postgresql. Другими словами, вам лучше, если вы исправите свою «пакетную» концепцию и избежите необходимости выполнять какие-либо построчные проверки в первую очередь.

StartupGuy
источник