Этот баннер — требование Роскомнадзора для исполнения 152 ФЗ.
«На сайте осуществляется обработка файлов cookie, необходимых для работы сайта, а также для анализа использования сайта и улучшения предоставляемых сервисов с использованием метрической программы Яндекс.Метрика. Продолжая использовать сайт, вы даёте согласие с использованием данных технологий».
Политика конфиденциальности
|
|
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Здравствуйте, господа профессионалы! Поздравляю Вас с майскими праздниками Праздники праздниками, однако имееются вопросы, которые не дают покоя даже в праздники. Изучая Transact-SQL код хранимых процедур, написанный разными программистами иногда для одних и тех же задач, я встретила совершенно разные варианты. На вопрос почему же имеено так написана процедура, во всех случаях я получала вполне аргументированные ответы. Поэтому и возникает вопрос - существует ли _некий общий, самый правильный_ вариант? Заранее спасибо всем ответившим * * * * * * Имеем следующую таблицу Users с индексами на SQL Server 2000 * * * * * * CREATE TABLE [Users] ( [User_Id] smallint NOT NULL IDENTITY(1,1), [Name] varchar(30) NOT NULL, [Descr] varchar(100) NOT NULL, CONSTRAINT ucpk_users PRIMARY KEY CLUSTERED ( [User_Id] ) ) CREATE UNIQUE NONCLUSTERED INDEX unc_users ON [Users] ( [Name] ) GO * * * * * * Пример № 1. * * * * * * /* Процедура Add_User с использованием EXISTS(): 1. не надо запускать транзакцию, так как она "ест" ресурсы сервера 2. в таблице есть индекс для столбца [Name], а SQL server все равно производит сканирование индекса когда делает проверку [Name] на уникальность */ CREATE PROCEDURE Add_User ( @Name varchar(30), @Descr varchar(100) ) AS BEGIN SET NOCOUNT ON IF EXISTS( SELECT NAME FROM [dbo].[Users] WHERE [Name] = @Name ) BEGIN PRINT 'ERROR: Невозможно добавить пользователя. Пользователь с таким именем уже существует: ' + @Name RETURN 1 END ELSE BEGIN INSERT [dbo].[Users] ( [Name], [Descr] ) VALUES ( @Name, @Descr ) PRINT 'Пользователь успешно добавлен: ' + @Name END END GO /* Процедура Add_User с использованием транзакции: 1. Такой подход является более надежным так как построен на транзакциях, 2. так же позволяет отлавливать все ошибки, а не только "Cannot insert duplicate key row in object" */ CREATE PROCEDURE Add_User ( @Name varchar(30), @Descr varchar(100) ) AS BEGIN SET NOCOUNT ON BEGIN TRAN INSERT [dbo].[Users] ( [Name] , [Descr] ) VALUES ( @Name, @Descr ) IF @@ERROR <> 0 BEGIN PRINT 'ERROR: Невозможно добавить пользователя с именем: ' + @Name ROLLBACK TRAN END ELSE BEGIN PRINT 'Пользователь успешно добавлен: ' + @Name COMMIT TRAN END END GO ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.05.2002, 13:48 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Не существует. каждый пишет как научился или как научили. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.05.2002, 14:09 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Хелоу, крошка. Также и тебя с праздниками. Майскими. Теперь о густном. Что есть "Правильно" каждый Developer и DBA понимает по-своему. Так что единого мения на этот счет нет и быть не может. Но то, что ты тут показала неправильно точно. И вот почему. Вариант 1 1. 1. не надо запускать транзакцию, так как она "ест" ресурсы сервера В данном случае транзакцию вообще не надо запускать. И знаешь, почему? Потому, дорогая, что отдельные SQL statement'ы транзакционны по определению. 2. PRINT 'ERROR: Невозможно добавить пользователя. Пользователь с таким именем уже существует: ' + @Name Тут опять плохой тон - print'у в процедуре не место, нужен он для отладки (да и то не всегда годится). Надо бы RAISERROR использовать. 3. Где обработка @Name IS NULL? 4. INSERT [dbo].[Users] ( [Name], [Descr] ) VALUES ( @Name, @Descr ) PRINT 'Пользователь успешно добавлен: ' + @Name Ну это уже совсем плохо - ведь могла появиться запись пока мы не дошли до этого места! А где проверка @@ERROR = 0 AND @@ROWCOUNT = 1? Также неплохо бы явно указать RETURN 0 (именно явно). Я уже молчу про скобки квадратные, отсутствие форматирования и MS-специфичный INSERT Вариант 2 1. См п.1 Варианта 1 по поводу транзакций 2. Где @@ROWCOUNT = 1? 3. Неясно, почему не добавлен пользователь - коды ошибок приложения отсутствуют как класс Целую ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.05.2002, 14:22 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Уважаемая Дарья! Прежде всего хочу заметить, что более надёжных и менее надёжных методов в нашей работе не бывает - есть надёжные и ненадёжные. Вариант №1 относится к ненадёжному (написанный по принципу "каждый пишет как научился" ). Очевидно, что в нём между IF EXISTS и INSERT проходит некоторое время, за которое ловкие пользователи могут вставить тыщу записей. Т.о. процедура будет генерить сообщение об ошибке на клиенте и дополнительно возвращать или не возвращать сообщения на русском. Вариант №2 относится к надёжному. Однако он избыточен. Управление транзакциями при работе одного стейтмента не требуется - оно происходит само. Так-что я предлагаю третий вариант: CREATE PROCEDURE Add_User @Name varchar(30), @Descr varchar(100) AS BEGIN SET NOCOUNT ON INSERT [dbo].[Users] ( [Name] , [Descr] ) VALUES ( @Name, @Descr ) IF @@ERROR <> 0 PRINT 'ERROR: Невозможно добавить пользователя с именем: ' + @Name ELSE PRINT 'Пользователь успешно добавлен: ' + @Name END GO ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.05.2002, 14:38 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
На мой взгляд ни та ни другая процедура не являются идеальными (я бы, во всяком случае, так ни писал бы в данном случае) по следующим причинам: 1. Так называемая Entity Integrity (уникальный индекс, например) - не нуждается в дополнительных проверках в хранимых процедурах, иначе зачем же тогда индекс если все равно каждый раз проверяется уникальность значения Name? 2. Строка с дублирующимся именем ВСЕ РАВНО НЕ БУДЕТ ВСТАВЛЕНА, поэтому открывать транзакцию бесполезно. А что если какой-то внешний процесс уже открыл транзакцию для чего-то большего, чем просто add_user (вложенные транзакции поддерживаются SQL Server-ом, но в чем СМЫСЛ)? Зачем нужна (в данном конкретном случае) транзакция, если она ничем не поможет? 3. На мой личный вкус, управление транзакциями должно быть вынесено в приложение. Наличие транзакций в хранимых процедурах (за редким исключением, когда процедура действительно выполняет какую-то "пошаговую" работу, которая либо целиком успешна, либо целиком неуспешна) - размывает логику системы. Вывод: будьте внимательны с понятием "правильно". Для каждой системы "правильно" - по-своему. Просто стоит подумать над всеми плюсами и минусами каждого конкретного подхода и использовать самый оптимальный. В примере, который вы привели, ни та ни другая процедура не являются оптимальными (на мой взгляд, опять же). ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 08.05.2002, 14:40 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Спасибо всем ответившим, особенно alexeyvg и Dimos Уважаемый, bik_! По-моему вы невнимательно читаете то, что называется Темой вопроса, а тема здесь "Транзакции vs EXISTS()". Что же касается PRINT, который здесь для упрощения кода и квадратных скобок, то это в принципе не спрашивалось, но все равно спасибо, что напомнили. Насчет @@ROWCOUNT = 1, это также не ясно зачем, ведь столбeц User_Id уникальный. Насчет MS-специфичный INSERT... интересно, а какой здесь должен быть INSERT, что ли ORACLE-специфичный? ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 09.05.2002, 13:44 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
2Дарья Хэлоу, кроха! Уважаемый, bik_! - запятая не нужна По-моему вы невнимательно читаете то, что называется Темой вопроса, а тема здесь "Транзакции vs EXISTS()". А вот здесь, после вступления ("По-моему") запятая будет весьма кстати. Теперь по сути: тема вопроса была Как правильно? (хранимые процедуры) , а не "Транзакции vs EXISTS()". Я читаю не только внимательно, но еще и обдумываю, что написано, в отличие от большинства. Что же касается PRINT, который здесь для упрощения кода и квадратных скобок, то это в принципе не спрашивалось, но все равно спасибо, что напомнили. Видимо, квадратные скобки тоже для упрощения? Мне не жалко, но только я не напомнил, как мне кажется, а просветил. Насчет @@ROWCOUNT = 1, это также не ясно зачем, ведь столбeц User_Id уникальный. Ну это уже совсем - ведь проверка: \nINSERT ... if NOT (@@ERROR = 0 AND @@ROWCOUNT = 1) begin -- set application-specific error code and do other job return end является куда более безопасной и естественной, чем проверка одного только значения @@ERROR (т.к. это косвенный способ судить об успехе операции добавления, а @@ROWCOUNT = 1 - непосредственный) Насчет MS-специфичный INSERT... интересно, а какой здесь должен быть INSERT, что ли ORACLE-специфичный? А тут я отсылаю тебя почитать доки (подсказка: INSERT table VS INSERT INTO table). Многие считают, что форма записи в стандарте ANSI все же лучше, т.к. понять ее проще, переносить код (если случиться такой кошмар) легче... Чао 2alexeyvg К сожалению, твой вариант не выдерживает критики, т.к. он не заботится о том, чтобы Пользователь узнал, в чем именно проблема, случись она. Хорошо, если Пользователь умный. А если нет? 2Dimos 1. Так называемая Entity Integrity (уникальный индекс, например) - не нуждается в дополнительных проверках в хранимых процедурах, иначе зачем же тогда индекс если все равно каждый раз проверяется уникальность значения Name?[q/uote] См. ответ 2alexeyvg [quote]2. Строка с дублирующимся именем ВСЕ РАВНО НЕ БУДЕТ ВСТАВЛЕНА, поэтому открывать транзакцию бесполезно. А что если какой-то внешний процесс уже открыл транзакцию для чего-то большего, чем просто add_user (вложенные транзакции поддерживаются SQL Server-ом, но в чем СМЫСЛ)? Зачем нужна (в данном конкретном случае) транзакция, если она ничем не поможет?[q/uote] По поводу вложенных транзакций: поверь, бывают случаи, когда они необходимы. Что же касается того, как их реализовать (а в данном случае они вообще не реализованы), то см. недавние сообщения в форуме. [quote]3. На мой личный вкус, управление транзакциями должно быть вынесено в приложение. Наличие транзакций в хранимых процедурах (за редким исключением, когда процедура действительно выполняет какую-то "пошаговую" работу, которая либо целиком успешна, либо целиком неуспешна) - размывает логику системы. Это еще одно девелоперсокое заблуждение (или байка, из серии тех, что есть в книжке Лу Гринзоу "Философия программирования под Windows 95/NT"). Не стОит надеятся на приложение. Приложение - это клиент, сегодня один, завтра другой... И если придурок, который завтра будет дергать наши процедуры из своего приложения забудет про необходимость обрамлять все транзакциями? Не надо лениться сегодня, это спасет от кучи проблем завтра. (Сорри, ничего личного) ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 09.05.2002, 19:35 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
2 bik_ >> Хэлоу, кроха! Фамильярность из вас, bik_, прямо так и прет, как и ваше многословие! > Уважаемый, bik_! >> - запятая не нужна Запятая здесь нужна поскольку она авторская и указывает на интонацию, в данном случае выражает удивление (опять же по поводу вашей многословности, сдобренной грамматическими ошибками), а форум это не диктант по русскому в средней школе (т.е. стиль изложения в мыслей свободный, литературный, а не технический). Кстати, ваша лексическая кострукция >> Также и тебя с праздниками. Майскими. так же далека от школьных каконов >> Ну это уже совсем - ведь проверка: \nINSERT ... if NOT (@@ERROR = 0 AND @@ROWCOUNT = 1) begin -- set application-specific error code and do other job return end Вместо того чтобы "копировать" код впустую, вы бы сами подумали над избыточностью пропагандируемого вами кода или вы считаете, что _уникальный индекс_ может куда-то исчезнуть со временем или в @@ERROR может оказаться равным 0, когда нарушена целостность данных? @@ROWCOUNT (c @@ERROR) используется для проверки ситуаци когда какого-то значения ключа нет в таблице. Мне бы следовало вам пояснить, что значение ключа User_Id передаваемого в процедуру всегда из "правильного" диапазона значений (так как берется в клиентском приложении) и даже проверка на NULL в принципе не обязательно ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 10.05.2002, 05:28 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
Хелоу эгейн! Насчет грамматики - я сам делаю кучу ошибок, это правда. Но не таких же жутких! Вместо того чтобы "копировать" код впустую, вы бы сами подумали над избыточностью пропагандируемого вами кода или вы считаете, что _уникальный индекс_ может куда-то исчезнуть со временем или в @@ERROR может оказаться равным 0, когда нарушена целостность данных? Да, я пропагандирую максимально безопасный подход кодированию. А что в этом плохого? А вот в твоих примерах многое плохо. А именно: Уникальный индекс может действительно исчезнуть по ошибке DBA, например (решит удалить, а воссоздать забудет). Код для операции обновления, как ты сама заметила, все-таки будет содержать проверку значений и @@ROWCOUNT и @@ERROR, а это значит, код будет сложнее (тут один тип проверки, там другой) Мне бы следовало вам пояснить, что значение ключа User_Id передаваемого в процедуру всегда из "правильного" диапазона значений (так как берется в клиентском приложении) и даже проверка на NULL в принципе не обязательно О каком таком User_Id идет речь? Что-то не вижу в упор. Если ты имеешь ввиду @Name, то надеяться на клиентское приложение просо наивно. Именно такой подход к кодированию и приводит к появлению монстров, в которых ошибки отловить очень и очень сложно. P.S. Тема исчерпана, я выражаю только свое скромное мнение, можно не соглашаться, но дискутировать далее нет смысла. ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 10.05.2002, 07:34 |
|
||
|
Как правильно? (хранимые процедуры)
|
|||
|---|---|---|---|
|
#18+
По-моему, дорогой bik_, судя по размерам и хамскому тону ваших постов, вы далеки от того, чтобы считать свое собственное мнение "скромным", и уж тем более далеки от того, чтобы позволить кому-бы то ни было с ним не соглашаться. Сорри, тоже ничего личного... ... |
|||
|
:
Нравится:
Не нравится:
|
|||
| 10.05.2002, 14:51 |
|
||
|
|

start [/forum/topic.php?fid=46&msg=32029832&tid=1822801]: |
0ms |
get settings: |
6ms |
get forum list: |
14ms |
check forum access: |
3ms |
check topic access: |
3ms |
track hit: |
46ms |
get topic data: |
7ms |
get forum data: |
2ms |
get page messages: |
37ms |
get tp. blocked users: |
1ms |
| others: | 241ms |
| total: | 360ms |

| 0 / 0 |
