SQL one to many? - mysql

So this is the problem I am trying to solve.
Category(CatID(PK), CatName,....)
Writer(wtrID(PK), CatID(FK), wtrName,....)
Report(rpID(PK), title)
Authors(rpID(FK),wtrID(FK))
how can I create a list to show the title of the Reports with Authors that come from exactly 3 different categories ?

select r.title, group_concat(w.wtrID) as writer_ids
from Reports r
inner join Authors a on a.rpID = r.rpID
inner join Writer w on w.wtrID = a.wtrID
inner join Category c on c.CatID = w.CatID
group by r.title
having count(distinct c.CatID) = 3

Related

Extracting latest version of articles from the results of a previous query

I have the following query:
SELECT e_c.*, c.name, j.status, j.version, j.articleId, j.title FROM assetcategory AS c
INNER JOIN assetentries_assetcategories AS e_c
ON c.categoryId = e_c.categoryId AND c.name = 'news'
INNER JOIN assetentry AS e
ON e.entryId = e_c.entryId
INNER JOIN journalarticle AS j
ON j.resourcePrimKey = e.classPK
AND e.classNameId = (SELECT classNameId FROM classname_ WHERE value = 'com.liferay.portlet.journal.model.JournalArticle')
AND j.companyId= e.companyId
WHERE j.status = 0
which returns all the category news in the journalarticles. From the results I need to select the most recent versions for each articleId. For example suppose there is an article with 4 versions, even with different title, it is the same article because it will have the same articleId. So therefore for each unique articleId I need the latest version. How can I do that?
Add a join to a subquery which finds the most recent version for each article:
SELECT e_c.*, c.name, j1.status, j1.version, j1.articleId, j1.title
FROM assetcategory AS c
INNER JOIN assetentries_assetcategories AS e_c
ON c.categoryId = e_c.categoryId AND c.name = 'news'
INNER JOIN assetentry AS e
ON e.entryId = e_c.entryId
INNER JOIN journalarticle AS j1
ON j1.resourcePrimKey = e.classPK AND
e.classNameId = (SELECT classNameId FROM classname_
WHERE value = 'com.liferay.portlet.journal.model.JournalArticle') AND
j.companyId = e.companyId
INNER JOIN
(
SELECT articleId, MAX(version) AS max_version
FROM journalarticle
WHERE status = 0
GROUP BY articleId
) j2
ON j1.articleId = j2.articleId AND j1.version = j2.max_version;
The basic idea behind the join to the subquery aliased as j2 above is that it restricts the result set to only the most recent version of each article. We don't necessarily have to change the rest of the query.

Joining to table with multiple FKs and Rows

I have the following query:
SELECT p.id,
p.firstname,
**p.address1id,
p.address2id,**
r.invoice_id,
i.authcode
FROM membershiprenewals r,
Profile p,
Invoice i
WHERE r.orgID = 1
and r.period_id = 3
and r.status = 0
and r.profile_id = p.id
and r.invoice_id = i.id;
This table selects a Users Profile and a few related details.
A Profiles Addresses are stored in another table (profileaddress). And a Profile can have 2 addresses. These addresses are referenced using p.address1 and p.address2.
I need to extend this query to join on the profileaddress table to get BOTH addresses and combined into the single record.
So the results I would need would be the following columns
p.id | p.firstname | .. etc .. | profileaddress1.address | profileaddress1.town | profileaddress2.address | profileaddress2.town | .. etc
I've been playing around with JOIN statements for hours, but just can't seem to crack it.
Any help hugely Appreciated !!
Jason
First, never use commas in the FROM clause. Always use proper, explicit JOIN syntax. So, your query should be:
SELECT p.id, p.firstname, **p.address1id, p.address2id,**
r.invoice_id, i.authcode
FROM membershiprenewals r JOIN
Profile p
ON r.profile_id = p.id JOIN
Invoice i
ON r.invoice_id = i.id
WHERE r.orgID = 1 AND r.period_id = 3 AND r.status = 0;
Then you want two joins to the address table:
SELECT p.id, p.firstname, p.address1id, p.address2id,
pa1.address, pa1.town,
pa2.address, pa2.town,
r.invoice_id, i.authcode
FROM membershiprenewals r JOIN
Profile p
ON r.profile_id = p.id JOIN
Invoice i
ON r.invoice_id = i.id LEFT JOIN
profileaddress pa1
ON p.address1id = pa1.id LEFT JOIN
profileaddress pa2
ON p.address2id = pa2.id
WHERE r.orgID = 1 AND r.period_id = 3 AND r.status = 0;
This uses LEFT JOIN in case one of the addresses is missing.

Selecting from different tables based on condition

I Have a table comments ->
id | comment | type | userid |
1 Hello human 9
2 Hi robot 4
3 Gnaw! animal 1
4 Boo ghost 2
Also i have four more tables human,robot,ghost and animal
These tables contains some basic details about themselves...
Now I have a know value of comment say : $id = 3
if i do
$data = mysqli_query($con,"SELECT type FROM comments WHERE id = $id");
while($row = mysqli_fetch_assoc($data)){
$table = $row['type'];
$table_data = mysqli_fetch_assoc(mysqli_query($con,"SELECT * FROM $table"));
}
this will fetch me all the data about the one who commented but this will prove to be too slow....is there any way i can combine this in one single query ?
One way to do this is with left joins.
SELECT c.type, COALESCE(h.detail,r.detail,a.detail,g.detail)
FROM comments
LEFT JOIN human h ON c.type = 'human' AND c.id = h.id
LEFT JOIN robot r ON c.type = 'robot' AND c.id = r.id
LEFT JOIN animal a ON c.type = 'animal' AND c.id = a.id
LEFT JOIN ghost g ON c.type = 'ghost' AND c.id = g.id
Another way would be to do a UNION on the four tables and then join those:
SELECT c.type, q1.detail
FROM comments c
LEFT JOIN
(
SELECT 'human' AS type, detail FROM human
UNION
SELECT 'robot', detail FROM robot
(etc.)
) q1 ON c.type = q1.type AND q1.id = c.id
I would prefer the second option, because this one makes it easier to join lots of detail-columns. I don't think there's much of a difference perfomance-wise.

MySQL Joins where one value can be optional

Situation
I have a database which heavily makes use of joins due to the various situations in which each entity is used. Here is a simplified diagram:
Goal
I would like to be able to get details of all modules and the "name" fields regardless of whether the "fk_chapter_id" within user_has_module is set or not.
In the case where "user_has_module.fk_chapter_id" is null, the system can return details of the module and then null chapter.
In the case where there is a user_has_module, I would like to get the status
Issue
Whenever I perform SQL statements, I get the results only partially returned. I.E. If I have 4 module records in total, two of which where the user has an entry in "user_has_module" returns the two records in full and then 2 null records for the other modules.
Update based on feedback, almost there
Now, the only problem is I get duplicates. Using some test data
SELECT DISTINCT
chapter_id,
chapter_name,
module_id,
module_name,
(null ) AS user_module_progress,
(SELECT COUNT(fk_chapter_id) FROM module_has_chapter WHERE fk_module_id = m.module_id) AS chapter_count
FROM
module as m
LEFT JOIN
module_has_chapter as mhc ON m.module_id = mhc.fk_module_id
LEFT JOIN
chapter as c ON mhc.fk_chapter_id = c.chapter_id
group by m.module_id
UNION
SELECT DISTINCT
chapter_id,
chapter_name,
module_id,
module_name,
user_module_progress,
(SELECT COUNT(fk_chapter_id) FROM module_has_chapter WHERE fk_module_id = m.module_id) AS chapter_count
FROM
module as m
LEFT JOIN
user_has_module as uhm ON m.module_id = uhm.fk_module_id
LEFT JOIN
user as u ON uhm.fk_user_id = u.user_id
LEFT JOIN
chapter as c ON uhm.fk_latest_chapter_id = c.chapter_id
WHERE u.user_id = 2
group by m.module_id;
I got there in the end but, not particularly happy about it. This works but, it's a bloody mess...Does anyone have a better solution please?
SELECT DISTINCT
(null) AS chapter_id,
(null) AS chapter_name,
module_id,
module_name,
(null ) AS user_module_progress,
(SELECT COUNT(fk_chapter_id) FROM module_has_chapter WHERE fk_module_id = m.module_id) AS chapter_count
FROM
module as m
LEFT JOIN
user_has_module as uhm ON m.module_id = uhm.fk_module_id
WHERE
uhm.fk_user_id IS NULL
UNION ALL
SELECT DISTINCT
chapter_id,
chapter_name,
module_id,
module_name,
user_module_progress,
(SELECT COUNT(fk_chapter_id) FROM module_has_chapter WHERE fk_module_id = m.module_id) AS chapter_count
FROM
module as m
LEFT JOIN
user_has_module as uhm ON m.module_id = uhm.fk_module_id
INNER JOIN
user as u ON uhm.fk_user_id = u.user_id
INNER JOIN
chapter as c ON uhm.fk_latest_chapter_id = c.chapter_id
WHERE
u.user_id = 2;

MySQL / PHP - 2 different arguments for 1 table

I have the following SQL:
$queryString = "
SELECT
iR.lastModified,
d.*,
c2.title as stakeholderTitle,
u.username as authorUsername,
c.title as authorContactName,
GROUP_CONCAT(iR.stakeholderRef) AS participants
FROM
informationRelationships iR,
contacts c2
INNER JOIN
debriefs d ON
d.id = iR.linkId
LEFT JOIN
users u ON
u.id = iR.author
LEFT JOIN
contacts c ON
c.ref = u.contactId
LEFT JOIN
debriefs d2 ON
d2.stakeholder = c2.ref
WHERE
(
iR.clientRef = '$clientRef' OR
iR.contactRef = '$contactRef'
)
AND
iR.projectRef = '$projectRef' AND
iR.type = 'Debrief'
GROUP BY
iR.linkId
ORDER BY
d.dateOfEngagement
";
notice how I require 2 different bits of data for the the contacts table.
So at one point, I need to match
c.ref = u.contactId
This will return one bit of information
but I also need a completely different grouping:
d2.stakeholder = c2.ref
Problem is that the title is the column i'm interested in for both:
c2.title as stakeholderTitle,
...
c.title as authorContactName
How do I go about doing this?
My current try is returning:
Error: Unknown column 'iR.linkId' in 'on clause'
I'm not sure I really understand what is happening here:
how to join two tables on common attributes in mysql and php?
EDIT::::---ANSWERED--zerkms
$queryString = "
SELECT
iR.lastModified,
d.*,
c2.title as stakeholderTitle,
u.username as authorUsername,
c.title as authorContactName,
GROUP_CONCAT(iR.stakeholderRef) AS participants
FROM
informationRelationships iR
INNER JOIN
debriefs d ON
d.id = iR.linkId
INNER JOIN
contacts c2 ON
d.stakeholder = c2.ref
LEFT JOIN
users u ON
u.id = iR.author
LEFT JOIN
contacts c ON
c.ref = u.contactId
WHERE
(
iR.clientRef = '$clientRef' OR
iR.contactRef = '$contactRef'
)
AND
iR.projectRef = '$projectRef' AND
iR.type = 'Debrief'
GROUP BY
iR.linkId
ORDER BY
d.dateOfEngagement
";
By re-ordering my query I have managed to get both columns in... Thanks zerkms!
You cannot mix implicit joins and explicit joins in a single query in mysql.
So
FROM informationRelationships iR,
contacts c2
should be rewritten to
FROM informationRelationships iR
INNER JOIN contacts c2 ON ...
Do not use cartesian product and joins in the same query (not subquery), here, use only joins (CROSS JOIN is the same as cartesian product).