Using subqueries with multiple results which depend on the main query - mysql

I have a persons table called "tc_person" and a marriage table called "tc_marriage". I want to select a few columns from my persons table and one column which represents the id of the partner.
The marriage table includes the pid_1 and pid_2 of two people - but it is important that there is only one entry for a couple and the order of the couples ids may vary. Here's the tables:
tc_person:
| id | name | lastname |
--------------------------------------
| 4 | peter | smith |
| 5 | sarah | smith |
tc_marriage:
| id | pid_1 | pid_2 |
--------------------------------------
| 0 | 5 | 4 |
| 1 | 7 | 9 |
It seems that my subquery is interpreted as a whole before the original select statement. Now I get the error that my subquery returns more than one row.
SELECT p.id, p.name, p.lastname,
(SELECT m.pid_1 FROM tc_marriage m WHERE m.pid_2 = p.id UNION
SELECT m.pid_2 FROM tc_marriage m WHERE m.pid_1 = p.id) as partner_id
FROM tc_person p WHERE p.lastname LIKE 'smith';
I am looking for the following output:
| id | name | lastname | partner_id |
-----------------------------------------------------
| 4 | peter | smith | 5 |
| 5 | sarah | smith | 4 |
Is this even possible with only one single query? You can probably tell by now that I'm quite the SQL noob. Maybe you guys can help.

You can use IN to avoid a UNION (which is typically slower), and a CASE statement to pick out the correct partner_id:
SELECT p.id,
p.name,
p.last_name,
CASE p.id WHEN m.pid_1 THEN m.pid_2 ELSE m.pid_1 END AS partner_id
FROM tc_person p
JOIN tc_marriage m ON p.id IN (m.pid_1, m.pid_2)

What you want to do is to join the person table to the marriage table. However, the problem is that you have two keys. One solution is to do two joins and then some logic to choose the right value.
I prefer to "double" the marriage table by swapping the partners. The following query takes this approach in a subquery:
select p.id, p.name, p.lastname, partner_id
from tc_person p join
(select pid_1 as firstid, pid_2 as partner_id
from marriage
union all
select pid_2 as firstid, pid_1 as partner_id
from marriage
) m
on p.id = m.firstid

Related

How to exclude rows when a certain condition is met in MySQL

I have two datatables. One table contains information of participants (Participants). And a table which contains all registrations for the event (Registrars).
+----+-------+
| id | name |
+----+-------+
| 1 | Peter |
+----+-------+
| 2 | John |
+----+-------+
+-----------+----------+
| person_id | event_id |
+-----------+----------+
| 1 | 1 |
+-----------+----------+
| 2 | 2 |
+-----------+----------+
| 2 | 1 |
+-----------+----------+
I run a MySQL query to get the information of registrars of an event. The query looks like this:
SELECT name
FROM Participants
INNER JOIN Registrars
ON Participants.id=Registrars.person_id
WHERE
event_id = 1
Now I want to build a query where if the participant also registered for event 2 it will not be returned in the result of the query the reg. How can I achieve this in one query?
You can group by participant and use conditional aggregation in the HAVING clause to set the conditions:
SELECT p.id, p.name
FROM Participants p INNER JOIN Registrars r
ON p.id = r.person_id
WHERE r.event_id IN (1, 2)
GROUP BY p.id, p.name
HAVING MAX(r.event_id = 1) = 1
AND MAX(r.event_id = 2) = 0
You can extend the code by adding more conditions in a similar way.
SELECT name
FROM Participants as p
INNER JOIN Registrars as reg
ON P.id=Reg.person_id
WHERE
reg.event_id = 1
and not exists (select 1 from Registrars as strr where strr.event_id=2 and p.id=strr.person_id )

MySQL GROUP_CONCAT with SUM() and multiple JOINs inside subquery

I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;

Mysql SELECT display only 1 result instead of multiple from 3 tables

I try to make one SQL request where I get all data from multiple tables. Problem start when one user have for example more "enforcement". But first lets look on code:
SELECT c.id, c.firstname, c.surname, c.email, c.process, c.search_work, c.note,
MAX(CASE WHEN cl.languageID = 1 THEN cl.skill ELSE '-' END)AS 'en',
MAX(CASE WHEN cl.languageID = 2 THEN cl.skill ELSE '-' END)AS 'ge',
ce.enforcement
FROM candidates AS c
LEFT JOIN candidates_language AS cl ON c.id = cl.candidates_id
LEFT JOIN candidates_enforcement as ce on c.id = ce.candidates_id
GROUP BY c.id, c.firstname, c.surname, c.email
As you can see from here I search above multiple tables with using foreign key on candidates ID.
For this purpouse here is how 2 tables looks like:
candidates
------------------------------------------------------------------------
| id | firstname | surname | email |
------------------------------------------------------------------------
| 22 | John | Doe | john#doe.com |
------------------------------------------------------------------------
| 23 | Peter | Miller | doe#john.com |
------------------------------------------------------------------------
candidates_enforcement
--------------------------------------------------
| id | candidates_id | enforcement |
--------------------------------------------------
| 1 | 22 | Advocate |
--------------------------------------------------
| 2 | 22 | Programmer |
--------------------------------------------------
| 3 | 23 | IT Admin |
--------------------------------------------------
candidates_id = foreign key from candidates. With my SQL request above result should looks like:
---------------------------------------------------------------------------------
| id | firstname | surname | email | enforcement
---------------------------------------------------------------------------------
| 22 | John | Doe | john#doe.com | Advocate, Programmer |
---------------------------------------------------------------------------------
| 23 | Peter | Miller | doe#john.com | IT Admin
---------------------------------------------------------------------------------
Unfortunately it display me ALWAYS only 1 result from "enforcement". So for cancidate with id 22 it is Advocate not Advocate, Programmer
Is there a chance someone can help me to find a way how to fix this?
Thanks
p.s. Working demo on FIDDLE
http://sqlfiddle.com/#!9/25b1b/1
You could use GROUP_CONCAT like this:
SELECT
candidates.id,
candidates.firstname,
candidates.surname,
candidates.email,
group_concat(DISTINCT candidates_enforcement.enforcement)
FROM
candidates
LEFT JOIN candidates_enforcement
ON candidates.id = candidates_enforcement.candidates_id
GROUP BY
candidates.id,
candidates.firstname,
candidates.surname,
candidates.email
Reference:
GROUP_CONCAT(expr)
You can use Group_Concat with the Distinct option
SELECT c.id,c.firstname,c.surname,c.email,group_concat(distinct ce.enforcement)
FROM candidates c
LEFT JOIN candidates_enforcement ce
ON c.id=ce.candidates_id
GROUP BY c.id,c.firstname,c.surname,c.email
The distinct option will help you remove off the redundant values.
As I understood you want both enforcement to show up when a person has two enforcement. The problem here is you are LEFT joining the candidates_enforcement table to candidate table.
LEFT join does is get the tuples from candidate table and joins the corresponding tuples from candidates_enforcement. There is only one tuple for candidate in candidate table. So it only shows up one time whether candidates_enforcement has many tuples for that particular candidate or not.
To correct this do a RIGHT JOIN. Or you can do the same LEFT JOIN, with tables swapped.
SELECT c.id, c.firstname, c.surname, c.email,ce.enforcement
FROM candidates AS c
RIGHT JOIN candidates_enforcement as ce on c.id = ce.candidates_id

Join two tables using multiple rows in the join

I have two tables
Table: color_document
+----------+---------------------+
| color_id | document_id |
+----------+---------------------+
| 180907 | 4270851 |
| 180954 | 4270851 |
+----------+---------------------+
Table: color_group
+----------------+-----------+
| color_group_id | color_id |
+----------------+-----------+
| 3 | 180954 |
| 4 | 180907 |
| 11 | 180907 |
| 11 | 180984 |
| 12 | 180907 |
| 12 | 180954 |
+----------------+-----------+
Is it possible for a query to get a result that looks something like this using multiple color id's to join the two tables?
Result
+----------------+--------------+
| color_group_id | document_id |
+----------------+--------------+
| 12 | 4270851 |
+----------------+--------------+
Since Color Group 12 is the only group that has the exact same set of Colors that Document 4270851 has.
I've got some bad data that i'm being forced to work with so I've had to manufacture the color groups by finding each unique set of color_id's associated with document_id's. I'm trying to then create a new relationship directly between my manufactured color groups and documents.
I know I could probably do something with a GROUP_CONCAT to make a pseudo key of concatenated color ids, but I'm trying to find a solution that would also work in, say, Oracle. Am I barking up the completely wrong tree with this logic?
My ultimate goal is to be able to have a single row in a table that would represent any number of Colors that are associated with a Document to be exported to a completely different system than the one I'm working with.
Any thoughts/comments/suggestions are greatly appreciated.
Thank you in advance for looking at my question.
Do a normal join of the two tables, and count the number of rows in each pairing. Then test whether this is the same as the number of times each of the items appears in the original tables. If all are the same, then all color IDs must match.
SELECT a.color_group_id, a.document_id
FROM (
SELECT color_group_id, document_id, COUNT(*) ct
FROM color_document d
JOIN color_group g ON d.color_id = g.color_id
GROUP BY color_group_id, document_id) a
JOIN (
SELECT color_group_id, COUNT(*) ct
FROM color_group
GROUP BY color_group_id) b
ON a.color_group_id = b.color_group_id and a.ct = b.ct
JOIN (
SELECT document_id, COUNT(*) ct
FROM color_document
GROUP BY document_id) c
ON a.document_id = c.document_id and a.ct = c.ct
SQLFIDDLE
If i understand your question correct you just have to join the two tables and then group the results by color_group_id an document_id.
SQL Fiddle
select color_group_id, document_id
from
color_document cd join
color_group cg
on cd.color_id = cg.color_id
group by color_group_id, document_id
That query will give you this result set:
COLOR_GROUP_ID DOCUMENT_ID
3 4270851
4 4270851
11 4270851
12 4270851
Is that what you want?

Join 2 tables, print data from 2nd table when joined rows occur

The case:
I have 2 tables, 'contracts' and 'members' tied with contract.id = members.cid.
Each contract has one main member and several secondary members (usually the children and spouse of the main member). The main member's details (name, address etc) are stored in table contracts whereas, extra members details are kept in table members. (bad logic, i know but this was a mess to begin with and i need time to redesign the db and website)
The desired output:
When I run a batch print of all contracts (lets say, every Friday) I need to also print a copy of the contract for each member, too but with the member's details on the contract instead of the main member.
The question:
How does this translate into a mysql query? Ok, its a left join, but how do I say "print data from table members instead of contracts for the joined rows"?
Main fields that occur in the 2 tables are name + surname, those should be enough for a draft query example.
Example tables and data:
contracts
-------------------------
id | name | surname |
-------------------------
1 | Tom | Jones |
2 | Jamie | Oliver |
members
--------------------------------
id | cid | name | surname |
--------------------------------
1 | 1 | Jack | Jones |
2 | 1 | Anne | Jones |
3 | 2 | Cathy | Wilson |
So the results I want shoudld be:
cid | name | surname |
--------------------------
1 | Tom | Jones |
1 | Jack | Jones |
1 | Anne | Jones |
2 | Jamie | Oliver |
2 | Cathy | Wilson |
If i write
SELECT c.name as name, c.surname as surname, m.name as name, m.surname as surname
FROM contracts c
join members m on c.id = m.cid
I simply end up with
name and name_1, surname and surname_1 but I want ALL names to fall under name and likewise for all other matching columns.
Hope this works :::
select c1.id, c1.name, c1.surname
from contracts c1
union
(Select m.id, m.name, m.surname
from
members m left join contracts c on (c.id = m.cid))
This is what I finally did and it worked (field names are different but the syntax is what matters):
SELECT u.id, u.onoma_u, u.name_u,
coalesce(u.programa, aa.programa) as programa,
coalesce(u.barcode, aa.barcode) as barcode,
coalesce(u.plan, aa.plan) as plan,
coalesce(u.im_exp, aa.im_exp) as im_exp,
coalesce(u.symb, aa.symb) as symb
FROM (SELECT a1.id, a1.onoma_u, a1.name_u, a1.programa, a1.barcode, a1.plan, a1.im_exp, a1.symb
FROM aitisi a1
UNION
SELECT a2.id, m.name, m.surname, NULL, NULL, NULL, NULL, NULL
FROM members m
JOIN aitisi a2 ON a2.id = m.symbid) u
JOIN aitisi aa ON aa.id = u.id;
I used aliases and NULLS as dummy fields to fill in the blanks.