SQL 行与 XML PATH 和 STUFF 的连接给出聚合 sql 错误

B.M*_*thy 9 xml sql-server sql-server-2008-r2 concat

我正在尝试查询两个表并获得如下结果:

Section    Names
shoes      AccountName1, AccountName2, AccountName3
books      AccountName1
Run Code Online (Sandbox Code Playgroud)

这些表是:

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');
Run Code Online (Sandbox Code Playgroud)

我看到一些回答说使用“XML PATH”和“STUFF”来查询数据以获得我正在寻找的结果,但我认为缺少一些东西。我尝试了以下查询并收到错误消息:

列“a.AccountId”在选择列表中无效,因为它既不包含在聚合函数中也不包含在 GROUP BY 子句中。

我在任一查询的 SELECT 子句中都没有它,但我认为错误是因为 AccountId 在 TableA 中不是唯一的。

这是我目前正在尝试正常工作的查询。

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
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 15

抱歉,我错过了关系中的一步。试试这个版本(虽然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;
Run Code Online (Sandbox Code Playgroud)

一种至少同样好但有时更好的方法是从 切换DISTINCTGROUP 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;
Run Code Online (Sandbox Code Playgroud)

在较高级别,原因DISTINCT适用于整个列列表。因此,对于任何重复项,它必须在应用之前为每个重复项执行汇总工作DISTINCT。如果您使用,GROUP BY那么它可能会执行任何聚合工作之前删除重复项。此行为可能因计划而异,具体取决于包括索引、计划策略等在内的各种因素。并且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;
Run Code Online (Sandbox Code Playgroud)

这里的 I/O 几乎没有:

在此处输入图片说明


但是,如果您不在 SQL Server 2017(或 Azure SQL 数据库)上,并且不能使用STRING_AGG我必须在信用到期的地方给予信用......下面 Paul White 的回答很少有 I/O 并且很兴奋关闭FOR XML PATH上述两种解决方案。

在此处输入图片说明


这些帖子的其他改进:

另见:


Pau*_*ite 12

我想我会尝试使用 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
);
Run Code Online (Sandbox Code Playgroud)

数据

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');
Run Code Online (Sandbox Code Playgroud)

加入并转换为 XML

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')
);
Run Code Online (Sandbox Code Playgroud)

XML 创建查询

变量中的 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
);
Run Code Online (Sandbox Code Playgroud)

询问

最后的查询将 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);
Run Code Online (Sandbox Code Playgroud)

结果

输出

执行计划

执行计划