2010-02-05 19 views
11

Supongamos que la tabla con dos columnas:de SQL Server en el contexto de otra columna

ParentEntityId int foreign key 
Number int 

ParentEntityId es una clave externa a otra tabla.

Number es un local identidad, es decir, es único dentro del único ParentEntityId.

La unicidad se logra fácilmente mediante una tecla única en estas dos columnas.

¿Cómo hacer que Number aumente automáticamente en el contexto de ParentEntityId en la inserción?


Addendum 1

Para aclarar el problema, aquí es un resumen.

ParentEntity tiene múltiples ChildEntity, y cada ChiildEntity debe tener un único incremento Number en el contexto de su ParentEntity.


Addendum 2

Treat ParentEntity como Cliente.

Tratar ChildEntity como Pedido.

Por lo tanto, los pedidos para cada cliente deben ser numerados 1, 2, 3 y así sucesivamente.

+0

Por favor, aclarar 'en el contexto de que es la entidad matriz' en términos de sql ... El único contexto es el de la tabla un registro reside en se logran a través de Relaciones limitaciones FK. y ya tienes uno, si de hecho 1: M es lo que buscas. ¿Cuál es el propósito del número 'sensible al contexto'? –

+0

@Sky: por favor, eche un vistazo a la Adenda 2. Espero que aclare la intención. –

Respuesta

9

Bueno, no hay soporte nativo para este tipo de columna, pero se puede aplicar usando un disparador:

CREATE TRIGGER tr_MyTable_Number 
ON MyTable 
INSTEAD OF INSERT 
AS 

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRAN; 

WITH MaxNumbers_CTE AS 
(
    SELECT ParentEntityID, MAX(Number) AS Number 
    FROM MyTable 
    WHERE ParentEntityID IN (SELECT ParentEntityID FROM inserted) 
) 
INSERT MyTable (ParentEntityID, Number) 
    SELECT 
     i.ParentEntityID, 
     ROW_NUMBER() OVER 
     (
      PARTITION BY i.ParentEntityID 
      ORDER BY (SELECT 1) 
     ) + ISNULL(m.Number, 0) AS Number 
    FROM inserted i 
    LEFT JOIN MaxNumbers_CTE m 
     ON m.ParentEntityID = i.ParentEntityID 

COMMIT 

No probado, pero estoy bastante seguro de que funcionará. Si tiene una clave principal, también puede implementar esto como un activador AFTER (No me gusta usar activadores INSTEAD OF, son más difíciles de entender cuando necesita modificarlos 6 meses después).

Sólo para explicar lo que está pasando aquí:

  • SERIALIZABLE es el modo de aislamiento más estricto; garantiza que solo una transacción de base de datos a la vez pueda ejecutar estas declaraciones, que necesitamos para garantizar la integridad de esta "secuencia". Tenga en cuenta que esto promueve de forma irreversible toda la transacción, por lo que no querrá usar esto dentro de una transacción de larga ejecución.

  • El CTE recoge el número más alto que ya se utiliza para cada ID de los padres;

  • ROW_NUMBER genera una secuencia única para cada ID principal (PARTITION BY) a partir del número 1; agregamos esto al máximo anterior si hay uno para obtener la nueva secuencia.

probablemente debería mencionar también que si sólo se necesita siempre para insertar una nueva entidad niño a la vez, es mejor simplemente canalizar esas operaciones a través de un procedimiento almacenado en lugar de utilizar un disparador - Vas Definitivamente obtener un mejor rendimiento de ella. Así es como se hace actualmente con las columnas hierarchyid en SQL '08.

+0

@Aaronaught: sí, voy a tener una clave principal de identidad. ¿Cómo se simplificaría este código en el caso del desencadenante 'AFTER'? Para ser sincero, no entiendo completamente tu código de activación 'EN LUGAR DE'. –

+0

@Anton: Yo no diría lo que es un 'gatillo AFTER' simplifica, exactamente - sólo cambia el' INSERT' en un '' UPDATE' con un JOIN'. Pero los activadores 'EN LUGAR DE 'tienen varias limitaciones molestas, es decir, solo se puede tener una por tabla, y siempre me resulta extraño volver a insertar los datos' insertados '. Pero así es como funciona. Espero que las anotaciones que agregué te ayuden a entenderlo mejor. – Aaronaught

+0

Gracias, @Aaronaught. Voy a usar el disparador como sugiriste. Aceptando –

0

Esto resuelve la cuestión tal como lo entiendo: :-)

DECLARE @foreignKey int 
SET @foreignKey = 1 -- or however you get this 

INSERT Tbl (ParentEntityId, Number) 
VALUES (@foreignKey, ISNULL((SELECT MAX(Number) FROM Tbl WHERE ParentEntityId = @foreignKey), 0) + 1) 
+0

¿Este enfoque garantiza la seguridad de un hilo? Quiero decir, ¿no aparecerán dos registros con el mismo valor de "Número"? Probablemente, se debe hacer en un disparador ... –

+0

@roufamatic: Anton está preguntando si es posible incrementar el número ** ** automáticamente. Llamar 'SELECT MAX (...)' no se ve como una generación automática de números de acuerdo a la forma en que he entendido la pregunta – Sung

+0

@Anton - podemos rodear esto con un AppLock para la seguridad de los subprocesos. @Song Meister: acepto estar en desacuerdo con la definición de "automático". :-) Esto actualiza automáticamente el campo Número cuando se llama. – roufamatic

2

necesidad añade OUTPUT cláusula para dar lugar para LINQ to SQL сompatibility.

Por ejemplo:

INSERT MyTable (ParentEntityID, Number) 
OUTPUT inserted.* 
SELECT 
    i.ParentEntityID, 
    ROW_NUMBER() OVER 
    (
    PARTITION BY i.ParentEntityID 
    ORDER BY (SELECT 1) 
) + ISNULL(m.Number, 0) AS Number 
FROM inserted i 
LEFT JOIN MaxNumbers_CTE m 
ON m.ParentEntityID = i.ParentEntityID 
Cuestiones relacionadas