Может ли пустой столбец быть частью первичного ключа?

15

Я занимаюсь разработкой базы данных SQL Server 2012, и у меня есть вопрос об отношении «один к одному».

У меня есть две таблицы, Codesи HelperCodes. Код может иметь ноль или один вспомогательный код. Это скрипт sql для создания этих двух таблиц и их взаимосвязей:

CREATE TABLE [dbo].[Code]
(
    [Id] NVARCHAR(20) NOT NULL, 
    [Level] TINYINT NOT NULL, 
    [CommissioningFlag] TINYINT NOT NULL, 
    [SentToRanger] BIT NOT NULL DEFAULT 0, 
    [LastChange] NVARCHAR(50) NOT NULL, 
    [UserName] NVARCHAR(50) NOT NULL, 
    [Source] NVARCHAR(50) NOT NULL, 
    [Reason] NVARCHAR(200) NULL, 
    [HelperCodeId] NVARCHAR(20) NULL,
    CONSTRAINT [PK_Code] PRIMARY KEY CLUSTERED
    (
        [Id] ASC
    ),
    CONSTRAINT [FK_Code_LevelConfiguration]
       FOREIGN KEY ([Level])
        REFERENCES [dbo].[LevelConfiguration] ([Level]),
    CONSTRAINT [FK_Code_HelperCode]
       FOREIGN KEY ([HelperCodeId])
        REFERENCES [dbo].[HelperCode] ([HelperCodeId])
)

CREATE TABLE [dbo].[HelperCode]
(
    [HelperCodeId] NVARCHAR(20) NOT NULL, 
    [Level] TINYINT NOT NULL, 
    [CommissioningFlag] TINYINT NOT NULL, 
    [LastChange] NVARCHAR(50) NOT NULL,
    CONSTRAINT [PK_HelperCode] PRIMARY KEY CLUSTERED
    (
        [HelperCodeId] ASC
    ),
    CONSTRAINT [FK_HelperCode_LevelConfiguration]
       FOREIGN KEY ([Level])
        REFERENCES [dbo].[LevelConfiguration] ([Level])
)

Это верно?

Код и HelperCode - это разные сущности. HelperCode может быть использован (ни один Код не ссылается на него), или используется (только один Код ссылается на него).

Возможно Code.HelperCodeId должен быть частью первичного ключа таблицы кодов. Но я не уверен, что нулевой столбец может быть частью первичного. Делая это, я хочу, чтобы два или более кодов ссылались на один и тот же HelperCode.

VansFannel
источник
1
Почему вы хотите HelperCodeIdбыть частью ПК? Это случайно не потому, что вы хотите запретить двум или более кодам ссылаться на один и тот же HelperCode?
Андрей М
Да, я хочу, чтобы два или более кода ссылались на один и тот же HelperCode. Другой вариант - установить HelperCodeIdстолбец как уникальный.
VansFannel
@ypercube Не могли бы вы добавить полное предложение SQL в качестве ответа? Я не очень часто работаю с SQL и не знаю, как это сделать. Благодарю.
VansFannel
Концептуально, инженеры СУБД не могли бы разрешить NULL в первичных ключах, не идя против всей модели реляционных данных. И реляционная модель является частью того, что делает реляционные базы данных такими полезными. Вы можете интересоваться или не интересоваться этим аспектом, но важно указать на будущих посетителей.
Уолтер Митти,
@WalterMitty Я никогда не понимал, почему наличие нулевого значения в PK уничтожит значение, которое приносит СУБД. Я слышал это много раз. Можете ли вы уточнить?
USR

Ответы:

24

Чтобы ответить на вопрос в заголовке, нет, все основные столбцы должны быть NOT NULL.

Но не изменяя структуру таблиц, вы можете добавить отфильтрованный индекс к Code (HelperCodeId)столбцу:

CREATE UNIQUE INDEX 
    FUX_Code_HelperCodeId
ON dbo.Code 
    (HelperCodeId) 
WHERE 
    HelperCodeId IS NOT NULL ;

Фильтр ( WHERE HelperCodeId IS NOT NULL) необходим из-за того, как SQL-Server обрабатывает пустые значения в уникальных ограничениях и уникальных индексах Без фильтра SQL-сервер не позволил бы более одной строки с NULLin HelperCodeId.


Альтернативным дизайном будет удаление HelperCodeIdиз Codeи добавление третьей таблицы, в которой будут храниться отношения Code- HelperCode. Взаимосвязь между двумя объектами выглядит как «ноль или один-один-ноль-один» (оба кода могут не иметь HelperCode, а HelperCode может не использоваться никаким кодом):

CREATE TABLE [dbo].[Code]
(
    [Id] NVARCHAR(20) NOT NULL, 
    [Level] TINYINT NOT NULL, 
    [CommissioningFlag] TINYINT NOT NULL, 
    [SentToRanger] BIT NOT NULL DEFAULT 0, 
    [LastChange] NVARCHAR(50) NOT NULL, 
    [UserName] NVARCHAR(50) NOT NULL, 
    [Source] NVARCHAR(50) NOT NULL, 
    [Reason] NVARCHAR(200) NULL, 
    -- 
    -- removed:   [HelperCodeId] NVARCHAR(20) NULL,
    -- 
    CONSTRAINT [PK_Code] PRIMARY KEY CLUSTERED
    (
        [Id] ASC
    ),
    CONSTRAINT [FK_Code_LevelConfiguration]
       FOREIGN KEY ([Level])
        REFERENCES [dbo].[LevelConfiguration] ([Level]),
) ;

HelperCode остается неизменной:

CREATE TABLE [dbo].[HelperCode]
(
    [HelperCodeId] NVARCHAR(20) NOT NULL, 
    [Level] TINYINT NOT NULL, 
    [CommissioningFlag] TINYINT NOT NULL, 
    [LastChange] NVARCHAR(50) NOT NULL,
    CONSTRAINT [PK_HelperCode] PRIMARY KEY CLUSTERED
    (
        [HelperCodeId] ASC
    ),
    CONSTRAINT [FK_HelperCode_LevelConfiguration]
       FOREIGN KEY ([Level])
        REFERENCES [dbo].[LevelConfiguration] ([Level])
) ;

Дополнительная таблица будет иметь два UNIQUEограничения (или одно основное и одно уникальное), чтобы гарантировать, что каждый код связан с (максимум) одним HelperCode, а каждый код HelperCode связан с (максимум) одним кодом. Оба столбца будут NOT NULL:

CREATE TABLE [dbo].[Code_HelperCode]
(
    [CodeId] NVARCHAR(20) NOT NULL, 
    [HelperCodeId] NVARCHAR(20) NOT NULL, 
    CONSTRAINT [UQ_Code_HelperCode_CodeId]
       UNIQUE (CodeId),
    CONSTRAINT [UQ_Code_HelperCode_HelperCodeId]
       UNIQUE (HelperCodeId),
    CONSTRAINT [FK_HelperCode_Code]
       FOREIGN KEY ([CodeId])
        REFERENCES [dbo].[Code] ([Id]),
    CONSTRAINT [FK_Code_HelperCode]
       FOREIGN KEY ([HelperCodeId])
        REFERENCES [dbo].[HelperCode] ([HelperCodeId])
) ;
ypercubeᵀᴹ
источник
Спасибо, вы можете изменить дизайн, если хотите. Я мог бы многому научиться.
VansFannel
Спасибо за ваш дизайн. Я не добавил новую таблицу, потому что думал, что эти таблицы используются только в отношениях «многие ко многим».
VansFannel
0

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

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

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

Кевин
источник