[ MS SQL ] уникальность в столбце типа: varchar
2) Можно ли SQL запросом вернуть существующую запись при попытке добавить не уникальную?
3) Можно ли таким методом проверять уникальность по нескольким столбцам (в совокупности)?
Можно пример конструкции?
Я так понимаю конструкция будет следующего вида:
BEGIN
INSERT INTO Company
(перечисляем столбцы)
VALUES (перечисляем значения)
END
ELSE
BEGIN
SELECT comp FROM Company WHERE comp='TestComp' OR adres='TestAdress'
END
Еще я тут по прочитанному понял, что можно создать хранимую процедуру вида:
@Comp nvarchar(50),
//тут еще поля
@CompID int OUTPUT
AS
и прям в нее передавать значения. Вот только как?
А по поводу хранимой процедуры, можете просветить? Я правильно понял ее смысл и как ей пользоваться?
А по поводу хранимой процедуры, можете просветить? Я правильно понял ее смысл и как ей пользоваться?
Вы не привели самой хранимки, лишь ее сигнатуру. Впрочем, возвращать идентификатор вполне годная практика.
Уникальность по комбинации нескольких столбцов обеспечивается включением их в один уникальный индекс. Уникальность по каждому отдельному столбцу - созданием отдельных уникальных индексов. В любом случае, смотрите на размер ключа индекса, чем он меньше тем лучше.
Если требуется вернуть, то да, примерно так, как вы сами написали, если требуется добавить новую или обновить существующую, то можно использовать, как уже посоветовали, MERGE, если версия сервера >=2008. Ну или по старинке insert+update.
Смысл хранимой процедуры - в том же, в чем и смысл процедуры в обычных языках программирования. Вы можете взять определенный набор инструкций, дать ему имя и сохранить на сервере, после чего, для его выполнения, просто обратиться по этому имени, указав список параметров если он был задан. Имейте кстати ввиду, что процедура!=транзакция. Т.е. если вы вызовете процедуру и думаете что она либо выполнится целиком, либо нет, то это не так, чтобы это было так - вызов процы нужно обернуть в транзакцию.
Касательно параметров output - вот, накидал простой пример, можете скопипастить и выполнить его в SSMS у себя на тестовом сервере
go
-- создаем таблицу
create table dbo.Company(CompID int identity primary key, Name varchar(100));
go
create unique nonclustered index ix_Name on dbo.Company(Name);
go
------------------------
-- создаем процедуру
create proc dbo.Company_insert
@CompID int = null out,
@Name varchar(100)
as
set @CompID = null;
if exists (select * from dbo.Company where Name = @Name) begin
raiserror ('Компания ''%s'' уже заведена в БД',10,1,@Name);
return;
end;
insert into dbo.Company(Name)
values (@Name);
set @CompID = scope_identity();
go
------------------------
-- вызываем процедуру, смотрим что добавилось, смотрим какой ИД получила запись
declare @CompID_inserted int;
-- вызов раз
exec dbo.Company_insert @CompID = @CompID_inserted out, @Name = 'ООО Рога и Копыта';
select @CompID_inserted;
-- вызов два-с
exec dbo.Company_insert @CompID = @CompID_inserted out, @Name = 'ИП Вася Пупкин';
select @CompID_inserted;
-- что в таблице
select * from dbo.Company;
-- пробуем добавить повторно
exec dbo.Company_insert @CompID = @CompID_inserted out, @Name = 'ИП Вася Пупкин';
select @CompID_inserted;
go
------------------------
-- удаляем демонстрационные данные
drop table dbo.Company;
drop proc dbo.Company_insert;
Имеется ввиду количество позиций (строк)? Предполагается примерно пару сотен. Возможно до тысячи, ну не больше))
Я так понял, это то что описано на MSDN - Индекс с включенными столбцами.
Отсюда вопрос: работает ли это на 2005-ом сервере? (Ах да, я вроде не упоминал про него. Сейчас учусь работать с ним)
Про хранимки пока понятно. Буду тестировать.
Имеется в виду длина строк которые будут храниться в этом столбце.
Нет, имеется ввиду размер типа данных поля, то что сказал hardcase. Т.е. если у вас поле например имеет тип varchar(8000), то индекс по нему, плохая идея. Собственно желательно чтобы суммарный размер типов данных колонок, участвующих в ключе, не превышал 900 байт, да и вообще, хоть 900 байт и позволяется как крайнее значение - не стоит этим злоупотреблять.
Отсюда вопрос: работает ли это на 2005-ом сервере? (Ах да, я вроде не упоминал про него. Сейчас учусь работать с ним)
Нет, включенные столбцы это другое. Это если, например, осуществляется операция поиск по индексу, найден нужный ключ индекса, то после этого требуется найти саму строку данных на которую указывает этот ключ, а это дополнительные операции. Но если в индекс включены, помимо самого ключа, еще и все данные что требуется запросу, то дополнительной операции поиска можно избежать, сразу взяв требуемые данные из самого индекса.
А я имел ввиду, что если например есть таблица с полями Фамилия, Имя. То если вы сделаете два уникальных индекса, один по фамилии, другой по имени, то нельзя будет ввести двух Ивановых (вне зависимости от их имени) или двух Василиев, вне зависимости от фамилии. Если же вы сделаете один индекс сразу по двум полям Имя+Фамилия, то будет проверяться уникальность сочетания этих значений. Например Можно будет ввести Иванов Иван и Иванов Василий. Но нельзя будет ввести Иванов Иван, Иванов Иван.
Если, кстати, у вас реально до тысячи и не предполагается больше, и таблица не широкая, то можете, в принципе, пока особо не задумываться над производительностью, это не большой объем данных. Но ради обеспечения целостности данных, если какое-то поле предполагается быть уникальным, то по нему в любом случае лучше создать уникальный констрейнт или индекс (с учетом пожеланий по размеру приведенных выше), даже если предполагается что с ним будут работать только через хранимые процедуры.
Эти 900 байт как высчитываются? Это Name varchar(900) (F_Name varchar(n) + S_Name varchar(m) < 900)? Или при Name varchar (n) и кол. предполагаемых строк "m": n*m<900 (то есть длинна строки помноженная на длинну) ?
На данный момент я практикую varchar (<100), но интересно естественно и дальнейшее развитие.
По поводу процедур, я попробовал, вроде разобрался как работает. Вы упоминали о том, что ХП не является транзакцией и при необходимости, необходимо ХП обернуть в транзакцию.
Для этого необходимо задать начало транзакции (BEGIN TRANSACTION <name>) и ее завершение (COMMIT TRANSACTION <name>). Тогда все, происходящее внутри либо завершится с успехом, либо "откатится" до точки начала транзакции?
Для работы с большим количеством строк (например в объемах онлайн-словарей) гораздо производительнее использовать хэши (CHECKSUM) и проверять сперва существование в таблице одноименной записи по хэшу, а лишь потом сравнивая сами строки.
Насчет транзакций да, нужно обернуть. Можно открыть транзакцию в начале тела самой ХП, но имхо, удобнее для этого использовать код клиентских компонентю Например в .NET что-то типа такого.
var sqlTran = sqlCon.BeginTransaction();
try
{
sqlCommand.Transaction = sqlTran;
sqlCommand.ExecuteNonQuery();
sqlTran.Commit();
}
catch
{
sqlTran.Rollback();
}
finally
{
sqlCon.Close()
}
Где sqlCon, sqlTran, sqlCommand - соотв. клиентские компоненты для работы с соединением, транзакцией и процедурой.
писал по памяти, студию влом запускать, так что мог налажать в названиях методов, но принцип думаю понятен. Наверняка можно что-то подобное изобразить и у вас. Тогда не нужно будет заботится об открытии и закрытии транзакций в самих процедурах.
hardcase,
Если не планируется вводить названия компаний более чем на двух языках (один из них англ), лучше оставить varchar() - функционал тот же, места в два раза меньше.
Я так понял, транзакция самостоятельно откатывает действия?
Можно ли как-нибудь проверить работу отката? Интересно как это выглядит )))
PS: огромное спасибо за терпение и "разжевывание". Жаль, что в университете так не объясняли :D. Получается предмет пройден, а из знаний только некоторые понятия, и то половина по собственной инициативе.
Если хотите проверить примитивно то вот например, запустите скрипт:
insert into t select 1;
begin tran;
insert into t select 2;
select * from t;
rollback tran;
select * from t;
go
drop table t;
go
Как вы увидите, сперва поcле добавления в транзакции в таблице будет две записи. Потом, без удаления, а просто откатом транзакции останется одна, т.е. последний инсерт откатится. Это очень упрощенно.
Интереснее исследовать механизмы блокировки и грязного чтения. Например, если вы откроете два окна в SSMS и
1) в первом выполните
go
insert into t select 1;
begin tran;
insert into t select 2;
select * from t;
----------
/*
rollback tran;
select * from t;
go
drop table t;
go
*/
2) во втором
3) далее вернитесь в первое и выделите мышкой только то, что между /*...*/ и, т.е.
select * from t;
go
drop table t;
go
Вы получите одну запись, тогда как запрос во втором окне, вернул две.
Т.е. он вернул запись которая реально уже не существует в таблице, т.к. транзакцию добавившую запись откатили а второй запрос попал так "неудачно" что прочел незафиксированные данные. Это эффекты грязного чтения с подсказкой nolock, которыми некоторые пользуются для ускорения работы сервера, т.к. не требуется ждать освобождения блокировок, но при этом не учитывают такие эффекты. так же возможны эффекты повторения записей, отсутствия записей и прочие интересные штуки.
Имхо, в универститете дают базу. Я вот с удовольствием учился первые три курса когда были матан, физика, дискретка, дифуры и т.д. И хоть сейчас я уже все почти забыл и в жизни это не пригодилось непосредственно - но зато это научило мыслить и воспринимать информацию абстрактно. Читал такую фразу и полностью с ней согласен : "Образование, это то, что у вас останется, когда вы забудете все, чему вас учили."
upd
А касательно собственой инициативы - это правильно! так что я целиком вас поддерживаю. Тоже учился всему что знаю самостоятельно и с помощью старших наставников на работе. Универ в этом плане у нас лажает. Мы тардиционно сильны в фундаментальных науках, все те предметы и многие другие что я перечислил выше, очень хорошие препы с советских времен. А на новые предметы увы - нет кадров. Зарплата не соответствует. Есть энтузиасты, в основном аспиранты или молодые препы работающие по совместительству, которым огромный поклон. Они пытаются что-то делать, но не у всех есть время и не всем работа позволяет. Общался недавно с одним таким челом, он сам MVP, более того приглашен на работу в MS, хотя пока преподает...но ведь тоже скоро уедет. Грустно короче. Энтузиасты - решение частных случаев, в очень не многих местах, чтобы это было повсеместно - нужна система. Ее увы пока нет. Хотя власти говорят о модернизации. Х.з. можно верить или это лозунги под очередные распилы бюджета. В любом случае надеяться, имхо, надо только на себя, так что самообразование пока рулит!
Не нужно так считать, например в MSSQL изолированность (буква I в аббревиатуре) управляется уровнем изоляции транзакции. Я предпочитаю открывать транзации не в клиентском коде, а в теле самой хранимой процедуре. В клиентском коде TransactionScope нужен для выполнения ряда запросов либо исполнения хранимых процедур, для случае вызова одной хранимки он выглядит странно.
hardcase,
Если не планируется вводить названия компаний более чем на двух языках (один из них англ), лучше оставить varchar() - функционал тот же, места в два раза меньше.
Это экономия на спичках либо преждевременная оптимизация.
Не нужно считать, что MSSQL обеспечивает ACID? В таком случае, приведите пример, когда с помощью транзакций нельзя обеспечить ACID. Сам функционал isolation level подразумевает изоляцию транзакций, разве не это декларирует ACID? Или вы под изоляцией понимаете вообще всегда полную изоляцию? Это тоже возможно, но в таком случае это просто ограничение, никак не влияющее на сам принцип. И лично я, рад, что разработчикам предоставлена возможность самим влиять на уровень изоляции. И т.е. если возможно грязное чтение (как я в предыдущем посте показал) это не значит что не обеспечивается изоляция, это значит, что изоляция контролируема. Вы так не считаете?
В клиентском ли коде или в процедурах - дело вкуса. Лично мне, удобнее, когда я открываю процу для редактирования не видеть логики обработки транзакции, т.к. я стараюсь оформлять в процедуры такой код, который либо выполняется целиком, либо нет. И транзакции были бы загромождением кода. Но повторяю, дело вкуса, в предыдущем посте я сказал, что возможно просто открывать транзакцию в начале тела процедуры (или вообще в любом месте, на самом деле).
Насчет экономии на спичках...Хм...=)
Прежде чем ответить вам, спрошу, в чем конкретно экономия и спички? =)
Под I подразумевается полная иозляция (уровень SERIALIZABLE), а при приличной нагрузке это выльется в очень сильное падение прозводительности. Поэтому были придуманы менее сильные режимы изоляции - они позволяют параллельно работать большему числу клиентов. Я веду к тому, что стандартный уровень изоляции READ COMMITTED совсем не является тем I про который написано в учебниках, он существнно слабее.
Насчет экономии на спичках...Хм...=)
Прежде чем ответить вам, спрошу, в чем конкретно экономия и спички? =)
Смысл в том, что у ТС имен компаний вряд ли наберется столько, что двукратное использование дисковой памяти под их хранение будет хоть сколько нибудь заметно. Попробуй ответить на вопрос - сколько имен компаний можно уместить в мегабайт.
Повторяю, вопрос не в том, каков уровень по-умолчанию, вопрос в том, что если нужно - можно обеспечить полную изоляцию. А в новых версиях сервера, благодаря snapshot isolation можно даже не сильно потерять в производительности. Хотя тут мы говорим не о производительности, а о самом факте наличия такой возможности.
Значит дисковая память под хранение. Отлично, именно по этому я уточнил что вы имеете ввиду говоря о спичках. =)
Но штука в том, что если бы сиквел сервер всегда осуществлял все операции чтения с диска - он был бы жутким тормозом. Для того чтобы этого избежать, в нем есть такая вещь как буферный кэш. И все операции чтения в сиквеле идут только через него!
Возможности этого кэша гораздо скромнее в плане объемов возможностей диска. По этому, чем он больше и чем меньше страниц данных в таблице, тем лучше для скорости.
Что мы получим, если в два раза увеличим объем строковых полей.
1. Кол-во страниц данных возрастет в два раза, потребуется в два раза больше физических операций чтения с диска, при отсутствии страницы в кэше.
2. Кол-во места занимаемого в кэше таблицей увеличится в два раза, а значит в два раза возрастет вероятность того, что данные будут вытеснены из кэша другими данными и придется делать повторное обращение к диску.
3. Если планируется осуществлять поиск по имени компании (а это в 99% почти всегда нужно), придется построить индекс по этому полю. Объем ключа индекса возрастет в два раза. А значит:
3.1. В два раза возрастут затраты на обслуживание индекса
3.2. В два раза уменьшится время необходимое для заполнения верхнего уровня индекса и создания еще одного. А значит в два раза быстрее увеличится число чтений при операции index seek (что так же накладывается на в два раза большую вероятность отсутствия страницы в кэше и дает эффект синергии падения производительности)
4. Если мы распространим такую "экономию на спичках" на проектирование всей БД - все эффекты возрастут многократно.
Хорошая плата за преждевременную псевдоуниверсальность?
Дисковое место тут играет последнюю роль, оно сейчас дешево, так что вы правы, когда говорите что его экономия нецелесообразна, но это очень поверхностно, а следует взглянуть в корень.
Моя полемика тут продиктована не желанием поспорить, а практическим опытом. Так уж получилось что я как раз занимаюсь системой CRM, в международной группе компаний, и пока за 7 лет существования системы, ни разу не возникла необходимость в юникоде. Т.к. в основном все интернациональные компании имеют международное английское название, а менеджеры работающие с системой, предпочитают видеть не китайские иероглифы на родном языке компании, а ее англоязычное название.
Так же, порекомендую вам статью Экономим место в БД за счет использования правильных типов данных. Там не обсуждается nvarchar vs varchar, но экономия всего в несколько байт позволяет уменьшить таблицу и снизить нагрузку на сервер, а тут мы говрим об экономии в два раза!