Query per tavolo normalizzare / combinare testo fila
-
16-10-2019 - |
Domanda
Ho una tabella (chiamarlo OldTable) con colonne in questo modo:
ID (int), Rango (int), TextLineNumber (int), someText (varchar)
Il PrimaryKey è multi-parte:. ID + Grado + TextLineNumber
Sto cercando di trasformare / unirsi in un'altra tabella (lo chiamano NewTable) con colonne in questo modo:
ID (int), Rango (int), CombinedText (varchar)
e la chiave primaria sarebbero ID + Grado.
ID e Grado sulla nuova tabella sono già popolati, ma ho bisogno di una domanda che avrebbe aggiornare la colonna CombinedText del NewTable con le seguenti considerazioni:
- La Classifica dato sulla nuova tabella non può esistere sul vecchio tavolo, nel qual caso deve scegliere il grado più alto a disposizione dalla vecchia tabella che non è maggiore il rango nella nuova tabella.
- La colonna CombinedText è una concatenazione di stringhe della colonna "someText" dalla vecchia tabella, concatenato al fine di "TextLineNumber" utilizzando Rank trovato dalla prima considerazione.
Ecco alcuni dati esempio:
vecchiaia http://i54.tinypic.com/jq0vmx.png
Nuovo- http://i53.tinypic.com/dhfyn8.png
Sto usando MSSql 2005, se quello che conta. Io attualmente faccio utilizzando T-SQL e while, ma è diventato un collo di bottiglia delle prestazioni gravi (prendendo circa 1 minuto per 10000 righe).
Modifica: Expanded esempio i dati in formato CSV:
Vecchio:
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
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:
Ecco un esempio di query che ho scoperto che funziona, ma non è abbastanza veloce (basandosi su molteplici sub-query):
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),'')
Si assume anche un numero di riga massimo di 5 che non può essere il caso. Non mi dispiace codificare i LINENUMBERS in tutta la strada ad un massimo di 20, se questo è quello che ci vuole, ma idealmente sarebbe in grado di conto per loro in modo diverso. Ottenere il tempo di esecuzione meno di 20 secondi (i dati reali) è l'obiettivo ...
Soluzione
Questo dovrebbe funzionare, io ripulire poi così il suo più efficiente.
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
Altri suggerimenti
È possibile creare una funzione che punge i valori insieme utilizzando un cursore all'interno della funzione, ma che su l'unica opzione. Avrai bisogno di fare riga per l'elaborazione di fila per rendere questo accada.
Non sono bravo con CTE ancora, ecco il mio prendere sulla questione con un approccio più tradizionale, senza cursori.
Il requisito # 2 mi ricorda di un progetto ho lavorato che ha richiesto la produzione di una concatenazione separati da virgole dei valori su una colonna raggruppate per qualche categoria. La soluzione che ho usato ha richiesto un UDF per produrre la stringa concatenata utilizzando la categoria id fornito.
Di seguito il atta UDF utilizzando parametri Classifica ID e:
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
Il requisito # 1 può essere realizzato partecipa alla NewTable Rank con tutti uguali distinti o meno OldTable Rank e ottenere la migliore corrispondenza / top:
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
Ora che abbiamo la mappatura del OldTable Rank, possiamo usare l'UDF per generare del 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
Lo svantaggio principale di questa soluzione è che ogni chiamata al FSU fnCombinedText () è essenzialmente un SELECT separato sul OldTable. Scommetto che questo approccio può essere portato su una query CTE più scalabile. E io che dovrei veramente andare in giro a padroneggiare CTE, anche.