J'essaie d'interroger deux tables et d'obtenir des résultats comme celui-ci:
Section Names
shoes AccountName1, AccountName2, AccountName3
books AccountName1
Les tableaux sont:
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');
J'ai vu une réponse à quelques questions disant d'utiliser "XML PATH" et "STUFF" pour interroger les données pour obtenir les résultats que je recherche, mais je pense qu'il manque quelque chose. J'ai essayé la requête ci-dessous et j'obtiens le message d'erreur:
La colonne 'a.AccountId' n'est pas valide dans la liste de sélection car elle n'est contenue ni dans une fonction d'agrégation ni dans la clause GROUP BY.
Je ne l'ai pas dans la clause SELECT de l'une ou l'autre requête, mais je suppose que l'erreur est due au fait que AccountId n'est pas unique dans TableA.
Voici la requête que j'essaie actuellement de faire fonctionner correctement.
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
Désolé, j'ai raté une étape de la relation. Essayez cette version (bien que celle de Martin fonctionnera également ):
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;
Une approche qui est au moins aussi bonne, mais parfois mieux, passe de 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;
À un niveau élevé, la raison DISTINCT
s'applique à la liste de colonnes entière. Par conséquent, pour tous les doublons, il doit effectuer le travail d'agrégation pour chaque doublon avant d'appliquer DISTINCT
. Si tu utilises GROUP BY
alors il peut potentiellement supprimer les doublons avant faisant tout le travail d'agrégation. Ce comportement peut varier selon le plan en fonction de divers facteurs, notamment les index, la stratégie du plan, etc. Et un basculement direct vers GROUP BY
peut ne pas être possible dans tous les cas.
Dans tous les cas, j'ai exécuté ces deux variantes dans SentryOne Plan Explorer . Les plans sont différents de quelques manières mineures et inintéressantes, mais les E/S impliquées dans la table de travail sous-jacente sont révélatrices. Voici DISTINCT
:
Et voici GROUP BY
:
Lorsque j'ai agrandi les tableaux (plus de 14 000 lignes correspondant à 24 valeurs potentielles), cette différence est plus prononcée. DISTINCT
:
GROUP BY
:
Dans SQL Server 2017, vous pouvez utiliser 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;
L'E/S ici n'est presque rien:
Mais, si vous n'êtes pas sur SQL Server 2017 (ou Azure SQL Database) et ne pouvez pas utiliser STRING_AGG
, Je dois donner du crédit là où le crédit est dû ... La réponse de Paul White ci-dessous a très peu d'E/S et lance le pantalon des deux FOR XML PATH
solutions ci-dessus.
Autres améliorations de ces messages:
Regarde aussi:
J'ai pensé que j'essaierais une solution utilisant 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')
);
Le XML de la variable ressemble à ceci:
<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>
La requête finale déchiquette le XML en sections et concatène les noms dans chacune:
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);