2010-09-08 7 views
8

Estoy trabajando en una aplicación heredada que se está extendiendo para ejecutarse en una configuración de varios inquilinos. La arquitectura básica toma la aplicación anterior y agrega una columna StoreID a cada tabla. Cada inquilino a continuación, ve las tablas heredadas a través de un conjunto de vistas que filtran el almacén Identificación, algo así como:¿Cómo puedo escribir un disparador INSTEAD OF INSERT que establece una columna para cualquier tabla?

create view AcmeBatWings.data as 
select * from dbo.data d where d.StoreId = 99 

Es un poco más elegante que eso, pero esto simplifica la cuestión.

Ahora, puedo crear un disparador como esto

create trigger tr_Tenant_fluff on AcmeBatWings 
instead of insert 
as 
insert into AcmeBatWings (Name, StoreId) 
select i.Name, 99 from inserted i 

Suponiendo una simple tabla con el nombre y StoreID columnas.

Mi problema es que tengo más de 100 tablas y si tuviera que seguir este patrón tendría que hacer un desencadenador especializado para cada tabla enumerando todos los campos para cada una de ellas. No solo es molesto en el corto plazo, es una pesadilla de mantenimiento, ya que cualquier cambio en la tabla debería incluir modificaciones de activación.

Entonces, ¿cómo escribir un activador que solo dice en cada inserción o actualización establecer el campo StoreId en 99 para cualquier tabla con un StoreId?

¡Gracias por ayudar a un principiante de SQL a salir!

+0

Usted mencionar que el sistema es multi-tenant; ¿Planeabas codificar con fuerza un valor de '99' para todos tus factores desencadenantes o el StoreId dependería de quién está usando el sistema? Además, ¿qué versión de SQL Server? – LittleBobbyTables

+0

¿Está diciendo que tiene vistas diferentes para cada inquilino que hace referencia a la misma tabla base y la única diferencia es que tienen un filtro de identificación diferente? –

+0

la vista y el activador no están alineados en su ejemplo. En la vista, AcmeBatWings es un nombre de esquema. En el disparador, es un nombre de objeto. ¿Cuál es? Un nombre de esquema, supongo. Eso hace una gran diferencia. –

Respuesta

7

Por lo que parece que está utilizando varios esquemas para transmitir la información de la tienda, manteniendo los nombres de objetos consistentes, w/un esquema por tienda, ¿verdad? Y algún tipo de conexión/magia del usuario para que las consultas lleguen a las vistas correctas.

Si es así, presento dos atroces hacks y una solución recomendada (para que conozca sus opciones).

Egregio truco # 1, asume el punto de vista de tienda incluyen todas las columnas de la tabla base excepto StoreID, en la misma posición ordinal como la tabla de base, y no hay otras columnas:

CREATE TRIGGER tr_Tenant_fluff ON AcmeBatWings.data 
INSTEAD OF INSERT 
AS BEGIN 
    DECLARE @StoreId INT 

    SELECT @StoreId = StoreId FROM dbo.StoreSchemas 
    WHERE StoreSchema = OBJECT_SCHEMA_NAME(@@PROCID) 

    INSERT dbo.data SELECT *, @StoreId FROM inserted 
END 

Si si alguna vez agrega una columna a la tabla base, deberá actualizar todas las vistas de tienda para incluir la columna, o los desencadenadores se romperán.

Egregio truco # 2, asume el mismo que (1), excepto que StoreID está incluido en las vistas tienda:

CREATE TRIGGER tr_Tenant_fluff ON AcmeBatWings.data 
INSTEAD OF INSERT 
AS BEGIN 
    DECLARE @StoreId INT 

    SELECT @StoreId = StoreId FROM dbo.StoreSchemas 
    WHERE StoreSchema = OBJECT_SCHEMA_NAME(@@PROCID) 

    SELECT * INTO #inserted FROM inserted 
    UPDATE #inserted SET StoreId = @StoreId 

    INSERT dbo.data SELECT * FROM #inserted 
END 

Los beneficios de truco # 2 más de truco # 1 es que se puede defina las vistas de su tienda con SELECT *, y si las tablas base cambian, simplemente recompila todas las vistas de tienda con sp_refreshview. La desventaja es que está copiando datos insertados de una tabla intermedia a otra, y actualizando la segunda tabla. Esto triplicó la sobrecarga de tu disparador INSTEAD OF INSERT, que ya era bastante caro para empezar. es decir,

  • overhead base del INSTEAD OF INSERT gatillo -> costo para poblar inserted ->x.
  • costo para poblar #inserted de inserted -> alrededor de x.
  • costo para actualizar #inserted -> acerca x
  • sobrecarga total de truco atroz # 2: alrededor de 3 x

Así lo contrario, lo mejor que puede hacer es la escritura de los factores desencadenantes fuera. Es un proceso bastante directo, una vez que esté familiarizado con las tablas del sistema, y ​​puede modificar la generación de activadores de la forma que considere más conveniente. Para el caso, también deberías escribir las vistas de la tienda.

Para empezar:

CREATE TABLE dbo.data (Name VARCHAR(10), StoreId INT) 
GO 
CREATE SCHEMA StoreA 
GO 
CREATE SCHEMA StoreB 
GO 
CREATE SCHEMA StoreC 
GO 
CREATE VIEW StoreA.data AS SELECT Name FROM dbo.data WHERE StoreId = 1 
GO 
CREATE VIEW StoreB.data AS SELECT Name FROM dbo.data WHERE StoreId = 2 
GO 
CREATE VIEW StoreC.data AS SELECT Name FROM dbo.data WHERE StoreId = 3 
GO 
CREATE TABLE dbo.StoreSchemas (StoreSchema SYSNAME UNIQUE, StoreId INT PRIMARY KEY) 
GO 
INSERT dbo.StoreSchemas VALUES ('StoreA', 1), ('StoreB', 2), ('StoreC', 3) 
GO 

DECLARE @crlf NCHAR(2) = NCHAR(13)+NCHAR(10) 
SELECT 
    N'CREATE TRIGGER tr_Tenent_fluff ON '+schema_name(v.schema_id)+N'.data'[email protected] 
+ N'INSTEAD OF INSERT'[email protected] 
+ N'AS BEGIN'[email protected] 
+ N' INSERT dbo.data (' 
+ STUFF((
    SELECT @crlf+N' , '+name FROM sys.columns tc 
    WHERE tc.object_id = t.object_id 
     AND (tc.name IN (SELECT name FROM sys.columns vc WHERE vc.object_id = v.object_id) 
     OR tc.name = N'StoreId') 
    ORDER BY tc.column_id 
    FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)') 
    ,5,1,N' ')[email protected] 
+ N' )'[email protected] 
+ N' SELECT' 
+ STUFF((
    SELECT @crlf+N' , '+name 
     + CASE WHEN name = N'StoreId' THEN ' = '+(
      SELECT CONVERT(NVARCHAR,StoreId) FROM dbo.StoreSchemas s 
      WHERE s.StoreSchema = SCHEMA_NAME(v.schema_id) 
     ) 
     ELSE '' END 
    FROM sys.columns tc 
    WHERE tc.object_id = t.object_id 
     AND (tc.name IN (SELECT name FROM sys.columns vc WHERE vc.object_id = v.object_id) 
     OR tc.name = N'StoreId') 
    ORDER BY tc.column_id 
    FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)') 
    ,5,1,N' ')[email protected] 
+ N' FROM inserted'[email protected] 
+ N'END'[email protected] 
+ N'GO'[email protected] 
FROM sys.tables t 
JOIN sys.views v 
    ON t.name = v.name 
AND t.schema_id = SCHEMA_ID('dbo') 
AND v.schema_id <> t.schema_id 
WHERE t.name = 'data' 
GO 
+0

Oooo, me gusta la parte de scripting. Me voy a la cama ahora mismo, pero lo miraré más de cerca por la mañana. – Ukko

+0

¡Este fue el boleto! ¡Gracias! – Ukko

2

En lugar de usar disparadores, ¿por qué no actualizar cada tabla haciendo StoreId NOT NULL, y dándole un valor predeterminado de 99?

Editar basado en aclaraciones

Usted podría intentar un después INSERT, desencadenador UPDATE como una alternativa a un desencadenador INSTEAD OF

create trigger tr_Tenant_fluff on AcmeBatWings 
AFTER insert, update 
as 

-- You'll need to get @StoreID here somehow 

update AcmeBatWings 
set StoreID = @StoreID 
where [Name] IN (SELECT [Name] FROM inserted) -- update based on primary key 

Si bien esto actualiza los datos que acaba de insertar o actualizado, tiene el beneficio de no romperse cuando agrega o elimina columnas de las tablas.

+0

@Martin Smith - Eso es algo de lo que me preguntaba, pero el ejemplo usó un '99' codificado, así que eso es lo que me confundió. – LittleBobbyTables

+0

Sí, eliminé ese comentario porque no estaba realmente seguro de mí mismo. –

+0

¿No estaría vinculada la identificación de la tienda a la vista, no a la tabla base? En cuyo caso, el incumplimiento de la columna en la tabla base no resolvería el problema. –

2

Por lo tanto, si Tengo este derecho, cada tienda tiene su propia identificación. La base de datos se implementa en cada tienda y la base de datos debe registrar una StoreId diferente en función de dónde se ha implementado con un mínimo esfuerzo de código. Esto es lo que propongo. Cree una tabla en la base de datos para almacenar StoreId. Crea una función para recuperar ese StoreId. Luego, cree la columna StoreId en cada tabla como una columna calculada que usa la función. Entonces, en cada implementación, el único cambio es actualizar StoreId en una tabla. Algo así como:

/* This table is updated with the unique value for each individual store */ 
create table MyStore (
    StoreId int 
) 

insert into MyStore 
    (StoreId) 
    values 
    (99)   
go 

/* This function will be used in the computed column of each table */ 
create function dbo.LookupStoreId() 
returns int 
as 
begin 
    return (select StoreId from MyStore) 
end 
go 

create table AcmeBatWings (
    Name char(10), 
    StoreId as dbo.LookupStoreId() 
) 

insert into AcmeBatWings 
    (Name) 
    values 
    ('abcde') 

select Name, StoreId from AcmeBatWings 
go 

/* Clean up after demo */ 
drop table AcmeBatWings 
drop table MyStore 
drop function dbo.LookupStoreId 
go 
+0

desventaja de esta solución: –

0

sólo tropezó sobre esta vieja pregunta: ¿

Se puede crear una restricción predeterminada para el StoreID (encontrar todas las tablas con la columna de la StoreID seleccionando sys.columns) , ya sea para un 99 fijo o una función que realiza una búsqueda en otra tabla o una función que devuelve un 99 fijo (para que solo tenga que cambiar una función en lugar de 100 restricciones cuando mueve el db a otra tienda)

Cuestiones relacionadas