Can't figure out a triple joint with group by - sql

I want to get the Creators of Artworks, GROUP BY by their role.
I find a way to have the list of the Creators with their role, for each Artwork.
SELECT
JSON_OBJECT('id', a.IDartwork, 'creaDate', a.artworkCreationDate, 'length', a.length, 'summarize', a.summarize) AS artwork,
(SELECT JSON_ARRAYAGG(JSON_OBJECT('id', c.IDcreator, 'name', c.creatorName, 'role', r.roleName))
FROM AMEGA_creator c
JOIN AMEGA_artwork_creator ac ON c.IDcreator = ac.IDcreator
JOIN AMEGA_role r ON r.IDrole = ac.IDrole
WHERE ac.IDartwork = a.IDartwork) AS creators
FROM
AMEGA_artwork a;
But I can't get to use the GROUP BY. I get the error:
Subquery returns more than 1 row
I can't find a solution. I want a result like that :
artworks creators
<art1> < <"director", <crea1, crea26>>, <"writer", <crea5, crea12>> >
<art2> < <"drawer", <crea23, crea8>> >
Here is the MLD structure :
Any idea ?

Here's a bit of a guess on how this should look from my comment. Essentially somewhere in your SQL (either via correlated subquery's where clause or through a single SELECT's FROM clause) you have to establish a relationship between AMEGA_artwork and the rest of the tables. You went with a correlated subquery which is a good choice. The issue though is that for each AMEGA_artwork record there is more than one aggregated JSON_ARRAYAGG() result in that subquery. Normally this would be fine, but because your subquery is in your SELECT clause you can only have a single record come through for each AMEGA_artwork record. Otherwise you get that error.
I've switched this to a single FROM clause to establish the relationships between your table, and then a single JSON_OBJECT leading to your artwork output column and an aggregated JSON_ARRAYAGG() of the remaining column.
SELECT
JSON_OBJECT('id', a.IDartwork, 'creaDate', a.artworkCreationDate, 'length', a.length, 'summarize', a.summarize) AS artwork,
JSON_ARRAYAGG(JSON_OBJECT('id', c.IDcreator, 'name', c.creatorName, 'role', r.roleName)
FROM
AMEGA_artwork a
JOIN AMEGA_artwork_creator ac
ON a.IDartwork = ac.IDartwork
JOIN AMEGA_creator c
ON c.IDcreator = ac.IDcreator
JOIN AMEGA_role r
ON r.IDrole = ac.IDrole
GROUP BY artwork;
The different here is that we establish the relationship between AMEGA_artwork and AMEGA_artwork_creator in the FROM clause.
What you will likely see on running this is more than one record for each artwork. Which one is correct, or which one you want to keep, or how you want to further aggregate the data to reduce it to a single record, will be up to you.

Related

When I try to get MIN value of manufactured parts I get (Only one expression ... when the subquery is not introduced with EXISTS)

I try to get MIN value of manufactured parts grouped by project like so:
This is my query:
SELECT
proinfo.ProjectN
,ProjShipp.[Parts]
,ProjShipp.Qty AS 'Qty Total'
,Sum(DailyProduction.Quantity) AS 'Qty Manufactured'
,(SELECT DailySumPoteau.IdProject, MIN(DailySumPoteau.DailySum)
FROM (SELECT PShipp.IdProject, SUM(DailyWelding.Quantity) DailySum
FROM DailyWeldingPaintProduction DailyWelding
INNER JOIN ProjectShipping PShipp ON PShipp.id=DailyWelding.FK_idPartShip
WHERE PShipp.id=ProjShipp.id
GROUP BY PShipp.id,PShipp.IdProject)DailySumPoteau
GROUP BY DailySumPoteau.IdProject ) AS 'Qt Pole'
FROM [dbo].[DailyWeldingPaintProduction] DailyProduction
INNER join ProjectShipping ProjShipp on ProjShipp.id=DailyProduction.FK_idPartShip
inner join ProjectInfo proinfo on proinfo.id=IdProject
GROUP By proinfo.id
,proinfo.ProjectN
,ProjShipp.[Parts]
,ProjShipp.Qty
,ProjShipp.[Designation]
,ProjShipp.id
I have three tables:
01 - ProjectInfo: it stores information about the project:
02 - ProjectShipping: it stores information about the parts and it has ProjectInfoId as foreign key:
03 - DailyWeldingPaintProduction: it stores information about daily production and it has ProjectShippingId as foreign key:
but when I run it I get this error:
Msg 116, Level 16, State 1, Line 13
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
How can I solve this problem?.
From your target results, I suspect that you want a window MIN(). Assuming that your query works and generates the correct results when the subquery is removed (column QtPole left apart), that would be:
SELECT pi.ProjectN, ps.[Parts], ps.Qty AS QtyTotal,
SUM(dp.Quantity) AS QtyManufactured,
MIN(SUM(dp.Quantity)) OVER(PARTITION BY pi.ProjectN) AS QtPole
ps.Designation
FROM [dbo].[DailyWeldingPaintProduction] dp
INNER join ProjectShipping ps on ps.id=dp.FK_idPartShip
INNER join ProjectInfo pi on pi.id=IdProject
GROUP BY pi.id, pi.ProjectN, ps.[Parts], ps.Qty, ps.Designation, ps.id
Side note: don't use single quotes for identifiers; they should be reserved for literal strings only. Use the proper quoting character for your database (in SQL Server: square brackets) - or better yet, use identifiers that do not require being quoted.
Formulating the query in the way you have done is not necessarily the best solution. As the other solution mentions, the best method in this instance is probably to use a window function / OVER. But since this can depend on indexes, and also to understand what went wrong, I will give you the way to fix the original query.
The issue with your query is that it has a correlated subquery in the SELECT which returns two values. What you are trying to do can be done in RDBMSs that support row constructors, unfortunately SQl Server is not one of them.
What you are trying to get at here is to get a whole resultset per row of the table.
The correct syntax for your query is to APPLY the resultset of the subquery for every row. You can CROSS APPLY in this instance because you are guaranteed a result anyway due to the correlation:
SELECT
proinfo.ProjectN
,ProjShipp.[Parts]
,ProjShipp.Qty AS 'Qty Total'
,Sum(DailyProduction.Quantity) AS 'Qty Manufactured'
,QtPole.IdProject
,QtPole.MinDailySum
FROM [dbo].[DailyWeldingPaintProduction] DailyProduction
INNER join ProjectShipping ProjShipp on ProjShipp.id=DailyProduction.FK_idPartShip
inner join ProjectInfo proinfo on proinfo.id=ProjShipp.IdProject
CROSS APPLY (
SELECT DailySumPoteau.IdProject, MIN(DailySumPoteau.DailySum) MinDailySum
FROM (SELECT DailyWelding.FK_idPartShip IdProject, SUM(DailyWelding.Quantity) DailySum
FROM DailyWeldingPaintProduction DailyWelding
WHERE DailyWelding.FK_idPartShip=ProjShipp.id
GROUP BY DailyWelding.FK_idPartShip) DailySumPoteau
GROUP BY DailySumPoteau.IdProject
) AS QtPole
GROUP By proinfo.id
,proinfo.ProjectN
,ProjShipp.[Parts]
,ProjShipp.Qty
,ProjShipp.[Designation]
,ProjShipp.id
,QtPole.IdProject
,QtPole.MinDailySum
I have taken the liberty of cleaning up the subquery by removing the unnecessary ProjectShipping reference. Note that the addition of grouping columns here does not matter because of the correlation to ProjShipp.Id
Note also that depending on indexes and density and such like, it may be better to formulate the subquery as a JOIN instead, with the correlation on the outside in the ON. You would need to modify the grouping in that case.

Applying two separate filters on a Rails database query

I am trying to apply two filters to a database query in Rails 3. The first filter shows only media of type images. The second filter shows the highest saluted stories. On their own the filters work ok, but when I try to combine both filters, I get errors.
There are 3 tables involved. Stories, memories, and salutes. The salutes table keeps track of how many times someone 'salutes' a memory. Each story is composed of multiple memories. A story's total salutes is the sum of the salutes of that story's memories. I want to retrieve records of image-only stories in the order of highest to lowest salutes.
models/story.rb
def self.where_contains_image()
joins(
'INNER JOIN memories AS wci_memories ON wci_memories.story_id = stories.id'
)
.where(
'wci_memories.media_type_cd = ?', Memory.image
).uniq
end
controllers/stories_controller.rb
if params[:filter_content] == 'image'
stories = stories.where_contains_image
end
if (params[:filter_trends] == 'most_saluted')
stories = stories.order("(SELECT COUNT(1) FROM salutes
LEFT JOIN memories AS ms_memories ON salutes.content_id = ms_memories.id
LEFT JOIN stories AS ms_stories ON ms_stories.id = ms_memories.story_id
WHERE ms_stories.id = stories.id AND salutes.content_type = 'Memory')
DESC");
end
On its own, when the 'most_saluted' param is set, the query works as expected. When both the 'most_saluted' param and the 'image' param are set, I get an error:
for SELECT DISTINCT, ORDER BY expressions must appear in select list
I understand what the error is, but I cannot figure out how to rewrite the queries so that it can return only images in the order of most saluted.
When I run this SQL query on the database, it returns the records I'm looking for. But I cannot figure out how to make rails return the same records. Furthermore, this query combines the two filters (only images and highest salutes). I want to keep them separate so that I can apply one filter individually, or both together.
SELECT DISTINCT stories.*, (SELECT COUNT(1) FROM salutes
LEFT JOIN memories AS ms_memories ON salutes.content_id = ms_memories.id
LEFT JOIN stories AS ms_stories ON ms_stories.id = ms_memories.story_id
WHERE ms_stories.id = stories.id AND salutes.content_type = 'Memory')
AS total_salutes FROM stories INNER JOIN memories AS wci_memories
ON wci_memories.story_id = stories.id WHERE wci_memories.media_type_cd = 0
ORDER BY total_salutes DESC
Any thoughts on how I can resolve this?
You can use scope to achieve this, actually the Activerecord scope are the more cleaner/moduler way to chain conditions
read here about scopes
HTH

Joining tables, counting, and group to return a Model

So I've got a SQL query I'd like to duplicate in rails:
select g.*
from gamebox_favorites f
inner join gameboxes g on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc;
I've been reading over the rails Active Record Query Interface site, but can't quite seem to put this together. I'd like the query to return a collection of Gamebox records, sorted by the number of 'favorites' a gamebox has. What is the cleanest way to do this in rails?
I believe this will work (works on a similarly structured database locally), though I'm not sure I have the proper models in the proper spots for what you're trying to do, so you might need to move a coule things around:
Gamebox.joins(:gamebox_favorites).
group('"gamebox_favorites"."gamebox_id"').
order('count("gamebox_favorites"."gamebox_id")')
On the console, this should compile to (in the case of PostgreSQL on the back end):
SELECT "gameboxes".* FROM "gamebox_favorites"
INNER JOIN "gamebox_favorites"
ON "gamebox_favorites"."gamebox_id" = "gamebox"."id"
GROUP BY "gamebox_favorites"."gamebox_id"
ORDER BY count("gamebox_favorites"."gamebox_id")
...and I'm guessing that you don't want do just wrap it in a find_by_sql call, such as:
Gamebox.find_by_sql("select g.* from gamebox_favorites f
inner join gameboxes g
on f.gamebox_id = g.id
group by f.gamebox_id
order by count(f.gamebox_id) desc")

Two identical queries that give different results

I have 2 queries: one is written in ANSI SQL, another is written using oracle dialect.
I think that they both must give the same resultset, but it is no true. First query gives 385 rows and the second - only 25
First:
SELECT idclient, cl.surname, sum(sub1.s)
FROM client cl JOIN incomestatement incst USING(idclient)
JOIN (SELECT c.idincome ID, sum(inst.total) AS s
FROM instalment inst JOIN credit c USING(idcredit)
WHERE inst.paydate > c.paydate AND c.isloaned = 1
GROUP BY c.idincome) sub1 ON incst.idincome = sub1.ID
GROUP BY idclient, cl.surname;
Second:
SELECT c.idclient, c.surname, sum(sub.s)
FROM client c, incomestatement inc,
(SELECT sum(inst.total) as s, cr.idincome as id
FROM instalment inst, credit cr
WHERE inst.paydate > cr.paydate AND cr.isloaned = 1 AND cr.idcredit = inst.idcredit
GROUP BY cr.idincome
) sub
WHERE c.idclient = inc.idclient AND inc.income = sub.ID
group by c.idclient, c.surname;
So why they don't give the same result?
I'd approach the problem in steps.
Do the two sub-queries produce the same data sets?
If they do, proceed to step 2.
If not, then you have two simpler queries to analyze and dissect.
Given that the pair of sub-queries produce the same answer, you can then establish whether the Client and IncomeStatement joins give the same results (treat it as another sub-query)
If they do, proceed to step 3.
If not, then you have a pair of queries (one with JOIN, one with classic SQL notation) to analyze and dissect.
Given that the pair of joins and the pair of subqueries each produce the same result, analyze why the join of these does not work correctly.
Have you made a commit?
It's possible that you donĀ“t commited some transactions, so the results can be differents.

MySQL Join from multiple options to select one value

I am putting together a nice little database for adding values to options, all these are setup through a map (Has and Belongs to Many) table, because many options are pointing to a single value.
So I am trying to specify 3 option.ids and a single id in a value table - four integers to point to a single value. Three tables. And I am running into a problem with the WHERE part of the statement, because if multiple values share an option there are many results. And I need just a single result.
SELECT value.id, value.name FROM value
LEFT JOIN (option_map_value, option_table)
ON (value.id = option_map_value.value_id AND option_map_value.option_table_id = option_table.id)
WHERE option_table.id IN (5, 2, 3) AND value.y_axis_id = 16;
The problem with the statement seems to be the IN on the WHERE clause. If one of the numbers are different in the IN() part, then there are multiple results - which is not good.
I have tried DISTINCT, which again works if there is one result, but returns many if there is many. The closest we have gotten to is adding a count - to return to value with the most options at the top.
So is there a way to do the WHERE to be more specific. I cannot break it out into option_table.id = 5 AND option_table.id = 2 - because that one fails. But can the WHERE clause be more specifc?
Maybe it is me being pedantic, but I would like to be able to return just the single result, instead of a count of results... Any ideas?
The problem with the statement seems to be the IN on the WHERE clause. If one of the numbers are different in the IN() part, then there are multiple results - which is not good. I have tried DISTINCT, which again works if there is one result, but returns many if there is many. The closest we have gotten to is adding a count - to return to value with the most options at the top.
You were very close, considering the DISTINCT:
SELECT v.id,
v.name
FROM VALUE v
LEFT JOIN OPTION_MAP_VALUE omv ON omv.value_id = v.id
LEFT JOIN OPTION_TABLE ot ON ot.id = omv.option_table_id
WHERE ot.id IN (5, 2, 3)
AND v.y_axis_id = 16
GROUP BY v.id, v.name
HAVING COUNT(*) = 3
You were on the right track, but needed to use GROUP BY instead in order to be able to use the HAVING clause to count the DISTINCT list of values.
Caveat emptor:
The GROUP BY/HAVING COUNT version of the query is dependent on your data model having a composite key, unique or primary, defined for the two columns involved (value_id and option_table_id). If this is not in place, the database will not stop duplicates being added. If duplicate rows are possible in the data, this version can return false positives because a value_id could have 3 associations to the option_table_id 5 - which would satisfy the HAVING COUNT(*) = 3.
Using JOINs:
A safer, though more involved, approach is to join onto the table that can have multiple options, as often as you have criteria:
SELECT v.id,
v.name
FROM VALUE v
JOIN OPTION_MAP_VALUE omv ON omv.value_id = v.id
JOIN OPTION_TABLE ot5 ON ot5.id = omv.option_table_id
AND ot5.id = 5
JOIN OPTION_TABLE ot2 ON ot2.id = omv.option_table_id
AND ot2.id = 2
JOIN OPTION_TABLE ot3 ON ot3.id = omv.option_table_id
AND ot3.id = 3
WHERE v.y_axis_id = 16
GROUP BY v.id, v.name