SQL: Как правилно да проверите дали записът съществува

Докато четях някаква документация, свързана с настройката на SQL, намерих това:

SELECT COUNT(*) :

  • Преброява броя на редовете.
  • Често се използва неправилно за проверка на съществуването на запис.

SELECT COUNT(*) наистина ли е толкова лошо?

Какъв е правилният начин за проверка на съществуването на запис?


person systempuntoout    schedule 23.11.2010    source източник


Отговори (9)


По-добре е да използвате едно от следните:

-- 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(*) така или иначе, така че макар да има разлика в теорията (и по-старите бази данни), не трябва да забелязвате разлика на практика.

person Martin Schapendonk    schedule 23.11.2010
comment
Ще поясня, че възнамерявах уникален ключ с клаузата ключ = стойност, но освен това все още съм зад отговора си. - person Martin Schapendonk; 26.09.2019
comment
ДОБРЕ. С тази предпоставка наистина заявката ще върне само един или нула запис. НО: Въпросът не се ограничава до уникална колона. Също така: Втората заявка count(1) е еквивалентна на count(*) от практически POV. - person Martin Ba; 27.09.2019
comment
Въпросът казва какъв е правилният начин да се провери съществуването на запис A. Тълкувах това като единствено число, както в: 1 запис. Разликата между count(*) и count(1) вече е покрита от моя отговор. Предпочитам count(1), защото не разчита на конкретна реализация на RDBMS. - person Martin Schapendonk; 27.09.2019

Бих предпочел изобщо да не използвам функцията 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
person Pavel Morshenyuk    schedule 23.11.2010
comment
Обикновено го използваме (проверката), когато искаме да направим нещо, тогава вашият отговор е по-пълен. - person Abner Escócio; 07.11.2018
comment
Добре е да споменем, че с помощта на T-SQL - person Bronek; 18.01.2019

Можеш да използваш:

SELECT 1 FROM MyTable WHERE <MyCondition>

Ако няма запис, съответстващ на условието, полученият набор от записи е празен.

person Cătălin Pitiș    schedule 23.11.2010
comment
Имахте предвид ТОП 1? -› (ИЗБЕРЕТЕ ТОП 1 ОТ MyTable, КЪДЕТО ‹MyCondition›) - person Jacob; 21.10.2015
comment
Не, имах предвид точно 1 - person Cătălin Pitiș; 21.10.2015
comment
за да позволите на оптимизатора на заявки дори да знае, че няма да прочетете/необходими оставащите набори от данни, трябва да посочите SELECT TOP 1 1 FROM... WHERE... (или използвайте подходящите съвети за заявка за вашата RDBS) - person eFloh; 13.07.2016
comment
Самият оператор Exists се опитва да извлече само абсолютния минимум от информация, така че добавянето на TOP 1 не прави нищо, освен да добави 5 знака към размера на заявката. - sqlservercentral.com/blogs/sqlinthewild/2011/04/05/ - person AquaAlex; 20.07.2016

Другите отговори са доста добри, но също така би било полезно да добавите LIMIT 1 (или еквивалент, за да предотвратите проверката на ненужни редове.

person JesseW    schedule 23.11.2010
comment
Ако някоя заявка за проверка за съществуване върне повече от един ред, мисля, че е по-полезно да проверите отново вашата клауза WHERE, вместо да ОГРАНИЧИТЕ броя на резултатите. - person Martin Schapendonk; 23.11.2010
comment
Мисля, че Limit се използва в Oracle, а не в SQL Server - person Shantanu Gupta; 23.11.2010
comment
Обмислям случая, когато те законно могат да бъдат множество редове -- където въпросът е: Има ли (един или повече) редове, които отговарят на това условие? В такъв случай не искате да ги разглеждате всичките, а само един. - person JesseW; 23.11.2010
comment
@Shantanu -- Знам, затова се свързах към (много задълбочена) статия в en.wikipedia, обясняваща другите форми. - person JesseW; 23.11.2010

SELECT COUNT(1) FROM MyTable WHERE ...

ще премине през всички записи. Това е причината да е лошо да се използва за съществуване на запис.

Бих използвал

SELECT TOP 1 * FROM MyTable WHERE ...

След намиране на 1 запис, той ще прекрати цикъла.

person oski    schedule 08.04.2013
comment
В случай на SELECT TOP 1 ще прекрати ли действително, след като намери един или продължава да намира всички, за да може да каже кой е ТОП? - person Eirik H; 13.03.2014
comment
PS: За да съм сигурен, че винаги IF EXISTS (SELECT TOP 1 1 FROM ... WHERE ..) - person Eirik H; 13.03.2014
comment
операторът Star ще принуди СУБД да има достъп до клъстерния индекс вместо само до индекса(ите), който ще е необходим за вашето условие за присъединяване. така че е по-добре да използвате постоянна стойност като резултат, т.е. изберете най-горе 1 1 .... Това ще върне 1 или DB-Null, в зависимост от това условието е съвпадение или не. - person eFloh; 13.07.2016
comment
това е хубаво. Първата ми харесва. - person isxaker; 24.08.2018

Можеш да използваш:

SELECT COUNT(1) FROM MyTable WHERE ... 

or

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

Това ще бъде по-ефективно от SELECT *, тъй като просто избирате стойност 1 за всеки ред, а не за всички полета.

Има и фина разлика между COUNT(*) и COUNT(име на колона):

  • COUNT(*) ще преброи всички редове, включително нули
  • COUNT(column name) ще брои само ненулеви срещания на име на колона
person Winston Smith    schedule 23.11.2010
comment
Вие правите погрешното предположение, че СУБД по някакъв начин ще провери всички тези колони. Разликата в производителността между count(1) и count(*) ще бъде различна само в най-мозъчната СУБД. - person paxdiablo; 23.11.2010
comment
@paxdiablo предполагате ли, че разчитането на подробности за изпълнението е добра идея? - person Winston Smith; 23.11.2010
comment
Не, казвам, че вие всъщност разчитате на подробности за изпълнението, когато заявявате, че ще бъде по-ефективно. Ако наистина искате да сте сигурни, че получавате най-добра производителност, трябва да го профилирате за конкретната реализация, като използвате представителни данни, или просто да забравите за него напълно. Всичко друго е потенциално подвеждащо и може да се промени драстично при преминаване (например) от DB2 към MySQL. - person paxdiablo; 23.11.2010
comment
Искам да стане ясно, че не пренебрегвам отговора ви. Е полезно. Единственото, което имам проблеми, е твърдението за ефективност, тъй като направихме оценки в DB2/z и открихме, че няма реална разлика между count(*) и count(1). Дали това е така за други СУБД, не мога да кажа. - person paxdiablo; 23.11.2010
comment
Всичко друго е потенциално подвеждащо и може да се промени драстично при преместване (например) от DB2 към MySQL Много по-вероятно е да бъдете ухапани от влошаване на производителността на SELECT COUNT(*) при преместване на DBMS, отколкото от разлика в изпълнението в SELECT 1 или COUNT(1). Аз съм твърдо привърженик на писането на код, който най-ясно изразява точно това, което искате да постигнете, вместо да разчитате на оптимизатори или компилатори, за да зададете по подразбиране желаното от вас поведение. - person Winston Smith; 23.11.2010
comment
Подвеждащо твърдение COUNT(*) означава точка „преброяване на редовете“. Не изисква достъп до конкретна колона. И в повечето случаи дори няма да изисква достъп до самия ред, тъй като всеки уникален индекс е достатъчен за преброяване. - person James Anderson; 23.11.2010

Можеш да използваш:

SELECT 1 FROM MyTable WHERE... LIMIT 1

Използвайте select 1, за да предотвратите проверката на ненужни полета.

Използвайте LIMIT 1, за да предотвратите проверката на ненужни редове.

person user3059943    schedule 14.01.2015
comment
Добра точка, но Limit работи на MySQL и PostgreSQL, top работи на SQL Server, трябва да го отбележите в отговора си - person Leo Gurdian; 09.11.2018

Аз използвам този начин:

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

Друг вариант:

SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM [MyTable] AS [MyRecord])
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
person Pranav    schedule 19.07.2019