Trouble concatenating rows into string with multiple joins - sql

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

Related

Postgres join and count multiple relational tables

I want to join the 2 tables to the first table and group by a vendor name. I have three tables listed below.
Vendors Table
| id | name
|:-----------|------------:|
| test-id | Vendor Name |
VendorOrders Table
| id | VendorId | Details | isActive(Boolean)| price |
|:-----------|------------:|:------------:| -----------------| --------
| random-id | test-id | Sample test | TRUE | 5000
OrdersIssues Table
| id | VendorOrderId| Details. |
|:-----------|--------------:-----------:|
| order-id | random-id | Sample test|
The expected output is to count how many orders belong to a vendor and how many issues belongs to a vendor order.
I have the below code but it's not giving the right output.
SELECT "vendors"."name" as "vendorName",
COUNT("vendorOrders".id) as allOrders,
COUNT("orderIssues".id) as allIssues
FROM "vendors"
LEFT OUTER JOIN "vendorOrders" ON "vendors".id = "vendorOrders"."vendorId"
LEFT OUTER JOIN "orderIssues" ON "orderIssues"."vendorOrderId" = "vendorOrders"."id"
GROUP BY "vendors".id;```
You need the keyword DISTINCT, at least for allOrders:
SELECT v.name vendorName,
COUNT(DISTINCT vo.id) allOrders,
COUNT(DISTINCT oi.id) allIssues
FROM vendors v
LEFT OUTER JOIN vendorOrders vo ON v.id = vo.vendorId
LEFT OUTER JOIN orderIssues oi ON oi.vendorOrderId = vo.id
GROUP BY v.id, v.name;
Consider using aliases instead of full table names to make the code shorter and more readable.
You are joining along two related dimensions. The overall number of rows is the number of issues. But to get the number of orders, you need a distinct count:
SELECT v.*, count(distinct vo.id) as num_orders,
COUNT(oi.vendororderid) as num_issues
FROM vendors v LEFT JOIN
vendorOrders vo
ON v.id = vo.vendorId LEFT JOIN
orderIssues oi
ON oi.vendorOrderId = vo.id
GROUP BY v.id;
Notes:
Table aliases make the query easier to write and to read.
Quoting column and table names makes the query harder to write and read. Don't quote identifiers (you may need to recreate the tables).
Postgres support SELECT v.* . . . GROUP BY v.id assuming that the id is the primary key (actually, it only needs to be unique). This seems like a reasonable assumption.

How to resolve an id by an foreign table

I want to resolve an ID by another table, where the name of this id is stored.
SELECT d.id_data, string_agg(s.name_last,', ') AS authors, d.title, i.name
FROM data d, institution i, staffs s
WHERE d.id_staffs = s.id_staffs
AND d.id_institution = i.id_institution
GROUP BY d.id_data limit 100 ;
But how can I get the name of my Institution. I want that the SELECT shows me the institution name, which has stored the data. Something like that
id_data | authors | title | name
----------------------------------------------------------------
1 |Mustermann, Musterfrau | sunmaker | university cologne
2 |Schmidt, Müller | dry age | university berlin
I just need to resolve the id of the institution to his name.
Always use proper, explicit, standard JOIN syntax. Never use commas in the FROM clause.
Presumably, you want something like this:
SELECT d.id_data, string_agg(s.name_last,', ') AS authors,
d.title, i.name
FROM data d JOIN
institution i
ON d.id_staffs = s.id_staffs JOIN
staffs s
ON d.id_institution = i.id_institution
GROUP BY d.id_data, d.title, i.name
LIMIT 100 ;
That is, fix the GROUP BY to have all the unaggregated columns.

How to use LISTAGG to concatenate from multiple rows?

I have report query along these lines in APEX 5.0:
WITH inner_table AS
( select distinct
i.ID
,i.name
,i.lastname
,case i.gender
when 'm' then 'Male'
when 'f' then 'Female'
end gender
,i.username
,b.name region
,i.address
,i.city city
,i.EMAIL
,r.name as "ROLE"
,ie.address as "region_location"
,case
when i.gender='m' THEN 'blue'
when i.gender='f' THEN '#F6358A'
END i_color
,b.course as COURSE
,si.city UNIVERSITY
,case
when i.id in (select app_user from scholarship) then 'check'
else 'close'
end as scholarship,
case
when i.id in (select ieur.app_user from ie_user_role ieur where role=4) then 'Admin'
else ''
end admin,
apex_item.checkbox(10, i.id, 'UNCHECKED onclick="highlightRow(this);"') as Del_usr
from app_users i left join regions b on (i.region=b.id)
left join ie_user_role ur on (i.id = ur.app_user)
left join ie_roles r on(ur.role = r.id)
left join user_house uh on (i.id=uh.app_user)
left join reg_location ie on (uh.house=ie.id)
left join study_list sl on i.id = sl.insan
left join study_institute si on sl.institute = si.id
left join course c on sl.course = c.id
where i.is_active='Y'
order by
i.name,i.lastname,i.username,region, city, i.EMAIL)
SELECT * FROM inner_table where (scholarship = :P5_SCHOLARSHIP or :P5_SCHOLARSHIP is null)
I might get results like this:
|---------------------|------------------|-------|------------------|
| Name | Lastname | ... | Course |
|---------------------|------------------|-------|------------------|
| Some | User | ... | Course1 |
|---------------------|------------------|-------|------------------|
| Some | User | ... | Course2 |
|---------------------|------------------|-------|------------------|
But I would like to achieve enlisted courses in same row, that was repeating previously, so:
|---------------------|------------------|-------|------------------|
| Name | Lastname | ... | Course |
|---------------------|------------------|-------|------------------|
| Some | User | ... | Course1, Course2 |
|---------------------|------------------|-------|------------------|
I tried using LISTAGG, and I didn't note down my attempts, so unfortunately I can't post that now. I basically tried:
,LISTAGG(b.course, ', ') within group (order by b.course) as COURSE
Then adding GROUP BY using COURSE, but in that case whole query is affected by GROUP BY and I have to apply other columns correctly, right? Otherwise its resulting in "ORA-00937: not a single-group group function". I got lost a bit there.
Other thing I tried is using a subquery table with same LISTAGG line above, and got wanted output from subquery, but then joining to the rest of the query didn't provide expected results.
I think I could use a bit of SQL help here for LISTAGG when joining multiple tables.
Thanks.
When you use an aggregate function (that collapses multiple rows into one) you need a GROUP BY clause, so you'd need something like this:
SELECT i.username,
LISTAGG( c.course, ', ' ) WITHIN GROUP ORDER BY ( c.course )
FROM app_users i
...
LEFT JOIN course c on sl.course = c.id
GROUP BY i.username
Basically, anything that's not being aggregated, needs to be in the GROUP BY clause. Try it in a much simpler query until you get the hang of it, then make your big one.
What you want is LISTAGG with an analytical window function. Then remove duplicates using distinct. Here is my sample result/ data: http://sqlfiddle.com/#!4/6e8e3f/3
Select DISTINCT name, last_name, other columns,
LISTAGG(course, ', ') WITHIN GROUP (ORDER BY course)
OVER (PARTITION BY name, last_name) as "Course"
FROM inner_table;

Select with count on 3 tables

I need your help for a particular SELECT on 3 tables. I'm not skilled on SQL so it's a difficult SELECT for me, since I have to apply COUNT (I suppose) to the query.
I show you my tables:
I need to know how many contacts there are in the database (all the contacts!!!!) and how many photos and videos are bound to any contact.
I should get a result similar to this:
-----------------------------------
| ID | NAME | PHOTO | VIDEO |
-----------------------------------
| 1 | MARK | 3 | 1 |
-----------------------------------
| ID | LISA | 2 | 0 |
-----------------------------------
Thank you for your help
You can use the following approach, if you are hesitant about duplicates in the query you can use a sql function and pass type parameter as a string. If you have uncertain number of types (VIDEO, PHOTO, TEXT etc) you need to redesign the output table format (I would go with the following tuple TYPE, CONTACT_ID, COUNT), or at the worst case go with dynamic query construction.
select c.ID, c.NAME,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'PHOTO' where cm.ID_CONTACT = c.ID) as PHOTO,
(select count(*) from CONTACT_MEDIA cm join MEDIA m on
m.ID = cm.ID_MEDIA and m.TYPE = 'VIDEO' where cm.ID_CONTACT = c.ID) as VIDEO
from CONTACT c
Please use below query , this will give you exact result
select contact_media.ID_Contact, contact.Name, count(M1.ID) as 'PHOTO', COUNT(M2.ID) as 'VIDEO' from Contact inner join contact_media on Contact.ID=contact_media.ID_Contact
left outer join media M1 on contact_media.ID_Media=M1.ID and M1.TYPE='PHOTO'
left outer join media M2 on contact_media.ID_Media=M2.ID and M2.TYPE='VIDEO'
group by contact_media.ID_Contact, contact.Name

Combine multiple row values into one row

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.