テーブルを正規化するためのクエリ/行テキストを結合します
-
16-10-2019 - |
質問
私は次のような列が付いたテーブルを持っています(古いテーブルと呼んでいます):
id(int)、rank(int)、textlineNumber(int)、sometext(varchar)
PrimaryKeyはマルチパートです:id+rank+textlineNumber。
私はそれを別のテーブルに変換/結合しようとしています(newtableと呼んで)。
id(int)、rank(int)、combinedtext(varchar)
主キーはID+ランクです。
新しいテーブルのIDとランクはすでに埋め込まれていますが、Newtableの複合テキスト列を次の考慮事項で更新するクエリが必要です。
- 新しいテーブルに与えられたランクは古いテーブルに存在しない可能性があります。その場合、新しいテーブルのランクよりも大きくない古いテーブルから利用可能な最高のランクを選択する必要があります。
- CombinedText列は、最初の考慮事項から見つかったランクを使用して「TextLineNumber」の順に連結された古いテーブルの「sometext」列の文字列の連結です。
これがデータの例です。
年-http://i54.tinypic.com/jq0vmx.png
新着-http://i53.tinypic.com/dhfyn8.png
それが重要な場合は、MSSQL 2005を使用しています。私は現在、T-SQLとループ中にこれを行いますが、それは深刻なパフォーマンスボトルネックになります(10000列に約1分かかります)。
編集:CSVの拡張例データ:
年:
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
編集2:
これが私が見つけたクエリの例です。
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),'')
また、5の最大線番号を想定していますが、そうではないかもしれません。それが必要な場合は、最大20の最大20までのLinenumbersをハードコーディングすることを気にしませんが、理想的には異なる方法で説明できるでしょう。 20秒未満(実際のデータ)を取得することが目標です...
解決
これは機能するはずです、私は後でそれをきれいにして、より効率的にします。
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
他のヒント
関数内のカーソルを使用して値を一緒に刺す関数を作成できますが、それはあなたの唯一のオプションに関するものです。これを実現するには、行ごとに処理する必要があります。
私はまだCTEが上手ではないので、カーソルなしでより伝統的なアプローチを使用して、質問に対する私の質問です。
要件#2は、あるカテゴリでグループ化された列の値のコンマ分離された連結を作成する必要があるというプロジェクトを思い出させます。私が使用したソリューションは、提供されたカテゴリIDを使用して連結された文字列を生成するためにUDFを必要としました。
以下は、IDパラメーターとランクパラメーターを使用した適応されたUDFです。
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
要件#1は、すべての明確な等しいまたはそれ以下のランクでニューテーブルランクをジョーにし、最高/トップマッチを取得することで達成できます。
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
マッピングされた古いランクができたので、UDFを使用して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
このソリューションの主な欠点は、UDF fncombinedText()への各呼び出しが基本的に古いテーブルの個別の選択であることです。このアプローチは、よりスケーラブルなCTEクエリに移植できると思います。そして、私も本当にCTEのマスターに取り掛かるべきだと思います。