Nous menons un projet de migration et cherchons à remplacer la plupart des index Rowstore par des index Clustered Columnstore pour les grands Data Warehouse. Nous ajoutons un index unique sur la colonne d'identité.
Quelqu'un a-t-il un script à modifier dans toutes les 100 tables et à remplacer l'index cluster par clé primaire par Columnstore Index?
Tester pour voir si l'index columnstore aidera à optimiser les performances si nous migrons.
* Au fait, les colonnes d'identité sont-elles recommandées dans Columnstore? Besoin d'un moyen d'identifier chaque ligne (parfois les colonnes d'identité peuvent être réinitialisées, donc en plaçant une contrainte unique, Ou faut-il utiliser Guids?) Si ce n'est pas l'identité, n'hésitez pas à supprimer la colonne d'identité avec Guid ou autre chose.
Courant:
CREATE TABLE [dbo].[Fact_SalesTransaction]
(
[FactSalesTransactionId] INT IDENTITY (1, 1) NOT NULL,
[DimCustomerId] INT NOT NULL,
[DimSellerId] INT NOT NULL,
[SalesDatetime] DATETIME NULL,
[DimSalesDateId] INT NULL,
[SalesAmount] DECIMAL (28, 2) NULL,
[ETLCreateDate] DATETIME NULL,
);
CREATE CLUSTERED COLUMNSTORE INDEX ccx_Fact_SalesTransaction ON Fact_SalesTransaction;
CREATE UNIQUE INDEX unx_FactSalesTransactionId ON dbo.Fact_SalesTransaction (FactSalesTransactionId);
Attendu:
CREATE TABLE [dbo].[Fact_SalesTransaction]
(
[FactSalesTransactionId] INT IDENTITY (1, 1) NOT NULL,
[DimCustomerId] INT NOT NULL,
[DimSellerId] INT NOT NULL,
[SalesDatetime] DATETIME NULL,
[DimSalesDateId] INT NULL,
[SalesAmount] DECIMAL (28, 2) NULL,
[ETLCreateDate] DATETIME NULL,
CONSTRAINT [pk_SalesTransactionId] PRIMARY KEY CLUSTERED ([SalesTransactionId] ASC)
);
Nous voulons uniquement utiliser T-SQL pour effectuer cela sur une base de données existante.
Ressource utile en commentaire: générer des scripts de création SQL pour des tables existantes avec une requête
3 Réponses :
Devart a répondu à cette question en demandant comment générer un script pour une table. J'ai modifié son code pour omettre la partie clé primaire et l'ai remplacée par des scripts de création de clé unique et de columnstore suivant son modèle général. J'ai également dû utiliser la réponse de marc_s à cette question sur la façon de savoir si la clé primaire d'une table est groupée ou non. J'ai mis tout cela dans une procédure stockée appelée #scriptTable. Voici ce qui est ci-dessous:
declare @rowId int = 1;
declare @table nvarchar(max);
declare @sql nvarchar(max);
while @rowId <= (select max(rowId) from @tables) begin
select @table = name from @tables where rowId = @rowId;
exec #scriptTable @table, @sql output;
print (@sql); -- turn 'print' into 'exec' or otherwise use sp_executeSql
set @rowId += 1;
end
Vous pouvez utiliser #scriptTable comme ceci:
declare @tables table (
rowId int identity(1,1),
name nvarchar(max)
);
insert @tables
select schema_name(schema_id) + '.' + name
from sys.tables
where type_desc = 'user_table'
Remplacez «print» par «exec» ou utilisez sp_executeSql lorsque vous êtes prêt.
Pour l'utiliser sur toutes les tables, capturez d'abord les tables que vous souhaitez modifier:
declare @sql nvarchar(max); exec #scriptTable 'dbo.Fact_SalesTransaction', @sql output; print (@sql);
Vous êtes maintenant prêt à boucler les tables et à appliquer #scriptTable:
create procedure #scriptTable
@table_name sysname,
@sql nvarchar(max) output
as
DECLARE
@object_name SYSNAME
, @object_id INT
SELECT
@object_name = '[' + s.name + '].[' + o.name + ']'
, @object_id = o.[object_id]
FROM sys.objects o WITH (NOWAIT)
JOIN sys.schemas s WITH (NOWAIT) ON o.[schema_id] = s.[schema_id]
WHERE s.name + '.' + o.name = @table_name
AND o.[type] = 'U'
AND o.is_ms_shipped = 0
;WITH index_column AS
(
SELECT
ic.[object_id]
, ic.index_id
, ic.is_descending_key
, ic.is_included_column
, c.name
FROM sys.index_columns ic WITH (NOWAIT)
JOIN sys.columns c WITH (NOWAIT) ON ic.[object_id] = c.[object_id] AND ic.column_id = c.column_id
WHERE ic.[object_id] = @object_id
),
fk_columns AS
(
SELECT
k.constraint_object_id
, cname = c.name
, rcname = rc.name
FROM sys.foreign_key_columns k WITH (NOWAIT)
JOIN sys.columns rc WITH (NOWAIT) ON rc.[object_id] = k.referenced_object_id AND rc.column_id = k.referenced_column_id
JOIN sys.columns c WITH (NOWAIT) ON c.[object_id] = k.parent_object_id AND c.column_id = k.parent_column_id
WHERE k.parent_object_id = @object_id
)
SELECT @sql = 'CREATE TABLE ' + @object_name + CHAR(13) + '(' + CHAR(13) + STUFF((
SELECT CHAR(9) + ', [' + c.name + '] ' +
CASE WHEN c.is_computed = 1
THEN 'AS ' + cc.[definition]
ELSE UPPER(tp.name) +
CASE WHEN tp.name IN ('varchar', 'char', 'varbinary', 'binary', 'text')
THEN '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length AS VARCHAR(5)) END + ')'
WHEN tp.name IN ('nvarchar', 'nchar', 'ntext')
THEN '(' + CASE WHEN c.max_length = -1 THEN 'MAX' ELSE CAST(c.max_length / 2 AS VARCHAR(5)) END + ')'
WHEN tp.name IN ('datetime2', 'time2', 'datetimeoffset')
THEN '(' + CAST(c.scale AS VARCHAR(5)) + ')'
WHEN tp.name = 'decimal'
THEN '(' + CAST(c.[precision] AS VARCHAR(5)) + ',' + CAST(c.scale AS VARCHAR(5)) + ')'
ELSE ''
END +
CASE WHEN c.collation_name IS NOT NULL THEN ' COLLATE ' + c.collation_name ELSE '' END +
CASE WHEN c.is_nullable = 1 THEN ' NULL' ELSE ' NOT NULL' END +
CASE WHEN dc.[definition] IS NOT NULL THEN ' DEFAULT' + dc.[definition] ELSE '' END +
CASE WHEN ic.is_identity = 1 THEN ' IDENTITY(' + CAST(ISNULL(ic.seed_value, '0') AS CHAR(1)) + ',' + CAST(ISNULL(ic.increment_value, '1') AS CHAR(1)) + ')' ELSE '' END
END + CHAR(13)
FROM sys.columns c WITH (NOWAIT)
JOIN sys.types tp WITH (NOWAIT) ON c.user_type_id = tp.user_type_id
LEFT JOIN sys.computed_columns cc WITH (NOWAIT) ON c.[object_id] = cc.[object_id] AND c.column_id = cc.column_id
LEFT JOIN sys.default_constraints dc WITH (NOWAIT) ON c.default_object_id != 0 AND c.[object_id] = dc.parent_object_id AND c.column_id = dc.parent_column_id
LEFT JOIN sys.identity_columns ic WITH (NOWAIT) ON c.is_identity = 1 AND c.[object_id] = ic.[object_id] AND c.column_id = ic.column_id
WHERE c.[object_id] = @object_id
ORDER BY c.column_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, CHAR(9) + ' ')
+ ')' + CHAR(13)
+ ISNULL((SELECT (
SELECT CHAR(13) +
'ALTER TABLE ' + @object_name + ' WITH'
+ CASE WHEN fk.is_not_trusted = 1
THEN ' NOCHECK'
ELSE ' CHECK'
END +
' ADD CONSTRAINT [' + fk.name + '] FOREIGN KEY('
+ STUFF((
SELECT ', [' + k.cname + ']'
FROM fk_columns k
WHERE k.constraint_object_id = fk.[object_id]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
+ ')' +
' REFERENCES [' + SCHEMA_NAME(ro.[schema_id]) + '].[' + ro.name + '] ('
+ STUFF((
SELECT ', [' + k.rcname + ']'
FROM fk_columns k
WHERE k.constraint_object_id = fk.[object_id]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
+ ')'
+ CASE
WHEN fk.delete_referential_action = 1 THEN ' ON DELETE CASCADE'
WHEN fk.delete_referential_action = 2 THEN ' ON DELETE SET NULL'
WHEN fk.delete_referential_action = 3 THEN ' ON DELETE SET DEFAULT'
ELSE ''
END
+ CASE
WHEN fk.update_referential_action = 1 THEN ' ON UPDATE CASCADE'
WHEN fk.update_referential_action = 2 THEN ' ON UPDATE SET NULL'
WHEN fk.update_referential_action = 3 THEN ' ON UPDATE SET DEFAULT'
ELSE ''
END
+ CHAR(13) + 'ALTER TABLE ' + @object_name + ' CHECK CONSTRAINT [' + fk.name + ']' + CHAR(13)
FROM sys.foreign_keys fk WITH (NOWAIT)
JOIN sys.objects ro WITH (NOWAIT) ON ro.[object_id] = fk.referenced_object_id
WHERE fk.parent_object_id = @object_id
FOR XML PATH(N''), TYPE).value('.', 'NVARCHAR(MAX)')), '')
+ ISNULL(((SELECT
CHAR(13) + 'CREATE' + CASE WHEN i.is_unique = 1 THEN ' UNIQUE' ELSE '' END
+ ' NONCLUSTERED INDEX [' + i.name + '] ON ' + @object_name + ' (' +
STUFF((
SELECT ', [' + c.name + ']' + CASE WHEN c.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END
FROM index_column c
WHERE c.is_included_column = 0
AND c.index_id = i.index_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')'
+ ISNULL(CHAR(13) + 'INCLUDE (' +
STUFF((
SELECT ', [' + c.name + ']'
FROM index_column c
WHERE c.is_included_column = 1
AND c.index_id = i.index_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + ')', '') + CHAR(13)
FROM sys.indexes i WITH (NOWAIT)
WHERE i.[object_id] = @object_id
AND i.is_primary_key = 0
AND i.[type] = 2
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
), '')
+ char(13)
-- Replaced "create primary key" logic in the original with what's below:
+ ISNULL(
(
select 'create clustered columnstore index [ccx_' + @table_name + '] on ' + @object_name + char(13) +
'create unique ' + convert(nvarchar(max), i.type_desc) + ' index [' + replace(k.name, 'pk_', 'unx_') + '] ' +
'on ' + @object_name + ' (' +
(
SELECT STUFF((
SELECT ', [' + ic.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
FROM index_column ic
WHERE ic.is_included_column = 0
AND ic.[object_id] = k.parent_object_id
AND ic.index_id = k.unique_index_id
FOR XML PATH(N''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) +
')' + CHAR(13)
FROM sys.key_constraints k WITH (NOWAIT)
join sys.indexes i with (nowait) on k.unique_index_id = i.index_id and k.parent_object_id = i.object_id
WHERE k.parent_object_id = @object_id
AND k.[type] = 'PK'
),
''
);
Comme précédemment, remplacez «print» par «exec» ou utilisez sp_executeSql lorsque vous êtes prêt.
Notez que ma partie peut nécessiter des modifications supplémentaires si vous avez des noms de table qui nécessiteront des crochets autour d'eux.
EDIT: Mise à jour un peu du code pour simplifier (légèrement) et travailler avec les noms de table nécessitant des crochets autour d'eux.
Voilà, cette réponse de devart renvoie à son tour à quelques excellents articles que les personnes intéressées par ce genre de choses devraient lire.
Pour la création de CCI sur toutes les tables, vous pouvez utiliser ce qui suit:
DECLARE @SQLscript nVARCHAR(1000) =
'CREATE CLUSTERED COLUMNSTORE INDEX &&& ON @@@ WITH (DROP_EXISTING = OFF,COMPRESSION_DELAY = 0)'
declare @tables table (
rowId int identity(1,1),
name nvarchar(max)
);
insert @tables
select schema_name(schema_id) + '.' + name
from sys.tables
where type_desc = 'user_table'
declare @rowId int = 1;
declare @table nvarchar(max);
declare @sql nvarchar(max);
while @rowId <= (select max(rowId) from @tables) begin
select @table = name from @tables where rowId = @rowId;
SET @SQLscript = REPLACE(REPLACE(@SQLscript, '@@@',@table) ,'&&&','cci_' + SUBSTRING(REPLACE(@table,'].[','_'),CHARINDEX('.',REPLACE(@table,'].[','_'))+1,LEN(REPLACE(@table,'].[','_'))))
BEGIN TRY
EXEC sp_executeSql @SQLscript;
END TRY
BEGIN CATCH
PRINT @SQLscript
END CATCH
SET @SQLscript = 'CREATE CLUSTERED COLUMNSTORE INDEX &&& ON @@@ WITH (DROP_EXISTING = OFF,COMPRESSION_DELAY = 0)'
set @rowId += 1;
end
La réponse de @pwilcox est juste, sauf pour la dernière partie où il crache le code de l'index du rowstore. Cet index ne doit pas être mis en cluster car la ligne ci-dessus crée déjà l'index clustered columnstore.
Réparer:
-- Replaced "create primary key" logic in the original with what's below:
+ ISNULL(
(
select 'create clustered columnstore index [ccx_' + @table_name + '] on ' + @object_name + char(13) +
'create unique index [' + replace(k.name, 'pk_', 'unx_') + '] ' +
'on ' + @object_name + ' (' +
(
SELECT STUFF((
SELECT ', [' + ic.name + '] ' + CASE WHEN ic.is_descending_key = 1 THEN 'DESC' ELSE 'ASC' END
FROM index_column ic
WHERE ic.is_included_column = 0
AND ic.[object_id] = k.parent_object_id
AND ic.index_id = k.unique_index_id
FOR XML PATH(N''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
) +
')' + CHAR(13)
FROM sys.key_constraints k WITH (NOWAIT)
join sys.indexes i with (nowait) on k.unique_index_id = i.index_id and k.parent_object_id = i.object_id
WHERE k.parent_object_id = @object_id
AND k.[type] = 'PK'
),
''
);
avez-vous essayé le blog brent ozar?
Toutes vos tables de stockage de lignes ont-elles uniquement un index clusterisé de clé primaire et aucun autre index ou contrainte de clé étrangère à conserver?
J'ai écrit quelque chose de similaire l'année dernière pour archiver beaucoup de données. J'ai interrogé sys.tables et sys.columns et ainsi de suite pour obtenir toutes les références, puis j'ai écrit SQL dynamique pour générer des scripts qui produiraient la structure vers laquelle copier. Cela fonctionnerait-il pour vous? Avez-vous essayé d'obtenir des métadonnées de sys.tables pour travailler?
J'ai trouvé quelque chose que vous pouvez utiliser comme référence pour créer un script pour créer le code de votre table à partir de métadonnées. On dirait qu'il a tout ce dont vous avez besoin ici: stackoverflow.com/questions/706664/...
Si je ne me trompe pas, vous devez modifier votre contrainte de cluster de clé primaire sous 'current' pour pointer vers FactSalesTransactionId au lieu de 'SalesTransactionId'.
cette question est toujours ouverte, si quelqu'un veut ajouter des réponses plus optimisées, n'hésitez pas, merci