¿Podría una columna nula ser parte de una clave primaria?

15

Estoy desarrollando una base de datos SQL Server 2012 y tengo una pregunta sobre una relación de uno a cero o uno.

Tengo dos mesas Codesy HelperCodes. Un código podría tener cero o un código auxiliar. Este es el script sql para crear estas dos tablas y sus relaciones:

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])
)

¿Es eso correcto?

Un código y un HelperCode son entidades diferentes. Un HelperCode puede ser usado (ningún Código lo hace referencia) o usado (solo un Código lo hace referencia).

Quizás Code.HelperCodeId debe ser parte de la clave primaria de la tabla de códigos. Pero no estoy seguro de si una columna nula podría ser parte de una primaria. Al hacer esto, quiero evitar que dos o más códigos hagan referencia al mismo HelperCode.

VansFannel
fuente
1
¿Por qué quieres HelperCodeIdser parte de la PK? ¿Es, por casualidad, porque quiere evitar que dos o más Códigos hagan referencia al mismo Código de ayuda?
Andriy M
Sí, quiero evitar que dos o más códigos hagan referencia al mismo HelperCode. Otra opción es establecer la HelperCodeIdcolumna como Única.
VansFannel
@ypercube ¿Podría agregar la oración sql completa como respuesta? No trabajo muy a menudo con SQL y no sé cómo hacerlo. Gracias.
VansFannel
Conceptualmente, los ingenieros de DBMS no podrían haber permitido NULL en claves principales sin ir en contra de todo el modelo de datos relacionales. Y el modelo relacional es parte de lo que hace que las bases de datos relacionales sean tan útiles. Puede que le interese o no este aspecto, pero es importante señalarlo para futuros visitantes.
Walter Mitty
@WalterMitty Nunca entendí por qué tener un valor nulo en una PK destruiría el valor que aporta un RDBMS. Lo he escuchado muchas veces. ¿Puedes elaborar?
Usr

Respuestas:

24

Para responder la pregunta en el título, no, todas las columnas primarias deben ser NOT NULL .

Pero sin alterar el diseño de las tablas, puede agregar un índice filtrado en la Code (HelperCodeId)columna:

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

El filtro ( WHERE HelperCodeId IS NOT NULL) es necesario debido a la forma en que SQL-Server trata los valores nulos en restricciones únicas e índices únicos. Sin el filtro, SQL-Server no permitiría más de una fila con NULLinHelperCodeId .


Un diseño alternativo se retire la HelperCodeIdde Codey añadir una tercera tabla que almacenará los Code- HelperCodelas relaciones. La relación entre las dos entidades parece ser Cero o Uno a Cero o Uno (tanto un Código puede no tener HelperCode como un HelperCode no puede ser usado por ningún Código):

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 permanece sin cambios:

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])
) ;

La tabla adicional tendrá dos UNIQUErestricciones (o una primaria y una única) para garantizar que cada Código esté relacionado con (máximo) un Código de Helper y cada Código de Helper esté relacionado con (máximo) un Código. Ambas columnas serían 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ᵀᴹ
fuente
Gracias, podría alterar el diseño si lo desea. Pude aprender mucho
VansFannel
Gracias por tu diseño. No he agregado una nueva tabla porque pensé que estas tablas se usan solo en una relación de muchos a muchos.
VansFannel
0

Intente usar una restricción única en su lugar. Supuestamente, el estándar ANSI declaró que los valores nulos como clave principal no eran válidos, pero nunca he visto el estándar y no deseo comprarlo para verificar esto.

No tener claves nulas parece ser una de esas cosas en las que los desarrolladores tienen una creencia muy dura de una forma u otra. Prefiero usarlos porque me resulta útil para las tablas de búsqueda que contienen información sobre herramientas y datos relacionados para cuadros combinados que no se han rellenado.

Me enseñaron que el valor nulo indica que una variable nunca se ha establecido y el valor vacío indica que el valor se ha establecido en el pasado. Por supuesto, esto depende del desarrollador para definir la aplicación, pero me parece absurdo permitir claves primarias vacías pero no claves primarias nulas.

Kevin
fuente