Kết hợp SQL Row với XML PATH và STUFF đưa ra lỗi sql tổng hợp


9

Tôi đang cố gắng truy vấn hai bảng và nhận được kết quả như sau:

Section    Names
shoes      AccountName1, AccountName2, AccountName3
books      AccountName1

Các bảng là:

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');

Tôi đã thấy một vài câu hỏi được trả lời rằng sử dụng "XML PATH" và "STUFF" để truy vấn dữ liệu để có được kết quả mà tôi đang tìm kiếm, nhưng tôi nghĩ còn thiếu một cái gì đó. Tôi đã thử truy vấn bên dưới và nhận được thông báo lỗi:

Cột 'a.AccountId' không hợp lệ trong danh sách chọn vì nó không có trong hàm tổng hợp hoặc mệnh đề GROUP BY.

Tôi không có nó trong mệnh đề SELECT của một trong hai truy vấn, nhưng tôi cho rằng lỗi là do AccountId không phải là duy nhất trong TableA.

Đây là truy vấn tôi hiện đang cố gắng để làm việc chính xác.

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

Câu trả lời:


14

Xin lỗi, tôi đã bỏ lỡ một bước trong mối quan hệ. Hãy thử phiên bản này (mặc dù Martin cũng sẽ hoạt động ):

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;

Một cách tiếp cận ít nhất là tốt , nhưng đôi khi tốt hơn, là chuyển từ DISTINCTsang 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;

Ở mức cao, lý do DISTINCTáp dụng cho toàn bộ danh sách cột. Do đó, đối với bất kỳ bản sao nào, nó phải thực hiện công việc tổng hợp cho mỗi bản sao trước khi áp dụng DISTINCT. Nếu bạn sử dụng GROUP BYthì nó có khả năng có thể loại bỏ các bản sao trước khi thực hiện bất kỳ công việc tổng hợp nào. Hành vi này có thể thay đổi theo kế hoạch tùy thuộc vào nhiều yếu tố bao gồm chỉ số, chiến lược kế hoạch, v.v. Và việc chuyển đổi trực tiếp sang GROUP BYcó thể không thể thực hiện được trong mọi trường hợp.

Trong mọi trường hợp, tôi đã chạy cả hai biến thể này trong SentryOne Plan Explorer . Các kế hoạch khác nhau theo một số cách nhỏ, không thú vị, nhưng I / O liên quan đến bàn làm việc cơ bản đang nói. Đây là DISTINCT:

nhập mô tả hình ảnh ở đây

Và đây là GROUP BY:

nhập mô tả hình ảnh ở đây

Khi tôi làm cho các bảng lớn hơn (hơn 14.000 ánh xạ hàng tới 24 giá trị tiềm năng), sự khác biệt này rõ rệt hơn. DISTINCT:

nhập mô tả hình ảnh ở đây

GROUP BY:

nhập mô tả hình ảnh ở đây

Trong SQL Server 2017, bạn có thể sử dụng 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 ở đây gần như không có gì:

nhập mô tả hình ảnh ở đây


Nhưng, nếu bạn không sử dụng SQL Server 2017 (hoặc Cơ sở dữ liệu SQL Azure) và không thể sử dụng STRING_AGG, tôi phải cung cấp tín dụng khi khoản nợ đến hạn ... Câu trả lời của Paul White bên dưới có rất ít I / O và đá vào quần tắt cả hai FOR XML PATHgiải pháp trên.

nhập mô tả hình ảnh ở đây


Những cải tiến khác từ những bài viết này:

Cũng thấy:


11

Tôi nghĩ rằng tôi sẽ thử một giải pháp bằng cách sử dụng XML.

SEDE Demo

Những cái bàn

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
);

Dữ liệu

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');

Tham gia và chuyển đổi sang 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')
);

Truy vấn tạo XML

XML trong biến trông như thế này:

<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>

Truy vấn

Truy vấn cuối cùng chia XML thành các phần và ghép các tên trong mỗi phần:

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);

Kết quả

Đầu ra

Kế hoạch thực hiện

Kế hoạch thực hiện


-3

nhập mô tả hình ảnh ở đây

Bảng demo

nhập mô tả hình ảnh ở đây

bảng bộ phận

nhập mô tả hình ảnh ở đây

select 
    g.Department,
    t.numberofemp,
    t.totalslary,
    t.totalage,
    t.name 
from (
    select  
        count(*) numberofemp,
        tl.dtid,
        sum(salary) as totalslary,
        sum(age) as totalage,
        name=
            (stuff((
                select  DISTINCT  
                    ', '+ [name] 
                from  demo as d 
                inner join demodept as dt 
                    on d.dtid=dt.dtid 
                where dt.dtid=tl.dtid 
            for xml path(''),type).value('(./text())[1]','varchar(max)'),1,2,''))  
 -- from demo as d inner join demodept as dt on d.dtid=dt.dtid
    from Demo as tl
    group by tl.dtid) as t 
inner join  demodept as g 
    on  t.dtid=g.dtid

Đầu ra


Chào mừng đến với DBA.SE! Bạn đã quên đầu ra của mình, có vẻ như ...
Glorfindel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.