2010-08-24 6 views
2

J'ai une application qui a besoin de créer un instantané des identificateurs d'enregistrement qui peuvent être persistés dans le serveur de base de données. J'ai actuellement une table qui ressemble à ceci:sélectionner en vrac dans un varbinary

CREATE TABLE Workset (
    Workset_id int, 
    Sequence int, 
    Record_id int 
) 

que j'insérer en utilisant quelque chose comme:

INSERT INTO WorkSet (Workset_id, Sequence, Record_id) 
SELECT TOP 100 
    @Workset_id, 
    ROW_NUMBER() OVER (
    ORDER BY -- this may be a complex ordering clause 
), 
    Record_id  
FROM SalesRecords 
-- WHERE some conditions are met 

Plus tard, je peux interroger pour l'identificateur d'enregistrement d'une entrée spécifique dans le jeu de travail par l'identifiant et le numéro de ligne

SELECT SalesRecords.* FROM SalesRecords 
JOIN WorkSet ON WorkSet.Record_Id = SalesRecords.Record_id 
WHERE Workset_id = @Workset_id AND Sequence = @Sequence 

le problème est que l'instantané devient grand la quantité de données que je dois écrire dans les données sous-projet se développe rapidement. Il ne serait pas rare d'avoir un workset dans les millions d'enregistrements et si chacun de ces éléments nécessite 12 octets de stockage en ligne, il s'additionne rapidement.

Il semble qu'une façon plus compacte de représenter les données consisterait à stocker simplement un ID de workset et une colonne varbinary contenant tous les identificateurs d'enregistrement dans l'ordre de séquence. Cela évite d'avoir à répéter l'identificateur de l'ensemble de travaux pour chaque ligne du même sous-projet et évite d'avoir à stocker les numéros de séquence (car ils sont impliqués par la position dans le varbinary).

Existe-t-il un moyen raisonnable de transformer ma requête INSERT en quelque chose qui génère un varbinary des identifiants d'enregistrement dans l'ordre?

Répondre

0

Quelque chose comme cela pourrait fonctionner:

-- create a generic numbers table for demonstration purposes and populate it 
IF OBJECT_ID('tempdb..#numbers') IS NOT NULL DROP TABLE #numbers; 
CREATE TABLE #numbers (number INT PRIMARY KEY); 
WITH Ten AS (SELECT * FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) a (number)) 
INSERT #numbers (number) SELECT a.number + b.number*10 + c.number*100 + d.number*1000 + e.number*10000 
FROM Ten a, Ten b, Ten c, Ten d, Ten e; 

-- concatenate the ints in a particular order 
DECLARE @varbinary VARBINARY(MAX); 
SET @varbinary = 0x; 
WITH Ids AS (SELECT number AS Id FROM #numbers) 
SELECT @varbinary += convert(BINARY(4),Id) 
FROM (
    SELECT Id, Seq = ROW_number() OVER (ORDER BY NEWID()) 
    FROM Ids WHERE Id BETWEEN 1000 AND 1099 
) a 
ORDER BY Seq; 

--split them back out by position 
;WITH Positions AS (SELECT number AS Position FROM #numbers) 
SELECT Position, Id = CONVERT(INT,substring(@varbinary,Position*4+1,4)) 
FROM Positions WHERE Position*4 < DATALENGTH(@varbinary); 

Vous pourriez vouloir vider les IDs d'enregistrement dans une table temporaire d'abord, avec une clé primaire en cluster sur Seq, avant concaténer, pour plus de sécurité. Le comportement de concaténation ordonné semble assez fiable, mais il n'est pas documenté de comportement AFAIK, et semble dépendre d'un spool ordonné ou d'un index cluster dans le plan d'exécution. Un curseur statique avec clause order by fonctionnerait également. Il va matérialiser les résultats dans tempdb, puis itérer sur eux. Pas trop lent, compte tenu.

Assurez-vous d'utiliser le .WRITE clause of the UPDATE statement pour les "insertions" incrémentielles, sinon vous allez mettre le serveur hors tension.

Alternativement:

Créer une table distincte pour chaque WorkSet. Cela réduit votre taille de ligne de 4, et c'est toujours une table régulière.