Is this possible using inner joins in MySQL? - mysql

I'm stuck on trying to build a query that will include a given order_reference cover file from the data sample below only if ALL of the print_item_qty values of the files under the same order_reference are equal to 5.
Is this possible using joins or is this outside the remit of a single query?
I've tried building inner joins but cannot work out how to restrict the results so that I only get a cover row when all the component parts of an order have equal values in the print_item_qty column.
Here is the base SELECT query and sample data:
SELECT c1.`order_id`,c1.name1,c1.name2,c1.`print_item_qty`,c1.`sub_item_type`,
c1.`order_reference` FROM print_items;
+--------------+-------+---------+----------------+---------------+-----------------+
| order_id | name1 | name2 | print_item_qty | sub_item_type | order_reference |
+--------------+-------+---------+----------------+---------------+-----------------+
| 000003201875 | Jason | Bramley | 5 | cover | 1875 |
| 000003201875 | Jason | Bramley | 5 | inner | 1875 |
| 000003201875 | Jason | Bramley | 1 | card | 1875 |
| 000003201876 | Jason | Bramley | 5 | cover | 1876 |
| 000003201876 | Jason | Bramley | 5 | inner | 1876 |
+--------------+-------+---------+----------------+---------------+-----------------+
My desired result for the above sample data would be only the following row:
+--------------+-------+---------+----------------+---------------+-----------------+
| order_id | name1 | name2 | print_item_qty | sub_item_type | order_reference |
+--------------+-------+---------+----------------+---------------+-----------------+
| 000003201876 | Jason | Bramley | 5 | cover | 1876 |
+--------------+-------+---------+----------------+---------------+-----------------+
Any help anyone could give to point me in the right direction would be greatly appreciated.
Many thanks.

So you want to verify that you only select data for orders for which the print_item_qty = 5 for all sub_item_type in that order_reference.
To do this, use a subsquery like
SELECT order_reference,
MAX(print_item_qty),
MIN(print_item_qty)
FROM print_items
GROUP BY order_reference
HAVING MAX(print_item_qty) = 5
AND MIN(print_item_qty) = 5
And join to your original dataset. The subquery will restrict to the ids you want, and joining back will return all rows associated with the order_references for which print_item_qty = 5 for every sub_item_type, eg,
SELECT c1.`order_id`,
c1.name1,
c1.name2,
c1.`print_item_qty`,
c1.`sub_item_type`,
c1.`order_reference`
FROM print_items AS c1
INNER JOIN (SELECT order_reference, MAX(print_item_qty), MIN(print_item_qty) FROM print_items
GROUP BY order_reference HAVING MAX(print_item_qty) = 5 AND MIN(print_item_qty) = 5) AS b
ON c1.order_reference = b.order_reference

Related

Left joins, i need an explanation about a code

i am watching a tutorial. There is a code which i don't understand what is supposed to do.
$sql = 'SELECT p.*,
a.screen_name AS author_name,
c.name AS category_name
FROM
posts p
LEFT JOIN
admin_users a ON p.author_id = a.id
LEFT JOIN
categories c ON p.category_id = c.id
WHERE
p.id = ?';
I read about the left joins but i didn't understand them. Can somebody please explain me the code i shared.
Thanks in advance!
Imagine you have two tables. One that stores the information about the programmers on your website, and the other table that keeps track of their online purchases.
PROGRAMMERS Table
+--------------------------------------------+
| ID | NAME | AGE | ADDRESS | SALARY |
+----+----------+-----+-----------+----------+
| 1 | Desire | 32 | 123 fake s| 3000.00 |
| 2 | Jamin | 25 | 234 fake s| 2500.00 |
| 3 | Jon | 23 | 567 fake s| 2000.00 |
| 4 | Bob | 30 | 789 fake s| 1500.00 |
| 5 | OtherGuy | 31 | 890 fake s| 1000.00 |
| 6 | DudeMan | 32 | 901 fake s| 500.00 |
+--------------------------------------------+
PURCHASES Table
+---------------------------------------------+
| ORDER_ID | PROG_ID | DATE | PRICE |
+-------------+---------+---------------------|
| 1 | 1 | 1-1-2017 | 100 |
| 2 | 2 | 1-2-2017 | 200 |
| 3 | 6 | 1-3-2017 | 300 |
+---------------------------------------------|
You decide you need to make a new table to consolidate this information to a table that contains
certain columns you want.
For example, you figure it would be nice for shipping purposes to have a table
that has the ID, the NAME, the PRICE, and the DATE columns.
Currently, the tables we have don't display all of that in a single table.
If we were to LEFT JOIN these tables, we would end up filling the desired columns
with NULL values where there is no information to join.
SELECT ID, NAME, PRICE, DATE
FROM PROGRAMMERS
LEFT JOIN PURCHASES
ON PROGRAMMERS.ID = PURCHASES.PROG_ID;
Notice that I'm selecting the columns I want from the starting table, then joining the right table
even though there might be missing information.
RESULTING TABLE
+-------------------------------------+
| ID | NAME | PRICE | DATE |
+----+----------+-----------------+---+
| 1 | Desire | 100 | 1-1-2017 |
| 2 | Jamin | 200 | 1-2-2017 |
| 3 | Jon | NULL | NULL |
| 4 | Bob | NULL | NULL |
| 5 | OtherGuy | NULL | NULL |
| 6 | DudeMan | 300 | 1-3-2017 |
+-------------------------------------+
For a visual representation of the difference between SQL JOINs check out
https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins .

Find unique/duplicated rows from has and belongs to many association

I have following DB structure:
Table cars:
+----+-----------------------+
| id | few other columns.... |
+----+-----------------------+
| 1 | ... |
| 2 | ... |
| 3 | ... |
+----+-----------------------+
Table properties:
+----+-------+
| id | name |
+----+-------+
| 1 | title |
| 2 | type |
| 3 | brand |
| 4 | color |
+----+-------+
Table cars_properties:
+----+--------+-------------+------------+
| id | car_id | property_id | txt |
+----+--------+-------------+------------+
| 1 | 1 | 1 | Volvo V70 |
| 2 | 1 | 2 | personal |
| 3 | 1 | 3 | Volvo |
| 4 | 1 | 4 | white |
| 5 | 2 | 1 | Volvo VV |
| 6 | 2 | 2 | personal |
| 7 | 2 | 3 | Volvo |
| 8 | 2 | 4 | blue |
| 9 | 3 | 1 | Volvo XXL |
| 10 | 3 | 2 | truck |
| 11 | 3 | 3 | Volvo |
| 12 | 3 | 4 | white |
+----+--------+-------------+------------+
I would like to get all cars that have unique/duplicated values in one or many properties. Currently I'm using this SQL pattern to get duplicates for car type and brand:
SELECT cars.id FROM cars
LEFT JOIN cars_properties AS cp_0 ON cp_0.car_id = cars.id AND cp_0.property_id = 2 # => type
LEFT JOIN cars_properties AS cp_1 ON cp_1.car_id = cars.id AND cp_1.property_id = 3 # => brand
INNER JOIN (
SELECT cp_0.txt AS type_txt, cp_1.txt AS brand_txt FROM cars
LEFT JOIN cars_properties AS cp_0 ON cp_0.car_id = cars.id AND cp_0.property_id = 2
LEFT JOIN cars_properties AS cp_1 ON cp_1.car_id = cars.id AND cp_1.property_id = 3
GROUP BY cp_0.txt, cp_1.txt
HAVING COUNT(cars.id) > 1
) dupes ON cp_0.txt=dupes.type_txt AND cp_1.txt=dupes.brand_txt;
And expected result is:
+----+
| id |
+----+
| 1 |
| 2 |
+----+
Explanation: Both cars with id = 1 and 2 has type and brand that is present in more than one car (multiple times).
As for unique cars, I'm just altering: HAVING COUNT(cars.id) = 1 and I want to find all rows where the combination of properties is present only in one car (once).
It works fine, but it's extremely slow with more than 2 properties I want to check.
I cannot change the DB structure, and I'm not sure how to optimize the query, or if there are better ways of achieving this.
It feels like I would need to implement counter table, where each property id and value (txt) would also store corresponding number of occurrences in cars, and update this counter on every insert/update/delete... But I still hope there is some better SQL, that could help. Do you know some? Any advice greatly appreciated, thanks!
PS: I tried to create fiddle for it, but after I build schema I cannot run any SQL on it. To quickly setup DB with data, you can check SQL Fiddle

mysql request using join and group_concat

I have three tables "names", "groups", "tasks"
"names"
| nid | name |
| 1 | John |
| 2 | Jim |
| 3 | Jerry |
"groups"
| gid | nid |
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
"tasks"
| tid | gid |
| 1 | 2 |
| 2 | 1 |
I want to get a list of names, that belong to a task, like this:
| tid | names |
| 1 | Jim,Jerry |
| 2 | John,Jim |
I successfully tried:
SELECT t.tid,n.name
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
This works so far, but the names are in single outputs so I tried to use GROUP_CONCAT(), but this does not work. I just get the first task with the wrong names!?!?
SELECT t.tid,GROUP_CONCAT(n.name)
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
I have never used GROUP_CONCAT() before. It works well, when using it in a single SELECT without joins. A short explanation what I did wrong, would be nice.
When using group_concat you need to group your results.
Add group by t.id at the end of your query.

Converting sub-queries to joins

Given the following schema...
how would I go about converting the following query to use joins (to work with MySQL)?
SELECT submissions.SubmissionID,
(SELECT data.DataContent FROM data WHERE data.DataFieldName =
(SELECT forms.FormEmailFieldName FROM forms WHERE forms.FormID = submissions.SubmissionFormID)
AND data.DataSubmissionID = submissions.SubmissionID) AS EmailAddress,
(SELECT data.DataContent FROM data WHERE data.DataFieldName =
(SELECT forms.FormNameFieldName FROM forms WHERE forms.FormID = submissions.SubmissionFormID)
AND data.DataSubmissionID = submissions.SubmissionID) AS UserName
FROM submissions
WHERE submissions.SubmissionFormID = 4
Below is some sample data and my desired result...
+--------+--------------------+-------------------+
| forms | | |
+--------+--------------------+-------------------+
| FormID | FormEmailFieldName | FormNameFieldName |
| 4 | UserEmailAddress | UserName |
| 5 | email | name |
+--------+--------------------+-------------------+
+--------------+------------------+
| submissions | |
+--------------+------------------+
| SubmissionID | SubmissionFormID |
| 10 | 4 |
| 11 | 5 |
| 12 | 5 |
+--------------+------------------+
+--------+------------------+------------------+------------------+
| data | | | |
+--------+------------------+------------------+------------------+
| DataID | DataSubmissionID | DataFieldName | DataContent |
| 1 | 10 | UserEmailAddress | user#example.com |
| 2 | 10 | UserName | Paul D'Otherone |
| 3 | 11 | email | bob#bobs.com |
| 4 | 11 | name | Bob Bobbington |
| 5 | 11 | phone | 01234 5678910 |
| 6 | 11 | country | UK |
| 7 | 12 | name | Sheila Sausages |
| 8 | 12 | country | UK |
+--------+------------------+------------------+------------------+
+--------------------------+------------------+-----------------+
| DESIRED RESULT | | |
+--------------------------+------------------+-----------------+
| submissions.SubmissionID | EmailAddress | UserName |
| 10 | user#example.com | Paul D'Otherone |
| 11 | bob#bobs.com | Bob Bobbington |
| 12 | NULL | Sheila Sausages |
+--------------------------+------------------+-----------------+
Also see http://sqlfiddle.com/#!2/78dea/1/0
I've tried various combinations of inner joins and left joins but can't get a result set in the same format as the above query. I am still learning how to use joins and am finding this hard to get my head around.
You can do this with one join to forms and two to data, one for each field.
SELECT s.SubmissionID, de.DataContent as EmailAddress,
dn.DataContent as UserName
FROM submissions s LEFT JOIN
forms f
ON f.FormId = s.SubmissionFormID LEFT JOIN
data dn
ON d.DataFieldName = f.FormNameFieldName LEFT JOIN
data de
ON d.DataFieldName = f.FormEmailFieldName
WHERE s.SubmissionFormID = 4
In case there is missing data, then you want to use LEFT JOIN. This will ensure that you get all the rows (which your original query does).
I also added table aliases into the query. These make queries easier to write and to read.
Without having sample data, just a quick try:
SELECT
s.SubmissionID,
d1.DataContent AS EmailAddress,
d2.DataContent AS Username
FROM submissions s
JOIN forms AS f1 ON (f1.FormID = s.SubmissionFormID)
JOIN data AS d1 ON (d1.DataFieldName = f1.FormEmailFieldName)
JOIN data AS d2 ON (d2.DataFieldName = f1.FormNameFieldName)
WHERE s.SubmissionFormID = 4
sql fiddle for it: http://sqlfiddle.com/#!2/9f917/1
Starting from Olli's and Gordon's suggestions I have come up with the following which gives the same result set as my original query...
SELECT
s.SubmissionID,
d1.DataContent AS EmailAddress,
d2.DataContent AS Username
FROM submissions s
INNER JOIN forms AS f1 ON (f1.FormID = s.SubmissionFormID)
LEFT JOIN data AS d1 ON (d1.DataFieldName = f1.FormEmailFieldName and d1.DataSubmissionID = s.SubmissionID)
LEFT JOIN data AS d2 ON (d2.DataFieldName = f1.FormNameFieldName and d2.DataSubmissionID = s.SubmissionID)
WHERE s.SubmissionFormID = 4
Does this seem like the correct way to go about achieving what I want?

Select Distinct Set Common to Subset From Join Table

Given a join table for m-2-m relationship between booth and user
+-----------+------------------+
| booth_id | user_id |
+-----------+------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 5 |
| 1 | 9 |
| 2 | 1 |
| 2 | 2 |
| 2 | 5 |
| 2 | 10 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
| 3 | 4 |
| 3 | 6 |
| 3 | 11 |
+-----------+------------------+
How can I get a distinct set of booth records that are common between a subset of user ids? For example, if I am given user_id values of 1,2,3, I expect the result set to include only booth with id 3 since it is the only common booth in the join table above between all user_id's provided.
I'm hoping I'm missing a keyword in MySQL to accompish this. The furthest I've come so far is using ... user_id = all (1,2,3) but this is always returning an empty result set (I believe I understand why it is though).
The SQL query for this will be:
select booth_id from table1 where [user_id]
in (1,2,3) group by booth_id having count(booth_id) =
(select count(distinct([user_id])) from table1 where [user_id] in (1,2,3))
If this could help you creating the MySQL query.