2010-07-30 11 views
6

DBMS: MS SQL Server 2005, Standardrestricción únicos dentro de un grupo de registros en los que algún valor es el mismo

me gustaría hacer una restricción de tabla para tener sólo un registro tiene un valor particular dentro de una subconjunto de la tabla (donde las filas comparten un valor en una columna en particular). es posible?

Ejemplo: tengo registros en myTable que tienen una clave externa no único (fk1), y una columna de bits denominado isPrimary para marcar que éste en particular debe ser utilizado por nuestra aplicación de una lógica especial.

en abstracto, que se parece a esto:

myTable 
------------- 
pk1  (int, not null) 
name  (varchar(50), null) 
fk1  (int, not null) 
isPrimary (bit, not null) 

que quieren asegurarse de que no hay una y sólo una registro con la bandera isPrimary establecido en 1, para cada valor único de fk1.

Datos ejemplo: Esto debería ser legal:

pk1  name  fk1 isPrimary 
---- ----- ----- ---------- 
1  Bill  111 1 
2  Tom  111 0 
3  Dick  222 1 
4  Harry 222 0 

Pero esto debe no ser (más de uno, donde fk = 111):

pk1  name  fk1 isPrimary 
---- ----- ----- ---------- 
1  Bill  111 1 
2  Tom  111 1 
3  Dick  222 1 
4  Harry 222 0 

Y tampoco se debe este (ninguno donde fk = 222):

pk1  name  fk1 isPrimary 
---- ----- ----- ---------- 
1  Bill  111 1 
2  Tom  111 0 
3  Dick  222 0 
4  Harry 222 0 

Es Hay una manera de hacer esto con una restricción de tabla?

ACTUALIZACIÓN he ido con la respuesta de Martin Smith por ahora, aunque voy a estar empujando para refactor de JohnFx en una próxima versión, ya que es la mejor solución a largo plazo. Sin embargo, quería publicar mi UDF actualizado basado en la respuesta de Raze2dust, en caso de que los futuros lectores decidan que se ajusta mejor a sus necesidades.

CREATE FUNCTION [dbo].[OneIsPrimaryPerFK1](@fk1 INT, @dummyIsPrimary BIT) 
RETURNS INT 
AS 
BEGIN 
    DECLARE @retval INT; 
    DECLARE @primarySum INT; 
    SET @retval = 0; 
    DECLARE @TempTable TABLE (
    fk1 INT, 
    PrimarySum INT) 

INSERT INTO @TempTable 
    SELECT fk1, SUM(CAST(isPrimary AS INT)) AS PrimarySum 
    FROM FacAdmin 
    WHERE fk1 = @fk1 
    GROUP BY fk1; 

    SELECT @primarySum = PrimarySum FROM @TempTable; 
    IF(@primarySum=1) 
     BEGIN 
      SET @retval = 1 
     END 
    RETURN @retval 
END; 

Cambios:

  1. usados ​​@tempTable en lugar de

    TempTable como lo requiere la UDF

  2. pasaron @ fk1 como un parámetro de modo que pueda (en la memoria v escritos en el disco.) seleccione para unicidad dentro de un grupo de valores fk1.
  3. complicado tuvieron que pasar también isPrimary aunque no es necesario que la lógica de la función , de lo contrario el optimizador SQL2005 no se ejecutará la restricción de comprobación cuando isPrimary es actualizada.

Respuesta

4

El uso de UDF con restricciones de verificación puede fallar en snapshot isolation o multirow updates.

Suponiendo que todos sus valores fk1 y PK1 son actualmente (y siempre será) positivo se puede crear una columna calculada con la siguiente definición

CASE WHEN isPrimary = 1 THEN fk1 ELSE -pk1 END 

a continuación, añadir una restricción única a eso. O si este supuesto no se puede hacer entonces tal

CASE WHEN isPrimary = 0 THEN 1.0/pk1 ELSE fk1 END 
+0

Aunque la solución de JohnFx es la mejor solución a largo plazo, esta es la solución para mí en este momento. –

6

SQL 2005 no proporciona la capacidad de aplicar una cláusula Where a un índice único como SQL 2008.Sin embargo, hay algunas maneras de resolver el problema en SQL 2005:

  1. Cree una vista indizada que filtre IsPrimary = 1 y agregue un índice único a esa vista.
  2. Crea un activador en el que asegures que solo uno puede ser primario.
  3. Encapsula tu lógica en un proceso almacenado y obliga a los usuarios a pasar por el proceso almacenado para insertarlo o actualizarlo desde tu tabla.
+2

4. Crear una función almacenada que se utiliza en una restricción de comprobación – Frank

+1

Advertencia: una UDF en una restricción puede trabajar, pero también puede causar serios problemas de rendimiento si está haciendo selects. – JohnFx

+1

La penalización de rendimiento solo se aplicaría al insertar y actualizar, ¿no? No puedo imaginar una restricción de verificación disparando cuando solo estoy seleccionando datos. Si eso es cierto, estoy bien tomando el golpe, ya que la tabla no está escrita muy a menudo. –

3

Usted podría intentar crear una función y luego usando una restricción de comprobación:

CREATE FUNCTION ChkFn() 
RETURNS INT 
AS 
BEGIN 
    DECLARE @retval INT 
    DECLARE @distinct INT 
    DECLARE @top INT 
    SET @retval = 0 
    SELECT fk1 AS ForeignKey, SUM(isPrimary) AS PrimarySum 
    INTO #TempTable 
    FROM myTable 
    GROUP BY fk1 
    SELECT @distinct = COUNT(DISTINCT(PrimarySum)) FROM #TempTable 
    SELECT @top = top PrimarySum FROM #TempTable 
    IF(@distinct=1 AND @top=1) 
    BEGIN 
    @retval = 1 
    END 
    RETURN @retval 
END; 
GO 

ALTER TABLE myTable 
ADD CONSTRAINT chkFkPk CHECK (dbo.ChekFn() = 1); 
GO 

Pruébelo y quiero saber si ha funcionado. No muy elegante, sin embargo ...

+0

Esto fue muy cercano y me ayudó a ir en la dirección correcta, así que lo marcaré como aceptado. Publicaré la función completada el lunes. El primer problema fue que no puede usar #TempTables en UDF, debe usar @TempTable. El otro lo abordaré en la publicación actualizada. –

+1

Puede fallar para actualizaciones multirrutas o en aislamiento de instantáneas. –

+0

ah sí, se olvidó de UDF ... No tengo idea de las actualizaciones multirow/aislamiento de instantáneas. Thx Martin para el artículo ... Trataré de comprenderlo. Soy un novato :-) –

7

Comencé una nueva respuesta ya que destrocé al primero mal.

Parece que podría solucionar el problema volviendo a pensar el diseño de su tabla un poco para evitar que la fuerza bruta imponga una restricción para implementar su regla de negocio.

¿Qué tal si eliminamos la columna IsPrimary de MyTable y agregamos una columna PrimaryPersonID a la otra tabla que hace referencia a la persona primaria?

De esta manera, la propia estructura haría cumplir que 1 y solo 1 entrada en la tabla FK era primaria para cada persona.

+2

Tiene toda la razón, este debería ser el modelo de datos, y me gustaría poder implementar esto. Por ahora, tenemos algunas aplicaciones diferentes que apuntan a esto y no podemos cambiarlas todas para acceder al modelo actualizado en este momento. Voy a presionar para hacer este cambio en el futuro. –

Cuestiones relacionadas