Combine multiple row values into one row - sql

I have a result set that should be pulled into a report as one line. However, there can be multiple buyers associated to an order and these buyers are represented as a new row in the database. So for instance, I have the following...
SELECT
O.OrdersID
,BS.LastName
FROM
Orders O
LEFT JOIN
BuyerSeller BS ON O.OrdersID = BS.OrdersID
If there are multiple buyers, it will return the following result set as follows:
OrdersID | LastName
----------------------
1 | Tester1
1 | Tester2
1 | Tester3
I'd like it to return as the following (separated by columns):
OrdersID | LastName
---------------------------------------
1 | Tester1, Tester2, Tester3
Thanks for the assistance.

Here is your Answer.
SELECT DISTINCT Ord.OrderID , substring
((SELECT ',' + BS.LastName AS [text()]
FROM Orders O LEFT JOIN BuyerSeller BS ON O.OrderID = BS.OrderID
ORDER BY O.OrderID FOR XML PATH('')), 2, 1000) LastName
FROM Orders ord
This will return the expected output.

To accomplish this in SSRS you would need to
Create a table with OrdersID as Row group
Make sure there is no detail section inside the group. If there is one delete it without deleting the rows.
Write this experession for LastName:
=Join(Lookupset(Fields!OrdersID.Value, Fields!OrdersID.Value, Fields!LastName.Value, "myDataSet"), ", ")
Remember SSRS is case sensitive.

Related

How to work left outer join in SQl Server?

First: I know to use all types of join but I don't know why it works like this for this Query
I have a Scenario for making a SQL Query, by using 3 tables and a left outer join between selling and order items.
My Tables:
--------------------
Item
--------------------
ID | Code
--------------------
1 | 7502
SQL > select * from Item where id = 1
---------------------
Item_Order
---------------------------
Item | Box | Quantity
---------------------------
1 | 30 | 15000
1 | 12 | 6000
SQL > select * from Item_Order where Item = 1
--------------------------
Invoice_Item
-------------------
Item | Num | Quantity
-------------------------
1 | 1.64 | 10
1 | 2.4 | 8
SQL > select * from Invoice_Item where Item = 1
I want this output:
Item | OrderQ | OrderB | SellN | SellQ
-----------------------------------------
1 | 1500 | 30 | 1.64 | 10
1 | 6000 | 12 | 2.4 | 8
My SQL code:
SELECT Item.ID, Item_Order.Box As OrderB, Item_Order.Quantity As OrderQ, Invoice_Item.Num As SellN, Invoice_Item.Quantity As SellQ
FROM Item LEFT OUTER JOIN
Invoice_Item ON Item.ID = Invoice_Item.Item LEFT OUTER JOIN
Item_Order ON Item_Order.Item = Item.ID
where Item.ID = 1
Why is my output 2x? or why does my output return 4 records?
Your result can be achieve with row_number:
select a.ID
, a.OrderB
, a.OrderQ
, b.Quantity SellQ
, b.Num SellN
from
(SELECT Item.ID
, Item_Order.Box As OrderB
, Item_Order.Quantity As OrderQ
, row_number () over (order by Item.ID) rn
FROM Item
left outer JOIN Item_Order ON Item.ID = Item_Order.Item) a
left outer join (select Item
, Num
, Quantity
, row_number () over (order by Item) rn
from Invoice_Item ) b
on a.ID = b.Item
and a.rn = b.rn
Here is a demo
You can add more tables like this:
left outer join (select Item
, Num
, Quantity
, row_number () over (order by Item) rn
from Invoice_Item ) b
Because when you first join Item with Item_Order it outputs two records because there are two records in Item_Order. Now this resulting query will be left join with Invoice_Item and that two records will be join with all of the records of Invoice_Item
You can better understand this like this
SELECT Item.ID, Item_Order.Box As OrderB, Item_Order.Quantity As OrderQ, Invoice_Item.Num As SellN, Invoice_Item.Quantity As SellQ
FROM Item LEFT OUTER JOIN
Invoice_Item ON Item.ID = Invoice_Item.Item LEFT OUTER JOIN
where Item.ID = 1 into table4 //Only to explain
Now the result of first query table4 will be joined with Items_Order
You are joining on one key -- two rows with the same key in one table times two rows in the second table = 4 rows.
You need a separate key. You can generate one using row_number():
SELECT i.ID, io.Box As OrderB, io.Quantity As OrderQ,
ii.Num As SellN, ii.Quantity As SellQ
FROM Item i LEFT OUTER JOIN
((SELECT ii.*,
ROW_NUMBER() OVER (PARTITION BY ii.item ORDER BY ii.item) as seqnum
FROM Invoice_Item ii
) FULL JOIN
(SELECT io.*,
ROW_NUMBER() OVER (PARTITION BY io.item ORDER BY io.item) as seqnum
FROM Item_Order io
) io
ON io.Item = ii.ID AND io.seqnum = ii.seqnum
)
ON i. = ii.Item
where i.ID = 1;
Note that this is one of the few cases where I use parentheses in the FROM clause. This code can handle additional rows in either of the tables -- if one table is longer than the other, the columns from the other will be NULL.
If you know the two tables have the same number of rows (for a given item) you can just use inner joins and no parentheses.
It is duplicating because you have no secondary association between Invoice_Item and Item_Order. For each record in Invoice_Item it is matching to Item_Order (known as a Cartesian result) base ONLY on the Item ID. So, your order qty APPEARS to be a 1:1 reference such that the first Invoice item Qty of 10 is MEANT to be associated with Item_Order Box = 30. and Qty 8 is MEANT to be associated with Item_Order Box = 12.
Item_Order
Item Box Quantity
1 30 15000
1 12 6000
Invoice_Item
Item Num Quantity
1 1.64 10
1 2.4 8
You probably need to tack on the "Box" reference so Item_Order and Invoice_Item are a 1:1 match.
What is happening is for each item in Invoice Item is joined to the Item_Order based on Item ID. So you are getting two. If you had 3 Invoice Items with 1 and 6 of Items_Order, you would be getting 18 rows.
FEEDBACK
Even though you have an accepted answer based on an OVER/PARTITION/ROW NUMBER, that process is forcing a surrogate secondary ID to each row. Relying on this approach is not best for an overall data structure association. What happens if you delete the second item on an order. are you positive you are deleting the second item in the invoice_items?
As for returning 2 records in the original scenario, you can via the surrogate process, but I think it would be better for you long term to understand what is happening on the join. Going back to your sample data of Item_Order and Invoice_Item. So lets start with the Item_Order table. The SQL engine is going to process each row individually.
First row SQL grabs Item = 1, Box = 30, Qty = 15000.
So now it joins to the Invoice Item table, and since your criteria it only joins based on Item. So, it sees the first row and says... yup this is item 1, so include that with the item order record (first row returned). Now it goes to the second line in the invoice item table... yup, it too is the same item 1, so it returns it again (second row returned).
Now, SQL grabs the second row Item = 1, Box = 12, Qty = 6000.
Goes back to the Invoice Item table and does exact same test... and for each row in the Item Order that has an Item = 1, and 3rd and 4th row hence your doubling... If either table had more records with the same Item id, it would return that many more records... 3 and 3 records would have returned 9 rows. 4 and 4 records would return 16 rows, etc. Doing the surrogate will work, but I don't think as safe as a better/updated design structure.

Trouble concatenating rows into string with multiple joins

I am struggling to create a statement in a snowflake schema. I need to show a list of all products and associated tracks but also include on each line a list of songwriters names and ownership percentages eg. add one column that looks like: "Sam Smith (50.00%), Sally Simpson (25.00%), John Chan (25.00%)". My tables are:
Table: PRODUCT -PRODUCT_ID -ALBUM_ARTIST -ALBUM_TITLE
Table: SONGWRITER -SONGWRITER_ID -FIRSTNAME -LASTNAME
Table: SONG_SONGWRITER -SONGWRITER_ID -TRACK_ID -OWNERSHIP_SHARE
Table: TRACK -TRACK_ID -PRODUCT_ID -TRACK_ARTIST |TRACK_NAME
I know that I need to concatenate (and join) the SONG_SONGWRITER and SONGWRITER tables, but I am very lost due to the several joins needed for to display the products and tracks.
I have tried (and failed with):
SELECT prod.*, tra.TRACK_NAME,(SELECT (SELECT ','+ writer.FIRSTNAME +' ' +writer.LASTNAME + '(' + FORMAT(ssw.OWNERSHIP_SHARE,'P2') + ')' FROM SONGWRITER writer INNER JOIN SONG_SONGWRITER ssw ON ssw.SONGWRITER_ID=writer.SONGWRITER_ID FOR XML PATH ('')))
FROM PRODUCT prod
INNER JOIN TRACK tra
ON tra.PRODUCT_ID=prod.PRODUCT_ID
ORDER BY tra.SEQUENCE;
As for the expected result it should hopeful return something like this:
PRODUCT_ID | ALBUM_ARTIST | ALBUM_TITLE | TRACK_NAME | OWNERSHIP
1 | Meatloaf | Bat out of Hell | Bat out of Hell | Meat loaf (50%), Johnny songwriter (50%
Can anyone please help?
You can join the 4 tables and turn on aggregation. In snowflake, listagg() can be used to aggregate strings:
select
p.product_id
p.album_artist,
p.album_title,
t.track_name,
listagg(concat(s.firstname, ' ', s.lastname, ' (', ss.ownership_share, ')')
within group (order by ss.ownership_share) ownership_share
from product p
inner join track t on t.product_id = p.product_id
inner join song_songwriter ss on ss.track_id = p.track_id
inner join songwriter s on s.songwriter_id = ss.songwriter_id
group by
p.product_id
p.album_artist,
p.album_title,
t.track_name
order by p.product_id, t.sequence;
Snowflake doesn't leverage the '+' notation when concatenating strings. You should leverage the concat() function or the || notation:
https://docs.snowflake.net/manuals/sql-reference/functions/concat.html

SQL many-to-many JOIN

i am having difficulties joining two tables.
I have the tables
Customer_table
---------------------------------------
| CustomerId(PK) | Firstname | Lastname |
---------------------------------------
CustomerInterest_table
----------------------------------------
| CustomerId(PK,FK) | InterestId(PK,FK) |
----------------------------------------
Interest_table
-------------------------------
| InterestId(PK) | InterestInfo |
-------------------------------
What i want to do is to select every customer, and join the interests with the FK reference on the table.
Ultimately i want to fetch a result containing the customers fetched from the customer table, and the customer interests fetched from the CustomerInterest_table.
Id like to build objects like this
{
customerId : 'Id12345,
firstname : 'John',
lastname : 'Doe',
interests : [{interestId : 1, interestInfo : 'Apples'}]
}
How would i go about fetching and joining the tables ?
Any help greatly appreciated.
Database design (First Normal Form) suppose that column should be simple type, in you case it mean no-array. Instead you can fetch desired from multiple selected rows:
SELECT customerId, firstname, lastname, interestId, InterestInfo
FROM Customer_table c
INNER JOIN CustomerInterest_table tc
ON c.customerId = tc.customerId
INNER JOIN Interest_table i
ON tc.InterestId = i.InterestId
ORDER BY customerId
Last ORDER BY allows you force order of rows so interests of the same customer will follow one by one.
Alternatively if customer MAY not have interests you can leverage LEFT JOIN (then two columns interestId, InterestInfo will be NULL)
SELECT customerId, firstname, lastname, interestId, InterestInfo
FROM Customer_table c
LEFT OUTER JOIN CustomerInterest_table tc
ON c.customerId = tc.customerId
INNER JOIN Interest_table i
ON tc.InterestId = i.InterestId
ORDER BY customerId
UPDATE
Alternatively (if you really want everything in single column for any cost) you can cast result to XML datatype, Then Last column will compose complex XML:
SELECT customerId, firstname, lastname
, [Name]
, (STUFF((SELECT CAST(', ' + interestId AS VARCHAR(MAX) + ':' + InterestInfo)
FROM Interest_table i
WHERE tc.InterestId = i.InterestId
FOR XML PATH ('')), 1, 2, '')) AS interests
FROM Customer_table c
INNER JOIN CustomerInterest_table tc
ON c.customerId = tc.customerId
(p.s. sorry syntax is not checked for correctness)

Identify duplicate values when using JOIN in Microsoft Access

I have 2 tables that hold values for customers. The first table holds the names of the customer and the second holds information relating to the customer such as transactions.
The first table looks like
CustomerID Name
1 Joe
2 Jane
The second table looks like
TransactID CustomerID Reference
1 1 REF123
2 2 REF123
3 1 REF321
I need to able to identify all duplicates in the reference column as well as the customer it belongs to e.g. Joe, Jane
The code i have at the moment looks like
SELECT o.name, COUNT(p.reference) as RefCount
FROM (t_cust as o
INNER JOIN t_custprop as p
ON o.customerid = p.customerid)
GROUP BY o.name, p.reference
HAVING (COUNT(p.reference)>1)
ORDER BY o.name ASC
As i don't know the possible values that may be duplicates, the code above doesn't return the name of the customer and the reference that may be a duplicate.
Grouping by the item you're counting will always result in the count being 1, which is eliminated by your having clause. This should to the trick:
SELECT o.name, p.reference
FROM t_cust o
INNER JOIN t_custprop p ON o.customerid = p.customerid
WHERE p.reference IN (
SELECT p2.reference
FROM t_custprop p2
GROUP BY p2.reference
HAVING COUNT(p2.customerid) > 1
)
ORDER BY o.name ASC

Sql create a table based on a cell value

I have a problem where I have tables that are created based on the date & time, this table is created in a SP that I didn't write. In any event need to get a count of these tables every time they are created.
What I have done so far is create a table that has these names, and added a Count field.
Table looks like this and is called SP.DBO.AUSEMAIL
SourceTable Count
SP.DBO.VIP_BPAU_00030_20130531_092027
SP.DBO.ADV_BPAU_00030_20130531_092027
Now basically I need to create a query that will give me a count of SP.DBO.VIP_BPAU_00030_20130531_092027 and SP.DBO.ADV_BPAU_00030_20130531_092027 and populate the above table.
As I will not know what the table will be called every day, and am working towards fully automating this I can't just to counts of each of these files.
I have tried something like this and am getting nowhere.
select count(*)
from top 1 (select sourcetable
from SP.DBO.AUSEMAIL
where source_table like 'SP.DBO.VIP_BPAU%')
Any ideas would be very helpful.
To update count column in your ausemail table
UPDATE a
SET a.count = i.rowcnt
FROM sysindexes AS i INNER JOIN sysobjects AS o
ON i.id = o.id JOIN ausemail a
ON o.name = a.source_table
If you know exactly the pattern you can do something like this
SELECT o.name source_table,
i.rowcnt count
FROM sysindexes AS i INNER JOIN sysobjects AS o
ON i.id = o.id
WHERE o.name LIKE '___!_BPAU!_%' ESCAPE '!' -- < adjust the pattern as needed
Sample output:
| SOURCE_TABLE | COUNT |
------------------------------------------
| VIP_BPAU_00030_20130531_092027 | 4 |
| ADV_BPAU_00030_20130531_092027 | 2 |
Here is SQLFiddle demo.