Найшвидше перевірити, чи існує рядок у PostgreSQL


177

У мене є купа рядків, які мені потрібно вставити в таблицю, але ці вставки завжди робляться партіями. Тому я хочу перевірити, чи існує один рядок із партії в таблиці, тому що я знаю, що вони всі були вставлені.

Отже, це не є первинним ключем перевірки, але не повинно мати великого значення. Я хотів би перевірити лише один рядок, так що, count(*)мабуть, це не добре, тому його щось на кшталт existsя думаю.

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

Моя партія містить рядки з такою структурою:

userid | rightid | remaining_count

Отже, якщо таблиця містить будь-які рядки з умовою, useridце означає, що всі вони там присутні.


Ви хочете побачити, чи таблиця містить БУДЬ-які рядки чи будь-які рядки з вашої партії?
JNK

будь-які рядки з моєї партії так. всі вони поділяють одне і те ж поле, погано редагують.
Валентин Кузуб

Будь ласка, уточнюйте своє запитання. Ви хочете додати партію записів, все чи нічого? Чи є щось особливе в підрахунку? (BTW застережене слово, непрактичне як назва стовпця)
wildplasser

добре, я намагався трохи спростити фактичну ситуацію, але ми все ближче і ближче до реальної реалізації. Після того, як ці рядки вставлені (є інше поле for_date), я починаю декрементувати права для вказаного користувача, оскільки він використовує конкретні права, як тільки права стають 0, вони більше не можуть виконувати ці дії на цю дату. ось реальна історія
Валентин Кузуб,

1
Просто покажіть (відповідну частину) визначення таблиць і скажіть, що ви маєте намір робити.
wildplasser

Відповіді:


345

Використовуйте ключове слово EXISTS для повернення правдиво / помилково:

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

21
Розширення на цьому, ви можете назвати повернений стовпець для легкої довідки. Напр.select exists(select 1 from contact where id=12) AS "exists"
Роуан

3
Це краще, тому що воно завжди буде повертати значення (істинне або хибне) замість іноді None (залежно від мови програмування), яке може не розширюватись так, як ви очікуєте.
isaaclw

1
У мене є Seq Scan з використанням цього методу. Я щось роблю не так?
FiftiN

2
@ Michael.MI мають таблицю БД з 30 мільйонами рядків, і коли я використовую existsабо у limit 1мене сильний спад продуктивності, оскільки Postgres використовує Seq Scan замість індексу сканування. І analyzeне допомагає.
FiftiN

2
@maciek, будь ласка, розумійте, що "id" є первинним ключем, тому "LIMIT 1" буде безглуздим, оскільки з цим ідентифікатором є лише один запис
StartupGuy

34

Як щодо просто:

select 1 from tbl where userid = 123 limit 1;

де 123є Userid партії, яку ви збираєтеся вставити.

Вищенаведений запит поверне або порожній набір, або окремий рядок, залежно від того, чи є записи із заданим користувачем.

Якщо це виявляється занадто повільним, ви можете розглянути можливість створення індексу tbl.userid.

якщо навіть один рядок із партії існує в таблиці, в такому випадку мені не потрібно вставляти рядки, тому що я точно знаю, що всі вони були вставлені.

Щоб це залишалося істинним, навіть якщо ваша програма переривається в серійній партії, я рекомендую вам переконатися, що ви керуєте транзакціями бази даних належним чином (тобто, що вся партія вставляється в рамках однієї транзакції).


11
"Вибрати кількість (*) з (вибрати 1 ... ліміт 1)" може бути іноді програмно простіше, оскільки гарантовано завжди повертати рядок зі значенням підрахунку (*) 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 ...
    )       
    ;

BTW: якщо ви хочете, щоб вся партія вийшла з ладу у разі копії, тоді (з урахуванням обмеження первинного ключа)

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

зробить саме те, що ви хочете: або це вдається, або провалюється.


Це перевірятиме кожен рядок. Він хоче зробити одну перевірку.
JNK

1
Ні, це робить одну перевірку. Підзапит має некорельований зв'язок. Він буде викуплений, як тільки знайдеться одна відповідна пара.
wildplasser

Право, ти думав, що це стосується зовнішнього запиту. +1 вам
JNK

BTW: оскільки запит знаходиться в межах транзакції, нічого не відбудеться, якщо слід було б вставити дублікат ідентифікатора, отже, підзапит можна опустити.
wildplasser

хм, я не впевнений, що розумію. Після того, як права вставлені, я починаю колонку підрахунку. (лише деякі деталі для малюнка) Якщо рядки вже існують, а підзапит пропущений, я думаю, погано отримують помилки з викинутим дублікатом унікального ключа або? (userid & правильна форма цього унікального ключа)
Валентин Кузуб

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);

1

як вказував @MikeM

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

з індексом контакту, як правило, це може скоротити витрати часу до 1 мс.

CREATE INDEX index_contact on contact(id);

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;

не працює зі мною: я отримую синтаксичну помилку біля виконання
Саймон

1
це pl / pgsql, а не SQL, отже, синтаксична помилка "PERFORM", якщо намагається запустити її як SQL
Mark K Cowan

-1

Я хотів би запропонувати ще одну думку, щоб конкретно вирішити ваше речення: "Тому я хочу перевірити, чи існує один рядок із партії в таблиці, тому що я знаю, що вони всі були вставлені ".

Ви робите все ефективніше, вставляючи в "партії", а потім роблячи існування перевіряє по одному запису? Мені це здається протилежним інтуїтивно зрозумілим. Отже, коли ви говорите " вставки завжди робляться партіями ", я вважаю, що ви маєте на увазі, що ви вставляєте кілька записів з одним оператором вставки . Вам потрібно усвідомити, що Postgres сумісний з кислотними кислотами. Якщо ви вставляєте кілька записів (пакет даних) за допомогою одного оператора вставки , не потрібно перевіряти, чи були вони вставлені чи ні. Заява або проходить, або буде невдалою. Усі записи будуть вставлені або відсутні.

З іншого боку, якщо ваш код 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. Іншими словами, вам краще, якщо ви зафіксуєте свою "пакетну" концепцію і уникнете необхідності робити перевірку рядків за рядками.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.