SQL: Як правильно перевірити наявність запису


207

Читаючи деяку документацію, пов’язану з настройкою SQL, я виявив таке:

SELECT COUNT(*) :

  • Підраховує кількість рядків.
  • Часто неправильно використовується для перевірки наявності запису.

Невже SELECT COUNT(*)так погано?

Який правильний спосіб перевірити наявність запису?

Відповіді:


252

Краще скористатися одним із наведених нижче:

-- Method 1.
SELECT 1
FROM table_name
WHERE unique_key = value;

-- Method 2.
SELECT COUNT(1)
FROM table_name
WHERE unique_key = value;

Перша альтернатива не повинна давати вам жодного результату чи одного результату, друга - дорівнює нулю чи одиниці.

Скільки років використовується документація, яку ви використовуєте? Хоча ви прочитали хороші поради, більшість оптимізаторів запитів в останніх RDBMS оптимізують SELECT COUNT(*)все одно, тому, хоча існує різниця в теорії (і в старих базах даних), ви не повинні помічати різниць на практиці.


1
Я уточню, що я задумав "унікальний ключ" із пунктом "ключ = значення", окрім того, що я все ще стояв за своєю відповіддю.
Мартін

1
ГАРАЗД. З цією умовою запит повертає лише один чи нульовий запис. АЛЕ: Питання не обмежується унікальним стовпцем. Також: 2-й кількість запитів (1) еквівалентний кількості (*) від практичної POV.
Мартін Ба

1
Питання говорить "який правильний спосіб перевірити наявність запису A". Я трактував це як однину, як у: 1 записі. Різниця між кількістю (*) та кількістю (1) вже покрита моєю відповіддю. Я вважаю за краще рахувати (1), оскільки він не покладається на конкретну реалізацію RDBMS.
Мартін

192

Я вважаю за краще взагалі не використовувати функцію Count:

IF [NOT] EXISTS ( SELECT 1 FROM MyTable WHERE ... )
     <do smth>

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

IF NOT EXISTS ( SELECT 1 FROM Users WHERE FirstName = 'John' AND LastName = 'Smith' )
BEGIN
    INSERT INTO Users (FirstName, LastName) VALUES ('John', 'Smith')
END

Зазвичай ми використовуємо це (підтверджуємо), коли хочемо щось зробити, тоді ваша відповідь є більш повною.
Abner Escócio


20

Ви можете використовувати:

SELECT 1 FROM MyTable WHERE <MyCondition>

Якщо немає запису, що відповідає умові, отриманий набір записів порожній.


Ви мали на увазі ТОП 1? -> (ВИБІР ТОП 1 Із MyTable WHERE <MyCondition>)
Яків

6
Ні, я мав на увазі саме "1"
Cătălin Pitiș

1
щоб увімкнути оптимізатор запитів рівним рівнем, що ви не будете читати / не потребуєте решти наборів даних, вам слід зазначити ВИБІР ТОП 1 1 З ... ДЕ ... (або використовувати відповідні підказки для запитів для RDBS)
eFloh

3
Сам оператор Exists намагається отримати лише абсолютний мінімум інформації, тому додавання TOP 1 не робить нічого, крім додавання 5 символів до розміру запиту. - sqlservercentral.com/blogs/sqlinthewild/2011/04/05/…
AquaAlex

13

Інші відповіді досить хороші, але також було б корисно додати LIMIT 1(або еквівалент , щоб не допустити перевірки зайвих рядків).


3
Якщо будь-який запит "перевірка наявності" повертає більше ніж один рядок, я вважаю, що корисніше двічі перевірити ваш пункт WHERE, а не обмежувати кількість результатів.
Мартін Шапендонк

2
Я думаю, що Limit використовується в Oracle, а не в SQL Server
Shantanu Gupta

7
Я розглядаю випадок, коли вони законно можуть бути декількома рядками - де питання: "Чи є (один чи більше) рядків, які відповідають цій умові?" У такому випадку ви не хочете дивитись на всіх, а лише на один.
JesseW

1
@Shantanu - Я знаю, саме тому я пов'язав із (дуже наскрізною) статтею en.wikipedia, що пояснює інші форми.
JesseW

11
SELECT COUNT(1) FROM MyTable WHERE ...

пройде через усі записи. Це причина, яку погано використовувати для існування записів.

Я б користувався

SELECT TOP 1 * FROM MyTable WHERE ...

Знайшовши 1 запис, він припинить цикл.


У випадку, якщо SELECT TOP 1він фактично припиняється після того, як його знайдуть, чи він продовжує знаходити все, щоб можна було сказати, який з них ТОП?
Ейрік Н

3
PS: Щоб бути впевненим, я завждиIF EXISTS (SELECT TOP 1 1 FROM ... WHERE ..)
Ерік Н

Оператор Star змусить СУБД отримати доступ до кластерного індексу, а не лише до індексів, які будуть потрібні для вашої умови приєднання. тому краще використовувати постійну валу в результаті, тобто вибрати верхню 1 1 .... Це поверне 1 або DB-Null, залежно від того, умова відповідає чи ні.
eFloh

Це гарно. Мені подобається перший.
isxaker

10

Ви можете використовувати:

SELECT COUNT(1) FROM MyTable WHERE ... 

або

WHERE [NOT] EXISTS 
( SELECT 1 FROM MyTable WHERE ... )

Це буде ефективніше, ніж SELECT * оскільки ви просто вибираєте значення 1 для кожного рядка, а не всі поля.

Існує також незначна різниця між COUNT (*) та COUNT (назва стовпця):

  • COUNT(*) буде рахувати всі рядки, включаючи нулі
  • COUNT(column name)буде враховувати лише ненульові випадки назви стовпців

2
Ви робите помилкове припущення, що СУБД якось перевірить усі ці стовпці. Різниця в продуктивності між count(1)і count(*)буде різною лише у найбільш загиблих в мозку СУБД.
paxdiablo

2
Ні, я кажу, що ви фактично покладаєтесь на деталі реалізації, коли заявляєте, що це буде ефективніше. Якщо ви дійсно хочете переконатися, що ви отримаєте найкращу ефективність, вам слід ознайомитись із конкретною реалізацією за допомогою представницьких даних або просто забути про них. Все інше потенційно вводить в оману і може кардинально змінитися при переході (наприклад) від DB2 до MySQL.
paxdiablo

1
Я хочу дати зрозуміти, що я не відкидаю вашої відповіді. Це є корисним. Єдиний біт, який я приймаю під сумнів, - це заява на ефективність, оскільки ми провели оцінки в DB2 / z і виявили, що між count(*)та і немає реальної різниці count(1). Чи так це для інших СУБД, я не можу сказати.
paxdiablo

3
"Все інше потенційно вводить в оману і може кардинально змінитись при переході (наприклад) з DB2 на MySQL" Ви набагато більше шансів отримати погіршення продуктивності SELECT COUNT (*) при переміщенні СУБД, ніж різниця в застосуванні в SELECT 1 або COUNT (1). Я твердо вірю в написанні коду, який найяскравіше виражає саме те, чого ви хочете досягти, а не покладаюся на оптимізаторів чи компіляторів, які за замовчуванням відповідають вашій бажаній поведінці.
Вінстон Сміт

1
Оманливий вислів "COUNT (*)" означає "підрахунок рядків" повну зупинку. Він не потребує доступу до якогось конкретного стовпця. І в більшості випадків навіть не вимагатиме доступу до самого рядка, оскільки підрахунок достатній для кожного унікального індексу.
Джеймс Андерсон

9

Ви можете використовувати:

SELECT 1 FROM MyTable WHERE... LIMIT 1

Використовуйте select 1 для запобігання перевірки непотрібних полів.

Використовуйте LIMIT 1 для запобігання перевірки зайвих рядків.


3
Добре, але Limit працює на MySQL і PostgreSQL, топ працює на SQL Server, ви повинні відзначити це у своїй відповіді
Лев Гурдян

0

Я використовую такий спосіб:

IIF(EXISTS (SELECT TOP 1 1 
                FROM Users 
                WHERE FirstName = 'John'), 1, 0) AS DoesJohnExist

0

Інший варіант:

SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM [MyTable] AS [MyRecord])
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.