Cualquier forma de índice único de 16 columnas como máximo

8

De acuerdo con la CREATE INDEXdocumentación:

Se pueden combinar hasta 16 columnas en una sola clave de índice compuesta.

Tenemos una tabla con ~ 18 columnas que deben formar una combinación única. Esta tabla no es sensible al rendimiento: raramente actualizamos valores / insert registros. Solo necesitamos asegurarnos de evitar la duplicación de nuestros registros ... y pensamos que podríamos imponer una restricción de unicidad simple.

¿Algunas ideas? Estoy abierto a evitar el índice / restricción único por completo si hay una mejor manera.

Nick B
fuente
44
Esa es una mesa.
@ Joe: no es inusual en algunas circunstancias cuando ha combinado subtipos similares en uno. En mi caso, se requiere una clave de 15 columnas en lugar de más de 50 tablas diferentes. Una decisión de implementación ...
gbn
Si bien lo que está preguntando es posible, no estoy tan seguro de que sea sabio. No estás siguiendo el camino trillado. Como tal, te esperan sorpresas. Es más probable que aprenda sobre sus propios errores que sobre los de otros. A la larga, podría ser más fácil probar un enfoque más convencional. Si publica más detalles, podríamos ayudarlo con la implementación.
AK
Sé que ha pasado un tiempo, pero ¿qué le impidió simplemente usar una columna de identidad GUID?
Robert Harvey

Respuestas:

14

Agregue una columna calculada persistente que combine las 18 claves, luego cree un índice único en la columna calculada:

alter table t add all_keys as c1+c2+c3+...+c18 persisted;
create unique index i18 on t (all_keys);

Consulte Crear índices en columnas calculadas .

Otro enfoque es crear una vista indizada:

create view v 
with schemabinding
as select c1+c2+c3+...+c18 as all_keys
from dbo.t;

create unique clustered index c18 on v(all_keys);

Consulte Crear vistas indizadas .

Ambos enfoques permiten un agregado clave parcial: agregado c1 + c2 + c3 como k1, c4 + c5 + c6 como k2, etc. luego indexar / crear una vista indexada en (k1, k2, ...). Thia podría ser beneficioso para los escaneos de rango (el índice se puede usar para buscar en c1 + c2 + c3.

Por supuesto, todas las +operaciones en mi ejemplo son agregación de cadenas, el operador real a usar depende de los tipos de todas esas columnas (es decir, puede que tenga que usar conversiones explícitas).

PD. Como las restricciones únicas se aplican mediante un índice único, cualquier restricción en los índices únicos se aplicará también a las restricciones únicas:

create table t (
    c1 char(3), c2 char(3), c3 char(3), c4 char(3),
    c5 char(3), c6 char(3), c7 char(3), c8 char(3),
    c9 char(3), c10 char(3), c11 char(3), c12 char(3),
    c13 char(3), c14 char(3), c15 char(3), c16 char(3),
    c17 char(3), c18 char(3), c19 char(3), c20 char(3),
    constraint unq unique
      (c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15,c16,c17,c18));
go  


Msg 1904, Level 16, State 1, Line 3
The index '' on table 't' has 18 column names in index key list. 
The maximum limit for index or statistics key column list is 16.
Msg 1750, Level 16, State 0, Line 3
Could not create constraint. See previous errors.

Sin embargo, crear la restricción en una columna computada persistente funciona:

create table t (
    c1 char(3), c2 char(3), c3 char(3), c4 char(3),
    c5 char(3), c6 char(3), c7 char(3), c8 char(3),
    c9 char(3), c10 char(3), c11 char(3), c12 char(3),
    c13 char(3), c14 char(3), c15 char(3), c16 char(3),
    c17 char(3), c18 char(3), c19 char(3), c20 char(3),
    all_c as 
        c1+c2+c3+c4+c5+c6+c7+c8+c9+c10+c11+
        c12+c13+c14+c15+c16+c17+c18 
        persisted
        constraint unq unique (all_c));
go  

Obviamente, la columna persistente consume el espacio en el disco, por lo que el enfoque puede ser malo para una tabla muy grande. El enfoque de vista indexada no tiene este problema, solo consume el espacio para el índice , no el espacio para la columna y el índice calculados .

Remus Rusanu
fuente
1
Observe el límite de clave de índice de 900 bytes, por supuesto ...
gbn
1
@gbn Sí, y es por eso que terminé con la función HashBytes como lo sugirió RBarryYoung. Sin embargo, acepté esta respuesta porque proporcionó más una explicación y exploración de diferentes métodos. (es decir, aprendí mucho aquí)
Nick B
13

Creo que sería mucho mejor poner su verificación de índice única en una columna calculada que se genera usando HASHBYTES('MD5', ...)la combinación de sus 18 columnas.

RBarryYoung
fuente
2

Encontré este problema y mi DBA senior sugirió usar una función de verificación de singularidad. Mis inserciones son relativamente pequeñas e infrecuentes (~ 1000 filas, insertadas al comienzo de cada mes) y mi única preocupación es hacer cumplir la unicidad.

CREATE FUNCTION dbo.fn_UQ_table1 ()  
RETURNS BIT

AS
BEGIN
      DECLARE @ResultBit BIT = 1

      IF EXISTS(
      SELECT COUNT(*)
      FROM [table1]
      GROUP BY [c1],[c2],[c3],[c4],[c5],[c6],
            [c7],[c8],[c9],[c10],[c11],[c12],
            [c13],[c14],[c15],[c16]
      HAVING COUNT(*) > 1)
      SELECT @ResultBit = 0

      RETURN      @ResultBit

END

SELECT dbo.fn_UQ_table1()

ALTER TABLE [table1]  
WITH NOCHECK ADD  
CONSTRAINT [CK_UQ] CHECK  (([dbo].[fn_UQ_table1]()=1))

@RBarryYoung, todavía no tengo el representante para comentar, pero tuve problemas con la solución HASHBYTES porque uno de mis tipos de datos era una fecha y cometí el error de novato (?) De no proporcionar el argumento de estilo opcional a mi CONVERTIR la función al convertir a varchar. Sin el estilo, obtiene el siguiente error cuando intenta agregar las PERSISTED UNIQUE NONCLUSTEREDrestricciones:

"column 'key_hash' in table 'table1' cannot be persisted because 
the column is non-deterministic."
Apoxy
fuente
0

Puede combinar algunos de los valores para crear un nuevo valor único y almacenarlo además de los datos actuales.

Cree una función definida por el usuario para crear los nuevos valores y un desencadenante para llenar el campo cuando se agreguen datos, entonces no tendrá mucha más sobrecarga en el mantenimiento del campo.

La combinación de dos o tres de sus campos lo colocaría por debajo del límite de 16.

Tony
fuente
-1 No estoy de acuerdo con la idea de desnormalizar la tabla en aras de reducir el número de columnas.
Matt M
@Matt M - ¿Me interesa saber por qué rechazaste mi respuesta cuando no es muy diferente de la primera sugerencia en la respuesta aceptada a esta pregunta? También me gustaría saber por qué no estás de acuerdo, ¿cuál sería tu solución?
Tony
En realidad, su sugerencia es, de hecho, diferente de la solución aceptada. Usted aboga por combinar columnas, mientras que la solución aceptada aboga por crear una nueva columna que contenga los valores combinados. Su solución podría presentar problemas de rendimiento a través de consultas demasiado complejas para dividir datos útiles de sus columnas combinadas. Personalmente, recomendaría la solución presentada por RBarryYoung que utiliza una columna calculada HashBytes PERSISTED combinada colocada en un índice único. Por el contrario, voté por su solución.
Matt M
@Matt M - Gracias por su explicación, pero dije "... cree un nuevo valor único y almacénelo además de los datos actuales". Tenía la intención de que la nueva columna clave fuera un nuevo campo que complementara los datos existentes y no los reemplazara. Estoy de acuerdo en que el uso de un campo calculado persistente es mejor que mi sugerencia de un UDF pero, en espíritu, mi solución fue la misma.
Tony
Parece que leí mal su solución, y me disculpo por eso. Dicho esto, combinar algunas de las columnas no es una solución tan buena, en mi opinión, como la solución de HashBytes dada. Retraeré mi -1. Nuevamente, me disculpo por mi comprensión de lectura.
Matt M
0

Podrías ir con un disparador para insert/ update. Haga una agrupación selecta por sus columnas con una cláusula de having count(*) > 1. Si eso vuelve no vacío, retroceda.

Ben Thul
fuente
0

Esto es lo que haría. Crearía un disparador DESPUÉS para INSERTAR, ACTUALIZAR que hace una ROW_NUMBER ()función y particiones por las 18 columnas únicas. Si el número máximo de filas es mayor que uno, entonces a ROLLBACK.

Thomas Stringer
fuente