2つのテーブルをクエリして、次のような結果を取得しようとしています。
Section Names
shoes AccountName1, AccountName2, AccountName3
books AccountName1
テーブルは次のとおりです。
CREATE TABLE dbo.TableA(ID INT, Section varchar(64), AccountId varchar(64));
INSERT dbo.TableA(ID, Section, AccountId) VALUES
(1 ,'shoes','A1'),
(2 ,'shoes','A2'),
(3 ,'shoes','A3'),
(4 ,'books','A1');
CREATE TABLE dbo.TableB(AccountId varchar(20), Name varchar(64));
INSERT dbo.TableB(AccountId, Name) VALUES
('A1','AccountName1'),
('A2','AccountName2'),
('A3','AccountNAme3');
「XML PATH」と「STUFF」を使用してデータをクエリし、探している結果を取得するようにといういくつかの質問に答えましたが、不足しているものがあると思います。以下のクエリを試したところ、エラーメッセージが表示されました。
列 'a.AccountId'は、集約関数にもGROUP BY句にも含まれていないため、選択リストでは無効です。
どちらのクエリのSELECT句にもありませんが、エラーはTableIdでAccountIdが一意でないためと考えられます。
これは、私が現在正しく動作するようにしようとしているクエリです。
SELECT section, names= STUFF((
SELECT ', ' + Name FROM TableB as b
WHERE AccountId = b.AccountId
FOR XML PATH('')), 1, 1, '')
FROM TableA AS a
GROUP BY a.section
申し訳ありませんが、関係の一歩を逃しました。このバージョンを試してください(ただし、 Martinも同様に機能します )。
SELECT DISTINCT o.section, names= STUFF((
SELECT ', ' + b.Name
FROM dbo.TableA AS a
INNER JOIN dbo.TableB AS b
ON a.AccountId = b.AccountId
WHERE a.Section = o.Section
FOR XML PATH, TYPE).value(N'.[1]', N'varchar(max)'), 1, 2, '')
FROM dbo.TableA AS o;
少なくとも同じように、時にはより良いアプローチは、DISTINCT
からGROUP BY
に切り替えることです。
SELECT o.section, names= STUFF((
SELECT ', ' + b.Name
FROM dbo.TableA AS a
INNER JOIN dbo.TableB AS b
ON a.AccountId = b.AccountId
WHERE a.Section = o.Section
FOR XML PATH, TYPE).value(N'.[1]', N'varchar(max)'), 1, 2, '')
FROM dbo.TableA AS o
GROUP BY o.section;
高レベルでは、理由DISTINCT
は列リスト全体に適用されます。したがって、重複の場合は、DISTINCT
を適用する前に、重複ごとに集計作業を実行する必要があります。 GROUP BY
を使用すると、重複を削除する可能性がありますbefore集計作業のいずれかを実行します。この動作は、インデックス、プラン戦略などのさまざまな要因に応じて、プランによって異なる場合があります。また、すべての場合において、GROUP BY
への直接切り替えができない場合があります。
いずれにせよ、私はこれら両方のバリエーションを SentryOne Plan Explorer で実行しました。計画はいくつかのマイナーで興味のない方法で異なりますが、基礎となる作業テーブルに関連するI/Oが示しています。これがDISTINCT
です:
そして、これがGROUP BY
です。
テーブルを大きくした場合(14,000以上の行が24の潜在的な値にマッピングされる)、この違いはより顕著になります。 DISTINCT
:
GROUP BY
:
SQL Server 2017では、STRING_AGG
を使用できます。
SELECT a.section, STRING_AGG(b.Name, ', ')
FROM dbo.TableA AS a
INNER JOIN dbo.TableB AS b
ON a.AccountId = b.AccountId
WHERE a.Section = a.Section
GROUP BY a.section;
ここのI/Oはほとんど何もありません。
ただし、SQL Server 2017(またはAzure SQLデータベース)を使用しておらず、STRING_AGG
を使用できない場合は、 クレジットを提供する必要がありますdue...以下のPaul Whiteの回答 はI/Oがほとんどなく、上記のFOR XML PATH
ソリューションの両方からズボンを蹴飛ばします。
これらの投稿からの他の機能強化:
こちらもご覧ください:
XMLを使用した解決策を試してみようと思いました。
DECLARE @TableA AS table
(
ID integer PRIMARY KEY,
Section varchar(10) NOT NULL,
AccountID char(2) NOT NULL
);
DECLARE @TableB AS table
(
AccountID char(2) PRIMARY KEY,
Name varchar(20) NOT NULL
);
INSERT @TableA
(ID, Section, AccountID)
VALUES
(1, 'shoes', 'A1'),
(2, 'shoes', 'A2'),
(3, 'shoes', 'A3'),
(4, 'books', 'A1');
INSERT @TableB
(AccountID, Name)
VALUES
('A1', 'AccountName1'),
('A2', 'AccountName2'),
('A3', 'AccountName3');
DECLARE @x xml =
(
SELECT
TA.Section,
CA.Name
FROM @TableA AS TA
JOIN @TableB AS TB
ON TB.AccountID = TA.AccountID
CROSS APPLY
(
VALUES(',' + TB.Name)
) AS CA (Name)
ORDER BY TA.Section
FOR XML AUTO, TYPE, ELEMENTS, ROOT ('Root')
);
変数のXMLは次のようになります。
<Root>
<TA>
<Section>shoes</Section>
<CA>
<Name>,AccountName1</Name>
</CA>
<CA>
<Name>,AccountName2</Name>
</CA>
<CA>
<Name>,AccountName3</Name>
</CA>
</TA>
<TA>
<Section>books</Section>
<CA>
<Name>,AccountName1</Name>
</CA>
</TA>
</Root>
最後のクエリは、XMLをセクションに細断し、それぞれの名前を連結します。
SELECT
Section =
N.n.value('(./Section/text())[1]', 'varchar(10)'),
Names =
STUFF
(
-- Consecutive text nodes collapse
N.n.query('./CA/Name/text()')
.value('./text()[1]', 'varchar(8000)'),
1, 1, ''
)
-- Shred per section
FROM @x.nodes('Root/TA') AS N (n);