2015-03-05 4 views
0

ВведениеSQL скалярная подзапрос проверки строки был найден

Иногда вместо объединения можно сознательно использовать скалярный подзапрос, чтобы проверить, что не более чем одна строка была найдена. Например, у вас может быть этот запрос, чтобы найти национальность для некоторых строк person.

select p.name, c.iso from person p join person_country_map pcm on p.id = pcm.person join country c on pcm.country = c.id where p.id in (1, 2, 3)

Теперь предположим, что person_country_map не является функциональным отображением. Данный человек может отображаться более чем в одной стране, поэтому в объединении может быть найдено несколько строк. Или, действительно, человек не может быть в таблице сопоставления вообще, по крайней мере, в отношении любых ограничений базы данных.

Но для данный конкретный вопрос Я знаю, что люди, которых я запрашиваю, будут иметь ровно одну страну. Это предположение, что я основываю свой код на. Но я хотел бы проверить это предположение, когда это возможно, чтобы, если что-то пошло не так, и я в конечном итоге попытаюсь сделать этот запрос для человека с более чем одной страной или без какой-либо страны, отображающей строку, он умрет.

Добавление проверки безопасности для не более чем одной строки

Для проверки более одной строки, вы можете переписать присоединиться в качестве скалярного подзапроса:

select p.name, ( select c.iso from person_country_map pcm join country c on pc.country = c.id where pcm.person = p.id ) as iso from person p where p.id in (1, 2, 3)

Теперь СУБД дать ошибку, если человек запросил карты для двух или более стран. Он не будет возвращать несколько записей для одного и того же человека, так как прямое соединение будет. Поэтому я могу спать немного легче, зная, что этот случай ошибки проверяется даже до того, как любые строки будут возвращены в приложение. Разумеется, в качестве осторожного программиста я могу проверить приложение.

Можно ли иметь проверку безопасности длянетстрок нашли?

Но как насчет того, нет ли строки в person_country_map для человека? Скалярный подзапрос в этом случае возвращает null, что делает его примерно эквивалентным левому соединению.

(ради аргумента предположим, внешний ключ от person_country_map.country к country.id и уникальный индекс country.id так, чтобы присоединиться частности всегда будет успешным и найти ровно одну строку страны.)

Мой вопрос

Есть ли способ выразить в SQL, что я хочу один и ровно один результат? Простой скалярный подзапрос равен «ноль или один». Я хотел бы быть в состоянии сказать

select 42, (select exactly one x from t where id = 55)

и есть запрос сбой во время выполнения, если подзапрос не возвращает строку. Конечно, вышеупомянутый синтаксис вымышленный, и я уверен, что это будет не так просто.

Я использую MSSQL 2008 R2, и на самом деле этот код хранится в процедуре, поэтому при необходимости я могу использовать TSQL. (Очевидно, что обычный декларативный SQL является предпочтительным, поскольку он также может использоваться в определениях определения.) Конечно, я могу сделать проверку exists, или я могу выбрать значение в TSQL-переменной, а затем явно проверить его на nullness и т. Д. Я даже мог выбрать результаты во временную таблицу, а затем создавать уникальные индексы на этой таблице в качестве проверки. Но нет ли более читаемого и элегантного способа отметить мое предположение, что подзапрос возвращает ровно одну строку и имеет ли это допущение, проверенное СУБД?

+0

Вы считали пользовательскую функцию, которая генерирует исключение, если не существует одного результата? – HABO

+0

@HABO, вы имеете в виду общую функцию check_not_null (x), которая возвращает x, если x не является нулевым, и barfs (путем запуска деления на ноль, скажем) в противном случае? –

+0

Я думал о 'GetCountryForUser (UserId)'. Он либо возвращает уникальный «CountryId» для указанного пользователя, либо жалуется. К сожалению, 'RaIsError' и' Throw' не разрешены в UDF, поэтому у вас осталось что-то вроде [this] (http://stackoverflow.com/questions/1485034/how-to-report-an-error-from- а-SQL-сервер, определяемый пользователь-функция). – HABO

ответ

0

В MSSQL оказывается, что только isnull оценивает свой второй аргумент, если первый имеет значение NULL. Таким образом, в целом можно сказать,

select isnull(x, 0/0)

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

select 42, isnull((select x from t where id = 55), 0/0)

гарантирует, что ровно одна строка найдена в select x подзапроса. Если более одного, сама СУБД вызовет ошибку; если нет строки, срабатывает деление на ноль.

Применяя это к исходному примеру, приводит к коду

select p.name, -- Get the unique country code of this person. -- Although the database constraints do not guarantee it in general, -- for this particular query we expect exactly one row. Check that. -- isnull(( select c.iso from person_country_map pcm join country c on pc.country = c.id where pcm.person = p.id ), 0/0) as iso from person p where p.id in (1, 2, 3)

Для лучшего сообщения об ошибке можно использовать сбой преобразования вместо деления на ноль:

select 42, isnull((select x from t where id = 55), convert(int, 'No row found'))

хотя что потребуется дополнительно convert shenanigans, если значение, которое вы извлекаете из подзапроса, само по себе не является int.

0

Вы делаете это тяжелее, чем это должно быть

Наверняка вы нужны отношения FK на person.id к person_country_map.person

либо есть ограничение уникальности на person_country_map.person или вы не» т?
Если у вас нет уникального ограничения, тогда да, вы можете иметь несколько записей для одного и того же person_country_map.person.

Если вы хотите знать, если у вас есть какие-либо дубликата затем

select pcm.person 
from person_country_map pcm 
group by pcm.person 
having count(*) > 1 

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

select p.name, 
     min(c.iso) 
from person p 
join person_country_map pcm 
    on p.id = pcm.person 
join country c 
    on pcm.country = c.id 
where p.id in (1, 2, 3) 
group by p.name 
+0

Нет уникального ограничения на person_country_map.person, потому что * вообще *, человек может принадлежать более чем одной стране. Но * для этого конкретного приложения *, я знаю, что я только обращаюсь к людям, которые принадлежат к одному.Пример схемы и запроса изобретен, чтобы проиллюстрировать общую точку, требующую проверки скалярного подзапроса, возвращает ровно одну строку, потому что ограничения базы данных не гарантируют ее, хотя я, как программист, считает, что это всегда будет так. –

+0

Работает ли ответ? – Paparazzi

+0

Это не ответ на мой вопрос, так как вы можете сделать SQL-скалярный подзапрос, который проверяет, что был возвращен только один результат. Пример схемы «человек» - это только то, что я придумал, чтобы проиллюстрировать этот вопрос. Запрос, который возвращает 'min (c.iso)', является одним из методов, который я использовал в прошлом, но в случае обнаружения более одной строки я не хочу определять, какой из них; Я хочу, чтобы запрос терпел неудачу с большой толстой ошибкой, чтобы я мог узнать, что не так с моими предположениями. –

Смежные вопросы