web-dev-qa-db-fra.com

Requête à normaliser Table / Combinaison de texte en ligne

J'ai une table (appelez-le oldtable) avec des colonnes comme si:

ID (INT), grade (int), textlinenumber (int), témoin (varchar)

Le primaire est en plusieurs parties: ID + rang + textlinénumber.

J'essaie de transformer/de le rejoindre dans une autre table (appelez-la nouvelle) avec des colonnes comme:

ID (int), rang (int), combinedText (varchar)

et la clé principale serait ID + rang.

ID et classement sur la nouvelle table sont déjà peuplés, mais j'ai besoin d'une requête qui mettrait à jour la colonne combinéeText de la nouvelle carte avec les considérations suivantes:

  1. Le rang indiqué sur la nouvelle table peut ne pas exister sur l'ancienne table, auquel cas il a besoin de choisir le rang disponible le plus élevé disponible de l'ancienne table qui n'est pas supérieure au rang sur la nouvelle table.
  2. La colonne combinéeText est une concaténation de la chaîne de la colonne "Démingtext" de l'ancienne table, concaténée par ordre de "TextLinenumber" à l'aide du rang trouvé à partir de la première considération.

Voici quelques exemples de données:

old- http://i54.tinypic.com/jq0vmx.png

nOUVEAU- http://i53.tinyypic.com/dhfyn8.png

J'utilise MSSQL 2005 si cela importe. Je fais actuellement cela en utilisant T-SQL et, tandis que des boucles, mais c'est devenu un goulot de bouteille de performance grave (prenant environ 1 minute pour 10000 rangées).

EDIT: Exemple expansé Données dans CSV:
Vieille:

ID,Rank,LineNumber,SomeText
1,1,1,the qu  
1,1,2,ick br  
1,1,3,own  
1,2,1,some te  
1,2,2,xt  
1,3,1,sample  
2,7,1,jumped ov  
2,7,2,er the  
2,7,3,lazy  
2,13,1,samp  
2,13,2,le text  
3,1,1,ABC  
3,1,2,DEF  
3,1,3,GHI  
3,1,4,JKL  
3,50,1,XYZ

Nouvelle:

ID,Rank,CombinedText
1,2,some text
2,13,sample text
2,14,sample text
3,4,ABCDEFGHIJKL
3,5,ABCDEFGHIJKL
3,50,XYZ
3,55,XYZ

eDIT2:
[.____] Voici un exemple de requête que j'ai trouvé cela fonctionne mais n'est pas assez rapide (en s'appuyant sur plusieurs sous-requêtes):

update newtable set combinedtext = 
coalesce ((select top 1 sometext from OldTable where OldTable.id=newtable.id and oldtable.rank=(select top 1 rank from oldtable where oldtable.id=newtable.id and oldtable.rank<=newtable.rank order by rank desc) and oldtable.linenumber=1),'') +
coalesce ((select top 1 sometext from OldTable where OldTable.id=newtable.id and oldtable.rank=(select top 1 rank from oldtable where oldtable.id=newtable.id and oldtable.rank<=newtable.rank order by rank desc) and oldtable.linenumber=2),'') +
coalesce ((select top 1 sometext from OldTable where OldTable.id=newtable.id and oldtable.rank=(select top 1 rank from oldtable where oldtable.id=newtable.id and oldtable.rank<=newtable.rank order by rank desc) and oldtable.linenumber=3),'') +
coalesce ((select top 1 sometext from OldTable where OldTable.id=newtable.id and oldtable.rank=(select top 1 rank from oldtable where oldtable.id=newtable.id and oldtable.rank<=newtable.rank order by rank desc) and oldtable.linenumber=4),'') +
coalesce ((select top 1 sometext from OldTable where OldTable.id=newtable.id and oldtable.rank=(select top 1 rank from oldtable where oldtable.id=newtable.id and oldtable.rank<=newtable.rank order by rank desc) and oldtable.linenumber=5),'')

Cela suppose également un numéro de ligne maximum de 5 qui peut ne pas être le cas. Je ne me dérange pas de coder les linenumbers de la manière d'un maximum de 20 si c'est ce qu'il faut, mais idéalement, il serait capable de les rendre compte différemment. Obtenir une heure d'exécution inférieure à 20 secondes (les données réelles) est l'objectif ...

5
Adam

Cela devrait fonctionner, je vais le nettoyer plus tard, donc c'est plus efficace.

DECLARE @Old TABLE ( 
  id         INT, 
  rank       INT, 
  linenumber INT, 
  sometext   VARCHAR(1000)) 
DECLARE @New TABLE ( 
  id           INT, 
  rank         INT, 
  combinedtext VARCHAR(1000)) 


;WITH combinedresults(ctid, id, rank, linenumber, combinedtext) 
     AS (SELECT 0, 
                id, 
                rank, 
                linenumber, 
                CAST (sometext AS VARCHAR(8000)) 
         FROM   @Old o 
         WHERE  NOT EXISTS (SELECT TOP 1 1 
                            FROM   @Old 
                            WHERE  id = o.id 
                                   AND rank = o.rank 
                                   AND linenumber < o.linenumber) 
         UNION ALL 
         SELECT ctid + 1, 
                o.id, 
                o.rank, 
                o.linenumber, 
                ct.combinedtext + o.sometext 
         FROM   @Old o 
                INNER JOIN combinedresults ct 
                  ON ct.id = o.id 
                     AND ct.rank = o.rank 
         WHERE  o.linenumber > ct.linenumber) 

UPDATE n 
SET    combinedtext = ct.combinedtext 
FROM   @New n 
       INNER JOIN (SELECT n.id, 
                          n.rank, 
                          MAX(o.rank) orank 
                   FROM   @new n 
                          INNER JOIN @Old o 
                            ON n.id = o.id 
                               AND o.rank <= n.rank 
                   GROUP  BY n.id, 
                             n.rank) r 
         ON n.id = r.id 
            AND n.rank = r.rank 
       INNER JOIN (SELECT id, 
                          ct.rank, 
                          MAX(ctid) ctid 
                   FROM   combinedresults ct 
                   GROUP  BY ct.id, 
                             ct.rank) r2 
         ON r2.id = r.id 
            AND r2.rank = r.orank 
       INNER JOIN combinedresults ct 
         ON r.id = ct.id 
            AND ct.rank = r.orank 
            AND ct.ctid = r2.ctid 

SELECT * 
FROM   @New 
2
TrevDev

Vous pouvez créer une fonction qui stingse les valeurs ensemble à l'aide d'un curseur dans la fonction, mais c'est à propos de votre seule option. Vous aurez besoin de faire une ligne par traitement de ligne pour que cela se produise.

2
mrdenny

Je ne suis pas bon avec CTE encore, alors voici ma prise sur la question en utilisant une approche plus traditionnelle sans curseurs.

L'exigence n ° 2 me rappelle un projet que j'ai travaillé à ce que la création d'une concaténation séparée par des virgules des valeurs sur une colonne regroupée par certaines catégories. La solution que j'ai utilisée nécessitait un UDF pour produire la chaîne concaténée à l'aide de l'ID de catégorie fournie.

Vous trouverez ci-dessous l'UDF adapté à l'aide des paramètres ID et de rang:

CREATE FUNCTION fnCombinedText
(
    @ID int,
    @Rank int
)
RETURNS varchar(MAX)
AS
BEGIN

DECLARE @CombinedText varchar(MAX)
SET @CombinedText = ''

SELECT @CombinedText = @CombinedText + SomeText
FROM oldTable
WHERE [ID] = @ID 
AND [Rank] = @Rank
ORDER BY [Rank]

RETURN @CombinedText

END

L'exigence n ° 1 peut être accomplie en reliant les classes de la nouvelle carte avec tous les rangs distincts égaux ou moins d'OldTable et à obtenir le meilleur match:

CREATE TABLE #RankMap
(
    newID int,
    newRank int,
    oldID int,
    oldRank int
)
INSERT INTO #RankMap
SELECT newID, newRank, oldID, oldRank
FROM
(
    SELECT
        n.id AS newID,
        n.rank AS newRank,
        o.id AS oldID,
        o.rank as oldRank,
        RANK() OVER(PARTITION BY n.rank ORDER BY o.rank DESC) AS topRank
    FROM
        newtable AS n 
        LEFT OUTER JOIN (SELECT DISTINCT id, rank FROM oldtable) AS o
            ON n.id = o.id
            AND n.rank >= o.rank
) AS matchEqualLess
WHERE topRank = 1

Maintenant que nous avons les rangs d'OldTable mappés, nous pouvons utiliser l'UDF pour générer les combinedText:

SELECT
    newID,
    newRank,
    dbo.fnCombinedText(oldID, oldRank) AS CombinedText
FROM #RankMap

--Below is the resultset:
newID       newRank     CombinedText
----------- ----------- --------------------
1           2           some text
3           4           ABCDEFGHIJKL
3           5           ABCDEFGHIJKL
2           13          sample text
2           14          sample text
3           50          XYZ
3           55          XYZ

L'inconvénient principal de cette solution est que chaque appel à l'UDF FnCombineDtext () est essentiellement une sélection séparée sur l'ancienne. Je parie que cette approche peut être portée à une requête CTE plus évolutive. Et je suppose que je devrais vraiment me contourner à maîtriser CTE.

0
Jett