- Table of contents
- Часть 1. Описание данных
Часть 1. Описание данных¶
За описание, создание и модификацию структуры базы данных отвечает подмножество языка SQL - DDL (Data Definition Language).
С его помощью можно создавать, изменять, удалять таблицы и связи между ними.
Это важно!
В примерах, наименование таблиц, столбцов и пр. будет происходить с использованием общепринятого sql style guide: ссылка
В структуре МБД Digispot II есть расхождения с рекомендациями по стилю. В случае необходимости, сопоставление проводите самостоятельно.
В ходе практических заметок, для примера, мы будем создавать сильно упрощенную структуру МБД Digispot II. Для тех, кто хочет более глубоко познакомиться с языком SQL и структурой МБД Digispot II, можете расширить таблицы в примерах дополнительными полями (атрибутами).
Начнем с формирования структуры для карточек элемента, базовая структура, в привычном для человека виде:
| Название | Имя файла | Исполнитель | Автор | Альбом | Длительность | Тип | Дата добавления |
|
Роза чайная |
\\root\snd\music\roza_chaynaya.mp3 | Филипп Киркоров, Маша Распутина | Степанов В. | Незнакомка | 4:22 | Музыка | 19.02.2021 12:05:37 |
| Жестокая любовь | \\root\snd\music\zhestokaya_lybov.mp3 | Филипп Киркоров | Попков О. | Незнакомка | 3:47 | Музыка | 03.12.2021 11:46:23 |
| Будь или не будь | \\root\snd\music\bud_ili_ne_bud.mp3 | Алла Пугачева, Максим Галкин | Любаша | Живи спокойно, страна | 3:39 | Музыка | 12.11.2006 15:37:12 |
| Исчезнет грусть | \\root\snd\music\ischeznet_grust.mp3 | Алла Пугачева | Забелин В. | Живи спокойно, страна | 3:45 | Музыка | 14.07.2021 09:16:18 |
Чтобы не делать себе очень больно, сразу посмотрим, как нормализовать базу до 3НФ по исходным данным.
Подробнее про Нормальные формы: Теоретическая_часть_Введение_в_язык_SQL_и_основы_реляционных_баз_данных
Поле "Исполнитель" нарушает НФ1, т.к. значение не атомарное, необходимо будет добавить еще одну строчку для "Розы чайной", а это в свою очередь приведет к тому, что нужно задублировать все остальные поля и только из-за того, что у композиции два исполнителя. У песни "Будь или не будь" - аналогичная ситуация и в целом, у нас в базе может быть очень много песен, которые исполнялись дуэтом.
Далее, глядя на поля "Исполнитель" и "Автор" можно предположить, что в будущем, в системе нужно будет хранить еще информацию о композиторе. Что нам тогда придется делать? Полный кошмар - добавлять новый столбец, это приведет к серьезному изменению структуры, не говоря уже об изменениях, которые потребуется вносить в код ПО по части добавления карточки элемента в БД и чтения карточки элемента из БД. Хранить информацию о них в таблице карточек элемента - не рационально.
Какие из представленных полей следует выделить в отдельную таблицу?
- Исполнитель - у одной песни может быть несколько исполнителей, один и тот же исполнитель может быть у разных песен
- Автор - у одной песни может быть несколько авторов, один и тот же автор может написать несколько песен
- Композитор - у одной песни может быть несколько композиторов (хоть и не часто, но это не такая уж и редкость), один композитор может сочинить музыку для разных песен
Условно, эти три поля можно отнести к т.н. "атрибуту" конкретной песни, а их количество может постоянно меняться, поэтому подготовим для них отдельную таблицу:
таблица атрибутов фонограммы (dbo.attributes)
| Атрибуты |
| Исполнитель |
| Автор |
| Композитор |
Теперь, для оставшихся полей, нам необходимо подобрать названия для таблицы и тип данных, который мы будем использовать.
Подробнее про тип данных в SQL можно прочитать здесь: Теоретическая_часть_Введение_в_язык_SQL_и_основы_реляционных_баз_данных
- Название - phonogram_name - nvarchar(250)
- Имя файла - file_name - nvarchar(250)
- Альбом - album_name - nvarchar(250)
- Длительность - duration_size - int
- Тип - type - nchar(1)
примечание: только ради примера, мы оставляем это поле в начале создания таблицы. Тип используется в различных местах базы данных и для обеспечения целостности данных, будет выделен в отдельный домен (перечень допустимых значений).
Пока что принимаем тип как М - музыка, Н - новости, П - передачи, Д - джинглы, Р - реклама - Дата добавления - add_date - datetime2(3)
примечание: нам достаточна и необходима точность до 1мс
Краткий перечень используемых команд¶
-
CREATE DATABASE имя_базы_данных
создает новую, простую базу данных -
DROP DATABSE имя_базы_данных
удаляет базу данных -
CREATE TABLE имя_таблицы (перечисление полей и их типов, ограничений)
создает новую таблицу в текущей БД -
DROP TABLE имя_таблицы
удаляет таблицу из текущей БД -
ALTER TABLE имя_таблицы ALTER COLUMN имя_столбца
переопределяет параметры столбца (если это допустимо) -
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения PRIMARY KEY (перечисление полей, входящих в первичный ключ)
добавляет первичный ключ к уже существующей таблице -
ALTER TABLE имя_таблицы DROP CONSTRAINT имя_ограничения
удаляет ограничение из таблицы -
ALTER TABLE имя_таблицы ADD имя_столбца1 тип1, имя_столбца2 тип2...
добавляет в таблицу новые столбцы (поля) с указанным типом -
ALTER TABLE имя_таблицы DROP COLUMN перечень_полей
удаляет столбцы из таблицы -
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения FOREIGN KEY (перечисление полей, которые ссылаются на поля другой таблицы) REFRENCES таблица_справочник(поля)
создает ссылочное ограничение, связь между таблицей и таблицей справочником -
CREATE INDEX имя_индекса ON имя_таблицы(поле1, поле2, ...)
создает индекс в таблице по указанным полям -
DROP INDEX имя_индекса ON имя_таблицы
удаляет индекс в указанной таблице
Операции создания/удаления базы данных/таблицы¶
Для выполнения примеров, создадим тестовую базу данных под названием DS2.
Простую базу данных (без указания дополнительных параметров) можно создать, выполнив следующую команду:
CREATE DATABASE DS2
Удалить базу данных можно командой:
DROP DATABASE DS2
Для того, чтобы переключиться на конкретную базу данных, можно выполнить команду:
USE DS2
Теперь, в нашей БД, мы можем создать первую таблицу для хранения информации о фонограммах:
CREATE TABLE phonograms( phonogram_name nvarchar(250), file_name nvarchar(250), album_name nvarchar(250), duration_size int, type nchar(1), add_date datetime2(3) )
Удалить таблицу можно командой:
DROP TABLE phonograms
Обязательные и необязательные столбцы для заполнения (NULL и NOT NULL)¶
По умолчанию, если не указать ограничение, все столбцы в таблице не обязательны для заполнения, т.е. могут не содержать значений (NULL). Чтобы указать столбцы, которые обязательно должны быть заполнены, можно использовать опцию NOT NULL.
В нашем примере, при добавлении фонограммы в базу данных, из перечисленных параметров обязательным является название (phonogram_name) и дата добавления (add_date), т.к. самого физического файла изначально у нас может и не быть, как и в принципе остальной информации о фонограмме, которую мы хотим добавить позже.
CREATE TABLE phonograms( phonogram_name nvarchar(250) NOT NULL, file_name nvarchar(250), album_name nvarchar(250), duration_size int, type nchar(1), add_date datetime2(3) NOT NULL )
Переопределение таблицы (ALTER TABLE)¶
Инструкция ALTER TABLE изменяет определение таблицы путем изменения, добавления или удаления столбцов и ограничений, а также переназначает и перестраивает секции или отключает и включает ограничения и триггеры. Подробнее...
В рамках нашего примера мы нередко будем обращаться к этой инструкции, выполняя различные модификации над таблицами. При использовании я буду описывать, что конкретно мы меняем, но для пояснения синтаксиса (если необходимо) - обращайтесь за документацией по ссылке выше.
Возвращаясь к нашему примеру, для того, чтобы в существующей таблице переопределить столбцы и указать их как обязательными для заполнения, можно воспользоваться командой:
-- обновление поля phonogram_name ALTER TABLE phonograms ALTER COLUMN phonogram_name nvarchar(250) NOT NULL -- обновление поля add_date ALTER TABLE phonograms ALTER COLUMN add_date nvarchar(250) NOT NULL
Это важно!
Если в таблице, в указанных столбцах уже существуют null-значения, инструкция ALTER COLUMN завершится с ошибкой. Прежде чем разрешит инструкцию ALTER COLUMN NOT NULL, следует обновить значения null, присвоив им какое-нибудь значение.
Первичный ключ (PRIMARY KEY)¶
При создании таблицы, для соблюдения 2НФ достаточно иметь потенциальный ключ, но желательно сразу определить первичный ключ, т.к. это упростит связь таблиц в дальнейшем.
Попробуем найти естественный ключ среди имеющихся полей или комбинации полей.
Из имеющихся полей, суперключом может быть поле add_date или любая комбинация с этим полем (т.к. вероятность того, что две фонограммы получат абсолютно идентичное время добавления крайне мала). Исходя из требований к потенциальному ключу (уникальность и минимальность), в качестве естественного ключа может выступить только поле add_date, но использовать его в дальнейшем и уж тем более для создания связей с другими таблицами - крайне неудобно, поэтому нам необходимо добавить еще одно поле к таблице, где мы будем автоматически генерировать уникальное значение для первичного ключа.
Согласно sql style guide, у каждой таблицы должен быть хотя бы один ключ, а первичный ключ должен быть объявлен в самом начале, сразу после оператора CREATE TABLE.
Задача:
- Добавить столбец, который будет выполнять функцию первичного ключа (простой, суррогатный ключ)
- Создать первичный ключ
Инструкция ALTER TABLE ADD COLUMN добавляет поле в конец таблицы (а если таблица и дальше будет модифицироваться, то этот столбец в итоге будет где-то в середине, что очень неудобно), поэтому нам придется полностью пересоздать текущую таблицу с учетом необходимости создания столбца для первичного ключа. Эту ошибку мы учтем в будущем и все остальные таблицы мы будем создавать сразу с первичным ключом.
Удаляем имеющуюся таблицу:
DROP TABLE phonograms
Создадим таблицу с полем для первичного ключа и сразу обозначим его таковым:
CREATE TABLE phonograms( phonogram_id int NOT NULL PRIMARY KEY, phonogram_name nvarchar(250) NOT NULL, file_name nvarchar(250), album_name nvarchar(250), duration_size int, type nchar(1), add_date datetime2(3) NOT NULL )
Новое поле - phonogram_id, для первичного ключа обязательно наличие ограничения NOT NULL, которое мы и указали, а при помощи команды PRIMARY KEY мы указали, что это поле является первичным ключом и СУБД создаст его для нас с системным именем вида "PK_phonogra_8075B41E1795A661".
Где,
PK_ - префикс, означающий, что данное ограничение - первичный ключ
phonogra - имя таблицы (а точнее, первые 8 символов от имени таблицы)
_8075B41E1795A661 - автоматически присваиваемый суффикс
Это важно!
Используя простой синтаксис PRIMARY KEY, система создает для таблицы первичный ключ с системным именем, в связи с чем будут недоступны такие команды как ALTER TABLE DROP CONSTRAINT.
В реальной архитектуре БД, менять ключи (PRIMARY KEY и FOREIGN KEY) в основном не приходится, но в процессе изучения данного материала, нам предстоит много чего изменить, поэтому только здесь мы будем явно создавать ограничения с ключами и присваивать им имя вручную, сохраняя базовые принципы именования.
В случае с MS SQL, удалить первичный ключ запросом будет не очень просто, поэтому выполняем это через Managment Studio.
Теперь мы можем добавить ограничение к таблице с указанием первичного ключа:
ALTER TABLE phonograms ADD CONSTRAINT PK_phonograms PRIMARY KEY(phonogram_id)
Для того, чтобы указать несколько полей для первичного ключа (составной ключ), названия этих полей перечисляются через запятую:
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения PRIMARY KEY(поле1, поле2, ...)
На заметку.
На текущем этапе, созданный нами "Первичный ключ" еще не совсем удобно использовать, т.к. его значение нам необходимо вносить в таблицу вручную.
Автоматическое заполнение поля phonogram_id мы реализуем чуть позже.
Нормализация БД - дробление на подтаблицы (справочники) и определение связей¶
Подробнее про нормализацию можно прочитать здесь: Теоретическая_часть_Введение_в_язык_SQL_и_основы_реляционных_баз_данных
Исходя из описания, цели, которые мы будем сейчас преследовать:
- Минимизировать повторяющиеся данные, тем самым сокращая занимаемый размер хранимых данных
- Защита данных от некорректного ввода и расхождений
- Устранение непоследовательных связей
На первом этапе, просто глядя на исходные данные, мы сразу же нашли нарушение 1НФ и сразу же учли это при составлении первой нашей таблицы базы данных, которая хранит в себе информацию о фонограммах. Затем, мы создали для таблицы первичный ключ, чтобы привести таблицу к 2НФ, давайте посмотрим, что мы еще можем сделать.
В начале, в отдельную таблицу мы уже вынесли поля "Исполнитель", "Автор" и "Композитор". Туда же мы добавим еще поле "Альбом" - исключаем повторяющиеся записи, предотвращаем ошибку в виде различных вариантов написания названия одного и того же альбома.
Теперь, таблица с перечнем атрибутов у нас выглядит примерно так:
CREATE TABLE attributes( attribute_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_attributes PRIMARY KEY, attribute_name nvarchar(250) NOT NULL )
Параметр IDENTITY(s,i) автоматически формирует идентификатор в поле по заданным параметрам "s" и "i". Где s (seed) - значение, присваиваемое самой первой строке, загружаемой в таблицу, i (increment) - значение приращения, которое прибавляется к значению идентификатора предыдущей загруженной строки. В нашем случае, идентификатора начинается с 1 и увеличивается последовательно на 1.
В исходных данных можно заметить нарушение 3НФ - транзиктивная зависимость. Длительность файла относится непосредственно к физическому аудиофайлу (как и часть других параметров, которые мы в изначальном примере опустили, но сейчас добавим). К тому же, несколько карточек фонограм могут обращаться к одному и тому же физическому файлу (получим задвоение данных) и дата добавления может отличаться у самой карточки фонограммы и физического файла. Т.е. мы можем в системе сначала завести карточку фонограммы, добавить все необходимое описание, а уже позже, когда будет готов физический файл, добавим его в систему и свяжем с карточкой. Выделим параметры, относящиеся к физическому файлу в отдельную таблицу:
CREATE TABLE files( file_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_files PRIMARY KEY, file_name nvarchar(250) NOT NULL, duration_size int NOT NULL, sample_size int NOT NULL, frequency int NOT NULL, add_date datetime2(3) NOT NULL )
Мы добавили такие поля, как sample_size - количество сэмплов у аудиофайла и frequency - частота дискретизации файла.
Сущность "Тип материала" - может использоваться в разных местах таблицы, но количество возможных значений слишком мало и выделять в отдельный справочник нет смысла. Изначальный наш выбор типа поля nchar(1) для хранения значения тоже не очень хороший - он допускает возможность ошибки, когда в одном месте может использоваться одинаково выглядящий символ в русской или английской раскладке. Проще и лучше использовать числовой идентификатор с заранее выбранным соответствием например: 1 - реклама, 2 - джинглы, 3 - музыка, 4 - новости, 5 - передачи.
Скорректируем нашу таблицу phonograms (не забудьте предварительно удалить предыдущую версию таблицы)
CREATE TABLE phonograms( phonogram_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_phonograms PRIMARY KEY, phonogram_name nvarchar(250) NOT NULL, type int NOT NULL, add_date datetime2(3) NOT NULL )
У нас получились 3 таблицы:
- attributes - промежуточная таблица с перечнем атрибутов, в дальнейшем будем использовать ее, чтобы связать конкрутную фонограмму с атрибутами, которые хотим для нее добавить и их значениями (в том числе, если значений для одного атрибута несколько)
- files - таблица со списком физических файлов и их параметрами, которые нужны будут для работы нашей системы
- phonograms - таблица со списком карточек фонограм и их описанием, которые мы будем использовать в системе
Чтобы иметь возможность связать карточку фонограмы с физическим файлом, в первую очередь добавим в таблицу phonograms дополнительное поле для связи, где будут хранится идентификаторы файлов из таблицы files. Тип ссылочных полей должен совпадать, в нашем случае - это int.
-- добавляем поле для ID физического файла ALTER TABLE phonograms ADD file_id int
Ссылочные ограничения (FOREIGN KEY)¶
Теперь пропишем ссылочные ограничения для этих полей, для того, чтобы пользователь не имел возможности записать в данные поля значения, отсутствующие среди значений ID находящихся в справочниках (подтаблицах).
ALTER TABLE phonograms ADD CONSTRAINT FK_phonograms_file_id FOREIGN KEY(file_id) REFERENCES files(file_id)
Рассшифруем запись: в таблице phonograms добавить ограничение с именем "FK_phonograms_file_id" для поля file_id, которое ссылается на диапазон значений в поле file_id таблицы files. Т.е. в нашей таблице phonograms мы можем указать только существующие id файлов.
Прежде чем связать фонограмму с новым файлом, нам необходимо добавить его в базу данных в соответствующую таблицу. И теперь, если нам надо изменить путь к файлу, нам достаточно это сделать в одном месте и не придется обновлять путь к файлу у всех фонограм, которые связаны с ним.
Имя ссылочного ограничения, обычно является составным, оно состоит из префикса "FK_" (FOREIGN KEY), затем идет имя таблицы и после идет имя поля, которое ссылается на идентификатор таблицы-справочника.
На заметку.
Таблица может ссылаться на саму себя, т.е. можно создать рекурсивную ссылку. Делается все аналогичным образом, но в нашем примере не требуется.
Ссылочные ограничения могут включать дополнительные опции:
- ON DELETE CASCADE - при удалении записи из справочника, удалять все записи из таблицы, которые ссылались на это значение
- ON UPDATE CASCADE - при изменении записи из справочника, обновлять все записи из таблицы, которые ссылались на это значение
Оба этих параметра стоит использовать осторожно, да и в целом необходимость в них возникает крайне редко. В нашем случае, если бы мы указали этот параметр при создании ограничения, то при удалении файла из таблицы files, были бы удалены все карточки фонограм из таблицы phonograms, которые ссылались на этот файл. Нам такое поведение не нужно, при удалении файла, карточкам нужно явно присвоит null-значение.
Прочие ограничения (UNIQUE, DEFAULT, CHECK)¶
При помощи ограничения UNIQUE можно указать, что значения для каждого строки в данном поле или наборе полей должно быть уникальным.
Например, такое ограничение мы можем добавить к полю file_name в таблице files - нам не нужно добавлять несколько раз одни и те же файлы. К тому же, если вдруг у нас появятся записи с одним и там же file_name, это может привести к проблеме.
Добавим ограничение к таблице files:
ALTER TABLE files ADD CONSTRAINT UQ_files_file_name UNIQUE(file_name)
Аналогично добавим ограничение к таблице attributes:
ALTER TABLE attributes ADD CONSTRAINT UQ_attributes_attribute_name UNIQUE(attribute_name)
Принцип именования ограничения аналогичен предыдущим ограничениям, которые мы уже использовали: сначала идет префикс "UQ_", затем имя таблицы и после - имя поля, на которое мы накладываем ограничение. Соответственно, если уникальным должно быть не поле, а комбинация полей, то перечисляем их через запятую:
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения UNIQUE(поле1, поле2, ...)
При помощи ограничения DEFAULT можно указать значение по умолчанию, которое будет подставляться в случае, если при вставке новой записи данное поле не будет перечислено в списке полей команды INSERT.
В нашем примере, когда мы добавляем новый файл в базу данных или создаем новую карточку элемента, вручную указывать дату добавления нет смысла, да и это не совсем правильно. Добавим ограничения, которые автоматически подставляют текущую дату и время в нужные поля при добавлении записи.
ALTER TABLE phonograms ADD CONSTRAINT DF_phonograms_add_date DEFAULT SYSDATETIME() FOR add_date
Синтаксис простой: для таблицы phonograms добавим ограничение в виде значения по умолчанию для поля add_date. Если при добавлении данных пользователь не указал его, автоматически будет подставлены текущие системные дата и время.
Мы добились этого с помощью инструкции SYSDATETIME() - эта функция как раз и возвращает текущие дату и время.
Аналогично добавим ограничение для таблицы files:
ALTER TABLE files ADD CONSTRAINT DF_files_add_date DEFAULT SYSDATETIME() FOR add_date
При помощи ограничения CHECK можно осуществить проверку вставляемых в поле значений. Оно необходимо, если поле таблицы не ссылается на какую-либо таблицу справочник, но нам необходимо обезопасить данные, чтобы туда не попали аномальные значения.
В нашем примере такое ограничение можно добавить для поля type в таблице phonograms, чтобы случайно не указать несуществующий в системе тип материала:
ALTER TABLE phonograms ADD CONSTRAINT CK_phonograms_type CHECK(type BETWEEN 1 AND 5)
Теперь, при попытке добавить карточку фонограмы с типом 6 (не существующий тип), запрос завершится с ошибкой.
Правильное именование ограничений упростит понимание структуры таблиц и связей при исследовании базы данных. Все эти ограничения можно указать непосредственно во время создания таблицы. Здесь важно соблюдать порядок создания таблиц, т.к. ссылочное ограничение не может быть создано, если не существует таблица, на которую мы пытаемся сослаться.
Удалим все наши таблицы и создадим их повторно уже сразу указав необходимые нам ограничения.
attributes:
CREATE TABLE attributes( attribute_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_attributes PRIMARY KEY, attribute_name nvarchar(250) NOT NULL CONSTRAINT UQ_attributes_attribute_name UNIQUE )
files:
CREATE TABLE files( file_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_files PRIMARY KEY, file_name nvarchar(250) NOT NULL CONSTRAINT UQ_files_file_name UNIQUE, duration_size int NOT NULL, sample_size int NOT NULL, frequency int NOT NULL, add_date datetime2(3) NOT NULL CONSTRAINT DF_files_add_date DEFAULT SYSDATETIME() )
phonograms:
CREATE TABLE phonograms( phonogram_id int IDENTITY(1,1) NOT NULL CONSTRAINT PK_phonograms PRIMARY KEY, phonogram_name nvarchar(250) NOT NULL, type int NOT NULL CONSTRAINT CK_phonograms_type CHECK(type BETWEEN 1 AND 5), file_id int CONSTRAINT FK_phonograms_file_id FOREIGN KEY REFERENCES files(file_id), add_date datetime2(3) NOT NULL CONSTRAINT DF_phonograms_add_date DEFAULT SYSDATETIME() )
Создание индексов¶
Подробнее о том, что такое индексы, можно прочитать здесь: Теоретическая_часть_Введение_в_язык_SQL_и_основы_реляционных_баз_данных
Вы уже могли заметить, что при создании ограничений PRIMARY KEY и UNIQUE, система автоматически создала индексы с такими же названиями. По умолчанию индекс для первичного ключа создается как кластеризованный (CLUSTERED), а для всех остальных индексов как некластеризованный (NONCLUSTERED). Таблица может иметь только один кластеризованный индекс. Кластеризованный - означает, что записи таблицы будут сортироваться по этому индексу, т.с. главный индекс таблицы.
В рамках данного материала мы не будем рассматривать такие вопросы как выбор индексов для таблицы, оптимизация запросов и прочее. Кому интересно - ищите дополнительную информацию в интернете. Здесь мы только рассмотрим синтаксис DDL, который позволяет создавать индексы для таблицы на случай, если появится такая необходимость.
Во-первых, если необходимо использовать кластеризованный индекс не в первичном ключе, а для другого индекса, то это необходимо указать при создании первичного ключа, указав опцию NONCLUSTERED:
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения PRIMARY KEY NONCLUSTERED(поле1, поле2, ...)
Аналогично, если мы хотим создать ключ с кластеризованным индексом, при его создании необходимо указать опцию CLUSTERED, например:
ALTER TABLE имя_таблицы ADD CONSTRAINT имя_ограничения UNIQUE CLUSTERED(поле1, поле2, ...)
Так же можно самостоятельно создать индексы по полю или комбинации полей:
CREATE INDEX имя_индекса ON имя_таблицы(поле1, поле2, ...)
Принцип именования индекса аналогичен принципам именования ограничений: "IDX_" - префикс, затем идет имя таблицы и после имя поля, по которому создается индекс.
Удалить индекс можно командой:
DROP INDEX имя_индекса ON имя_таблицы