user421875
user421875

Reputation: 15

“Stuff and 'For Xml Path'” or UNION to exclude duplicate rows

This is further progress of question here: how to use "Stuff and 'For Xml Path'" to unite rows in table

I have 3 tables - invoices, positions and accounts which build common table by SELECT as specified in result below. I need to exclude duplicate rows which appear due to different accounts for same invoices. I think it can be solved by “Stuff and 'For Xml Path'” and/or by UNION, but I don't really know how to do that.

Please assist with this issue. Thank You in advance.

Here's dbfiddle with DL and sample data population: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=6401c2886a24b21239dade27e8c549ec

Using DevExpress dashboard designer based on SQL Server 2016.

Query:

-- DDL and sample data population, start
CREATE TABLE invoices
(
invoice VARCHAR(20) NOT NULL,
id INT NOT NULL,
);
INSERT invoices(invoice, id) 
VALUES ('ty20210110', 1);
INSERT invoices(invoice, id) 
VALUES ('ty20210111', 2);
INSERT invoices(invoice, id) 
VALUES ('ty20210112', 3);

CREATE TABLE positions
(
position VARCHAR(20) NOT NULL,
quantity INT NOT NULL,
price INT NOT NULL,
summ INT NOT NULL, 
invoice INT NOT NULL,
);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000001', 2, 100, 200, 1);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000002', 3, 100, 300, 2);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000003', 1, 250, 250, 2);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000004', 2, 120, 240, 1);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000005', 4, 100, 400, 1);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000006', 3, 100, 300, 1001);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000007', 5, 80, 400, 3);
INSERT positions(position, quantity, price, summ, invoice) 
VALUES ('p1000008', 5, 100, 500, 3);

CREATE TABLE accounts
(
account INT NOT NULL,
invoice INT NOT NULL,
);
INSERT accounts(account, invoice) 
VALUES (01, 2);
INSERT accounts(account, invoice) 
VALUES (02, 2);
INSERT accounts(account, invoice) 
VALUES (03, 1);
INSERT accounts(account, invoice) 
VALUES (04, 3);
INSERT accounts(account, invoice) 
VALUES (05, 1);
INSERT accounts(account, invoice) 
VALUES (06, 3);
-- DDL and sample data population, end


SELECT
positions.position, positions.quantity, positions.price, positions.summ,
invoices.invoice,
accounts.account

FROM
positions
INNER JOIN
invoices invoices ON invoices.id = positions.invoice
INNER JOIN
accounts accounts ON invoices.id = accounts.invoice

Result:

position quantity price summ invoice account
p1000001 2 100 200 in20210110 3
p1000001 2 100 200 in20210110 5
p1000002 3 100 300 in20210111 1
p1000002 3 100 300 in20210111 2
p1000003 1 250 250 in20210111 1
p1000003 1 250 250 in20210111 2
p1000004 2 120 240 in20210110 3
p1000004 2 120 240 in20210110 5
p1000005 4 100 400 in20210110 3
p1000005 4 100 400 in20210110 5
p1000006 3 100 300 in20210110 3
p1000006 3 100 300 in20210110 5
p1000007 5 80 400 in20210112 4
p1000007 5 80 400 in20210112 6
p1000008 5 100 500 in20210112 4
p1000008 5 100 500 in20210112 6

Required result 1:

position quantity price summ invoice account
p1000001 2 100 200 in20210110 3, 5
p1000004 2 120 240 in20210110 3, 5
p1000005 4 100 400 in20210110 3, 5
p1000006 3 100 300 in20210110 3, 5
p1000002 3 100 300 in20210111 1, 2
p1000003 1 250 250 in20210111 1, 2
p1000007 5 80 400 in20210112 4, 6
p1000008 5 100 500 in20210112 4, 6

Required result 2:

position quantity price summ invoice account 1 account 2
p1000001 2 100 200 in20210110 3 5
p1000004 2 120 240 in20210110 3 5
p1000005 4 100 400 in20210110 3 5
p1000006 3 100 300 in20210110 3 5
p1000002 3 100 300 in20210111 1 2
p1000003 1 250 250 in20210111 1 2
p1000007 5 80 400 in20210112 4 6
p1000008 5 100 500 in20210112 4 6

Upvotes: 0

Views: 477

Answers (1)

critical_error
critical_error

Reputation: 6706

Your first resultset can be handled in two ways, depending on your version of SQL Server. Try the following examples in SSMS:

Create Data

DECLARE @invoices table (
    invoice VARCHAR(20) NOT NULL, id INT NOT NULL 
);

INSERT INTO @invoices (invoice, id) VALUES 
    ( 'ty20210110', 1 ), ( 'ty20210111', 2 ), ( 'ty20210112', 3 );

DECLARE @positions table (
    position VARCHAR(20) NOT NULL, quantity INT NOT NULL, price INT NOT NULL, summ INT NOT NULL, invoice INT NOT NULL
);

INSERT INTO @positions ( position, quantity, price, summ, invoice ) VALUES
    ( 'p1000001', 2, 100, 200, 1 ),
    ( 'p1000002', 3, 100, 300, 2 ),
    ( 'p1000003', 1, 250, 250, 2 ),
    ( 'p1000004', 2, 120, 240, 1 ),
    ( 'p1000005', 4, 100, 400, 1 ),
    ( 'p1000006', 3, 100, 300, 1 ),
    ( 'p1000007', 5, 80, 400, 3 ),
    ( 'p1000008', 5, 100, 500, 3 );

DECLARE @accounts table (
    account INT NOT NULL, invoice INT NOT NULL
);

INSERT INTO @accounts ( account, invoice ) VALUES 
    ( 1, 2 ), ( 2, 2 ), ( 3, 1 ), ( 4, 3 ), ( 5, 1 ), ( 6, 3 );

If you're using SQL Server 2017+

SELECT
    positions.position,
    positions.quantity,
    positions.price,
    positions.summ,
    invoices.invoice,
    STRING_AGG ( accounts.account, ',' ) AS account
FROM @positions positions
INNER JOIN @invoices invoices 
    ON invoices.id = positions.invoice
INNER JOIN @accounts accounts 
    ON invoices.id = accounts.invoice
GROUP BY
    position, quantity, price, summ, invoices.invoice
ORDER BY
    invoice;

For Pre-SQL Server 2017

SELECT
    positions.position,
    positions.quantity,
    positions.price,
    positions.summ,
    invoices.invoice,
    acct.account
FROM @positions positions
INNER JOIN @invoices invoices 
    ON invoices.id = positions.invoice
INNER JOIN @accounts accounts 
    ON invoices.id = accounts.invoice
OUTER APPLY (
    SELECT STUFF ( (
        SELECT ',' + CAST ( a.account AS varchar(50) ) AS "text()"
        FROM @accounts a
        WHERE
            a.invoice = invoices.id
        FOR XML PATH ( '' )
    ), 1, 1, '' ) AS account
) AS acct
GROUP BY
    position, quantity, price, summ, invoices.invoice, acct.account
ORDER BY
    invoice;

Both queries return

+----------+----------+-------+------+------------+---------+
| position | quantity | price | summ |  invoice   | account |
+----------+----------+-------+------+------------+---------+
| p1000001 |        2 |   100 |  200 | ty20210110 | 3,5     |
| p1000004 |        2 |   120 |  240 | ty20210110 | 3,5     |
| p1000005 |        4 |   100 |  400 | ty20210110 | 3,5     |
| p1000006 |        3 |   100 |  300 | ty20210110 | 3,5     |
| p1000002 |        3 |   100 |  300 | ty20210111 | 1,2     |
| p1000003 |        1 |   250 |  250 | ty20210111 | 1,2     |
| p1000007 |        5 |    80 |  400 | ty20210112 | 4,6     |
| p1000008 |        5 |   100 |  500 | ty20210112 | 4,6     |
+----------+----------+-------+------+------------+---------+

The second resultset you requested gets considerably more complicated and requires the use of the XML datatype. The following example makes liberal assumptions of your data, most notably that only two accounts are expected. Having said that, you can expand the [account n] columns in the PIVOT as necessary without having to resort to dynamic SQL.

SELECT DISTINCT
    positions.position,
    positions.quantity,
    positions.price,
    positions.summ,
    invoices.invoice
    , x.*
FROM @positions positions
INNER JOIN @invoices invoices 
    ON invoices.id = positions.invoice
INNER JOIN @accounts accounts 
    ON invoices.id = accounts.invoice
OUTER APPLY (

    -- Create an XML account list --
    SELECT CAST ( (
        SELECT 
            'account ' + CAST ( ROW_NUMBER() OVER ( ORDER BY a.invoice ) AS varchar(50) ) AS id,
            a.account
        FROM @accounts a
        WHERE
            a.invoice = invoices.id
        FOR XML PATH ( 'data' ), ROOT ( 'accounts' )
    ) AS xml ) AS account_xml

) AS acct
OUTER APPLY (

    -- PIVOT the account details --
    SELECT
        *
    FROM (
        SELECT
            x.f.value( 'id[1]', 'varchar(50)' ) AS id,
            x.f.value( 'account[1]', 'varchar(50)' ) AS act
        FROM acct.account_xml.nodes( '//accounts/data' ) x(f)
    ) AS d
    PIVOT (
        MAX ( act ) FOR id IN ( [account 1], [account 2] )
    ) AS p

) AS x
ORDER BY
    invoice;

Returns

+----------+----------+-------+------+------------+-----------+-----------+
| position | quantity | price | summ |  invoice   | account 1 | account 2 |
+----------+----------+-------+------+------------+-----------+-----------+
| p1000001 |        2 |   100 |  200 | ty20210110 |         3 |         5 |
| p1000004 |        2 |   120 |  240 | ty20210110 |         3 |         5 |
| p1000005 |        4 |   100 |  400 | ty20210110 |         3 |         5 |
| p1000006 |        3 |   100 |  300 | ty20210110 |         3 |         5 |
| p1000002 |        3 |   100 |  300 | ty20210111 |         1 |         2 |
| p1000003 |        1 |   250 |  250 | ty20210111 |         1 |         2 |
| p1000007 |        5 |    80 |  400 | ty20210112 |         4 |         6 |
| p1000008 |        5 |   100 |  500 | ty20210112 |         4 |         6 |
+----------+----------+-------+------+------------+-----------+-----------+

Upvotes: 1

Related Questions