Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Быстрая проверка существования записи в Oracle


Страницы:   1 2

Ломброзо ©   (2007-03-25 14:19:00) 

Как известно, В MSSQL оператор EXISTS может использоваться как скалярная функция, которая возвращает булевый результат, и её можно использовать для быстрой проверки существования записи по условию, как то:

IF EXISTS(SELECT * FROM CUSTOMERS WHERE NAME = @NAME) THEN ....

В Oracle такая фича не работает и я выкручиваюсь через подсчёт числа записей:


DECLARE
 iCNT NUMBER;
BEGIN
 SELECT COUNT(ID) INTO iCNT FROM CUSTOMERS WHERE NAME = :NAME;
 IF iCNT = 0 THEN ...


Есть ли в оракле какой-нибудь заменитель сей громоздкой конструкции?


Petr V. Abramov   (2007-03-25 19:12:00) [21]

> констрейнт - это, по всем канонам проектирования, последний бастион обороны
это да

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

> Если есть какой-то другой способ трансформации низкоуровневых ошибок
> СУБД во внятные русскоязычные сообщения - готов выслушать :)

when others then
 if sqlcode = 1 then
   -- тут в автономной транзакции запиши куда-нить оригинальный sqlerrm
   raise_application_error(-2001, 'сам дурак и никаких имен таблиц' );
 else
   raise;
 end if;


еще обрати внимание на pragma exception_init


Ломброзо   (2007-03-25 19:00:00) [22]

Не, я не спорю с тем, что можно обойтись уникальным констрейнтом, но из соображений юзабилити гораздо красивше не вываливать на клиента мессежд бокс со страшным текстом о том, что Unique constraint violated, а самому ручками проверить входные данные и в случае чего - на литературном русском языке порадовать его внятной инструкцией о причине ошибки и способах её разрешения - безо всякого намёка на имена таблиц, полей и констрейнтов.

Если есть какой-то другой способ трансформации низкоуровневых ошибок СУБД во внятные русскоязычные сообщения - готов выслушать :)

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


Petr V. Abramov   (2007-03-25 18:28:00) [23]

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


Ломброзо   (2007-03-25 17:56:00) [24]

Petr V. Abramov ©   (25.03.07 17:45) [6]

аспасиба. Bulk collect тоже штука малознакомая, пойду изучать.
Индексы не пойдут, я хотел универсальное решение. В общем-то с тем же и остался, разве что с поправкой на конкурирующие транзакции. Буду иметь в виду.


Petr V. Abramov   (2007-03-25 17:45:00) [25]

> Ломброзо ©   (25.03.07 17:25) [5]
именно
только select count ... for update не пройдет :)

select id bulk collect into coll
а потом coll.count

вот в данном твоем случае не проще ли индекс уникальный сгородить и ловить исключение?


Ломброзо   (2007-03-25 17:25:00) [26]

а, понятно. Select for update лочит те строки, в которых я считаю count, и до завершения моей транзакции вторая, которая хочет выполнить commit, или будет висеть, или кинет исключение?


Petr V. Abramov   (2007-03-25 17:19:00) [27]

> В хранимой COUNT может сбойнуть?
ну уж слишком круто было бы :)
> насколько я знаю, ХП выполняется в транзакции.
ну и что?

вот смотри: :)

SELECT COUNT(ID) INTO iCNT FROM CUSTOMERS WHERE NAME = :NAME;
cnt чему-то равно, например, нулю.
IF iCNT = 0 THEN ...
if-то начинает выполняться не "прям сразу", и между селектом и и ифом вполне может кто-то закоммититься, и в результате в контектсте твоей же транзакции, если выполнить селект, он вернет уже, например, единицу. Но ты принимаешь решениена основе переменной cnt. В результате, если например, в этом IF iCNT = 0 THEN ... ты захочешь сказать insert, получишь дубль, хотя и честно проверял.
И не говори, что это "маловероятно". Законы Мэрфи имеют приоритет над законами больших чисел. Данный пример, конечно, упрощенный, но чем "дальше" от селекта ты принимаешь решение на основе его результата, тем быстрее словишь глюк, который  еще и никто не воспроизведет специально.


Ломброзо   (2007-03-25 17:04:00) [28]

Desdechado ©   (25.03.07 15:36) [1]
В 99% COUNT считается по индексированным полям, а посему, раз более простого способа проверить наличие записи не существует, продолжу пользоваться COUNT :)

Petr V. Abramov ©   (25.03.07 15:47) [2]

Это не клиентский код, серверная ХП. Проверка предусловий.
В хранимой COUNT может сбойнуть? насколько я знаю, ХП выполняется в транзакции. Объясни, пожалуйста, на пальцах :) что где нужно написать, чтобы между двумя операторами никакой паразит не смог прошмыгнуть


Petr V. Abramov   (2007-03-25 15:47:00) [29]

SELECT COUNT(ID) INTO iCNT FROM CUSTOMERS WHERE NAME = :NAME;
IF iCNT = 0 THEN ...

скользкая довольно конструкция. к моменту выполнения if`а  count уже может стать каким угодно, т.е., вообще говоря, оно эквивалентно if random.

for update нужно по-хорошему


Desdechado   (2007-03-25 15:36:00) [30]

Не знаю, как в 10-ке, а в 9-ке я не нашел.
Обычно использую одну из 3 конструкций (какая ближе к контексту операции):
CREATE OR REPLACE FUNCTION fn_TESTSELECT
RETURN INTEGER
AS
 nRes INTEGER := 10;
BEGIN
 /* такая конструкция вызовет исключение NO_DATA_FOUND, что прервет выполнение процедуры */
 SELECT class_id INTO nRes
   FROM Classes
   WHERE class_id = -1;
 /* 1-й способ обойти исключение */
 -- Позволяет также обойти случай с выборкой нескольких строк в одну переменную,
 -- что тоже вызывает исключение.
 -- Вернет первое попавшееся значение по условию или не изменит состояние
 -- переменной nRes при отсутствии данных в таблице.
 -- Наиболее близкое к поведению InterBase.
 FOR x IN( SELECT class_id INTO nRes
             FROM Classes
             WHERE class_id = -1 ) LOOP
   nRes := x.class_id;
   EXIT;  -- прерывание цикла
 END LOOP;
 /* 2-й способ обойти исключение */
 -- Позволяет проверить разные виды исключений, не только с отсутствием данных.
 -- При исключении вернет то, что указано в ветке WHEN.
 -- Гибко, но громоздко.
 BEGIN
   SELECT class_id INTO nRes
     FROM Classes
     WHERE class_id = -1;
 EXCEPTION
   WHEN NO_DATA_FOUND THEN
     nRes := -1;
 END;
 /* 3-й способ обойти исключение */
 -- Подходит только для случая выборки одного числового поля,
 -- чреват логическими ошибками при наличии нескольких разных значений по условию.
 -- Вернет NULL при отсутствии данных.
 SELECT Max( class_id ) INTO nRes
   FROM Classes
   WHERE class_id = -1;
   
 -- проверим, что же нашлось
 RETURN nRes;
END;


Count хуже тем, что все-таки индекс (если он есть) прорабатывается дольше. Если нет, то вообще дрова.


Страницы:   1 2

Наверх

Титульная страница Поиск, карта сайта Написать письмо