mySQL Left outer join not selecting possible all data [duplicate] - mysql

This question already has answers here:
Left Join not returning all rows
(4 answers)
Closed 3 years ago.
Im trying to match both userIngredient.i_id and recipe_ingredient.i_id and match and compare all values of recipe_ingredient.i_id, the query I tried only displays all matching i_id's without the non matching i_id's, heres the data-
recipe_ingredients table:
+---------+------+
| post_id | i_id |
+---------+------+
| ifqnnv | 1 |
+---------+------+
| ifqnnv | 2 |
+---------+------+
| ifqnnv | 3 |
+---------+------+
| ifqnnv | 4 |
+---------+------+
userIngredient table:
+---------+------+
| user_id | i_id |
+---------+------+
| 4 | 1 |
+---------+------+
| 4 | 2 |
+---------+------+
| 4 | 3 |
+---------+------+
Query that I've tried:
SELECT userIngredients.i_id,recipe_ingredients.i_id, recipe_ingredients.recipe_id,
CASE
WHEN userIngredients.i_id = recipe_ingredients.i_id THEN "true"
WHEN userIngredients.i_id != recipe_ingredients.i_id THEN "false"
END as state
FROM userIngredients
LEFT OUTER JOIN recipe_ingredients
ON userIngredients.i_id = recipe_ingredients.i_id
WHERE userIngredients.uid = 4 AND recipe_ingredients.post_id = 'ifqnnv'
Output I got:
+------+------+-----------+-------+
| i_id | i_id | recipe_id | state |
+------+------+-----------+-------+
| 1 | 1 | ifqnnv | true |
+------+------+-----------+-------+
| 2 | 2 | ifqnnv | true |
+------+------+-----------+-------+
| 3 | 3 | ifqnnv | true |
+------+------+-----------+-------+
Desired output:
+------+------+-----------+-------+
| i_id | i_id | recipe_id | state |
+------+------+-----------+-------+
| 1 | 1 | ifqnnv | true |
+------+------+-----------+-------+
| 2 | 2 | ifqnnv | true |
+------+------+-----------+-------+
| 3 | 3 | ifqnnv | true |
+------+------+-----------+-------+
| null | 4 | ifqnnv | false |
+------+------+-----------+-------+

Move the condition on the left joined table to the on side of the join. Otherwise, this condition can never be fulfilled when the left join does not match, and the corresponding record is eliminated from the resultset.
SELECT
i.i_id,
r.i_id,
r.recipe_id,
CASE
WHEN i.i_id = r.i_id THEN 'true'
ELSE 'false'
END as state
FROM
userIngredients u
LEFT OUTER JOIN recipe_ingredients r
ON i.i_id = r.i_id
AND r.post_id = 'ifqnnv'
WHERE i.uid = 4
Side notes:
meaningfull table aliases make the query more concise and easier to understand; use them at all times when more than one table is involved in the query
the case expression can be simplified to WHEN ... ELSE ..., since both conditions being check are logically opposed
use single quotes instead of double quotes to delimit strings; this corresponds to the SQL standard, while also some RDBMS use double quotes for identifiers
mixing table names with camel case (userIngredient) and underscore separated (recipe_ingredients) is error prone; matter of fact, use underscore separated table and column names, since some RDBMS manage table names in a case-insenstive manner, making camel case pointless

When using left join, order matters. To get your desired result join userIngredients on recipe_ingredients

Related

SQL Self join table, defaulting to null and removing duplicates

I have an SQL model (MySQL v8.0.20) that describes a physical card with 1 or 2 faces. Because card faces can appear on multiple physical cards, I've modeled this as a many-many relationship using the join table cardFaces.
I'm now trying to get a list of distinct front facing cards. That is if a card has only 1 face, it would be represented once, if it has 2 faces, it would be represented twice (once for each side front facing).
Input
The join table currently looks like this (simplified for question):
SELECT * FROM cardFaces;
+----+--------+--------+
| id | cardId | faceId |
+----+--------+--------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | B | 3 |
+----+--------+--------+
Expected Output
The result I'm expecting to achieve is this:
+--------+-------------+------------+
| cardId | frontFaceId | backFaceId |
+--------+-------------+------------+
| A | 1 | NULL |
| B | 2 | 3 |
| B | 3 | 2 |
+--------+-------------+------------+
Current Output
I've only gotten so far as self-joining and removing duplicates, but I can't figure out how to introduce NULL as the backFaceId for cards with only 1 face.
SELECT frontFace.cardId, frontFace.faceId frontFaceId, backFace.faceId backFaceId
FROM cardFaces frontFace
LEFT JOIN cardFaces backFace
ON frontFace.cardId = backFace.cardId
WHERE backFace.id != frontFace.id;
+--------+-------------+------------+
| cardId | frontFaceId | backFaceId |
+--------+-------------+------------+
| B | 2 | 3 |
| B | 3 | 2 |
+--------+-------------+------------+
Move the where condition to the on clause:
SELECT frontFace.cardId, frontFace.faceId frontFaceId, backFace.faceId backFaceId
FROM cardFaces frontFace LEFT JOIN
cardFaces backFace
ON frontFace.cardId = backFace.cardId AND
backFace.id <> frontFace.id;
NULLs fail almost all comparisons, including <>, turning the outer join into an inner join.

How can I merge rows from two joined tables?

There are two tables, which can be joined, and the relationship is 1 to many. I wish the rows of results to be merged.
For example:
Table 1: contacts
.------------.----------.
| contact_id | username |
:------------+----------:
| 1 | user1 |
:------------+----------:
| 2 | user2 |
:------------+----------:
| 3 | user3 |
'------------'----------'
Table 2: documents
.-------------.------------.----------.
| document_id | contact_id | filename |
:-------------+------------+----------:
| 1 | 1 | abc.txt |
:-------------+------------+----------:
| 2 | 1 | bcd.txt |
:-------------+------------+----------:
| 3 | 1 | cde.txt |
:-------------+------------+----------:
| 4 | 2 | 123,txt |
:-------------+------------+----------:
| 5 | 2 | 234.txt |
:-------------+------------+----------:
| 6 | 3 | xyz.txt |
'-------------'------------'----------'
The result I wish I can get:
.------------.----------.---------------------------.
| contact_id | username | filenames |
:------------+----------+---------------------------:
| 1 | user1 | abc.txt, bcd.txt, cde.txt |
:------------+----------+---------------------------:
| 2 | user2 | 123.txt, 234.txt |
:------------+----------+---------------------------:
| 3 | user3 | xyz.txt |
'------------'----------'---------------------------'
Updated:
SELECT c.contact_id, c.username, GROUP_CONCAT(d.filename) as filenames
FROM contacts c
LEFT JOIN documents d
ON c.contact_id = d.contact_id
GROUP BY c.contact_id
You should really post your attempts with your question, so that we can see what you have tried. In that way, it will be easy to push you in the right direction, as well as give the rest of us the impression that you have put some effort into the matter before asking the question. Stackoverflow is not a coding service.
To answer your question,
What you would like to do in this case, is to perform an INNER JOIN on your two tables, and have the MYSQL function, GROUP_CONCAT();, in your SELECT statement.
When you look at your two tables, you have a coherent id (contact_id) that you should use in your INNER JOIN to link your two tables together.
You then, at the end, need to perform a GROUP BY to group your results accordingly, i.e. to group the results by contact_id.
Your SQL would look something like this:
SELECT
tbl_contacts.contact_id,
tbl_contacts.username,
GROUP_CONCAT(tbl_documents.filename) as file_name
FROM
tbl_contacts
INNER JOIN
tbl_documents ON tbl_contacts.contact_id = tbl_documents.contact_id
GROUP BY
tbl_contacts.contact_id
Working SQL fiddle

MySQL concat columns and rows from multiple tables

I'm trying to concatenate data from three related tables according to:
orders orderrow orderrow_op
+----+ +----+----------+ +----+-------------+
| id | | id | id_order | | id | id_orderrow |
+----+ +----+----------+ +----+-------------+
| 1 | | 1 | 1 | | 1 | 1 |
| 2 | | 2 | 1 | | 2 | 1 |
| 3 | | 3 | 2 | | 3 | 2 |
+----+ | 4 | 3 | | 4 | 3 |
+----+----------+ | 5 | 3 |
| 6 | 3 |
+----+-------------+
The result i'm looking for is something like:
orderops (Desired Result)
+----------+-----------------+
| id_order | id_row:id_ops |
+----------+-----------------+
| 1 | 1:(1,2); 2:(3); |
| 2 | 3:(4,5,6) |
| 3 | 4:NULL |
+----------+-----------------+
I.e i want the operations and rows all be displayed on one row related to the order. So far i've tried things like:
SELECT
db.orders.id AS orderid,
db.orderrow.id AS rowids,
GROUP_CONCAT(DISTINCT db.orderrow.id) AS a,
GROUP_CONCAT(db.orderrow.id, ':', db.orderrow_op.id) AS b
FROM
db.orders
LEFT JOIN db.orderrow ON db.orders.id = db.orderrow.id_order
LEFT JOIN db.orderrow_op ON db.orderrow.id = db.orderrow_op.id_orderrow
GROUP BY orderid
Where in column 'a' i get the row ids and in column 'b' i get the operation_ids with corresponding row_id prepended. I'd like to combine the two into a single column such that related values in 'b' will start of with id from 'a' and only show once.
I'm fairly new to MySQL so i don't know if this is even possible or if i'ts a good idea at all? The aim is to structure the data into JSON for delivery via REST application so perhaps it's better to deliver the rows directly to the webserver and handle json parsing over there? I just figured that this approach might be faster.
This is not the nicest query but it's working for your example table setup.
SELECT
o.id AS id_order,
group_concat(sub.ops
SEPARATOR ' ') AS id_row_id_ops
FROM
(SELECT
orderrow.id_order,
IF(isnull(l3.ops), concat(orderrow.id, ':', 'NULL'), concat(orderrow.id, ':', l3.ops)) as ops
FROM
orderrow
LEFT JOIN (SELECT
orderrow_op.id_orderrow,
concat('(', group_concat(orderrow_op.id), '); ') as ops
FROM
orderrow_op
GROUP BY orderrow_op.id_orderrow) l3 ON l3.id_orderrow = orderrow.id) sub
LEFT JOIN
orders o ON o.id = sub.id_order
GROUP BY o.id;
One of the things to mind is the LEFT JOIN and that you need to cast a "null" value to a "null" text (otherwise your element 4 will vanish).
The output:

Mysql count per distinct user with a join

currently i have two tables with some data. the first table has the following:
+----------------+-----------+
| name | member_id |
+----------------+-----------+
| Juice Box | 49432 |
| Rainsurge | 49631 |
| spiderpigrider | 50482 |
+----------------+-----------+
The second table has the following:
+------------+-----------+
| recruit_id | bin(refs) |
+------------+-----------+
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
| 49432 | 1 |
| 49631 | 1 |
+------------+-----------+
I would like to return the name, total refs and member_id/recruit_id like so (listing only users with at least 1 ref)
+------------+-----------+------------+
| recruit_id | name | total_refs |
+------------+-----------+------------+
| 49631 | Rainsurge | 5 |
| 49432 | Juice Box | 5 |
+------------+-----------+------------+
select r.recruit_id,bin(r.refs),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
this returned my data but obviously without a total count and repeated names/ids
select r.recruit_id,count(bin(r.refs)),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
this returned data with the total count of everyone but only one id/name
+------------+--------------------+-----------+
| recruit_id | count(bin(r.refs)) | name |
+------------+--------------------+-----------+
| 49432 | 10 | Juice Box |
+------------+--------------------+-----------+
this returns the data but again without a count
select distinct r.recruit_id,bin(r.refs),ipb.name from refs as r
inner join syndicate_ipb.core_members as ipb on ipb.member_id=r.recruit_id;
+------------+-------------+-----------+
| recruit_id | bin(r.refs) | name |
+------------+-------------+-----------+
| 49432 | 1 | Juice Box |
| 49631 | 1 | Rainsurge |
+------------+-------------+-----------+
Any help or guidance is greatly appreciated. I feel like i'm close here but just not competent enough with SQL to get it. thanks!
You were almost there. You just missed the GROUP BY clause at the end.
Query:
SELECT
r.recruit_id,
count(bin(r.refs)),
ipb.name
FROM refs AS r
INNER JOIN syndicate_ipb.core_members AS ipb
ON ipb.member_id = r.recruit_id
GROUP BY r.recruit_id;
Note:
If bin(refs) column always contains value 1 then actually you don't need to keep that column. In that case you can use count(*) or count(r.recruit_id) to get the count.
And if bin(refs) column contains any value then count will not give you the right answer. In that case you need to use sum like Sum( bin(refs)).
You have to use the group by clause:
select r.recruit_id, ipb.name, count(bin(refs)) as total_refs
from refs as r
inner join syndicate_ipb.core_members as ipb
on ipb.member_id=r.recruit_id
group by r.recruit_id, ipb.name
having count(bin(refs)) >= 1
This group by r.recruit_id, ipb.name will group the results and this having count(bin(refs)) >= 1 will garante that it only returns members with at least one ref
Do not only group your columns just by the ones you want. Even though MySql allows it, it is not SQL Ansi pattern and even MySql now is complying with it. Use an aggregation function grouping with your entire columns on the select statement.
SELECT ipb.*, COUNT(`r`.`recruit_id`) AS cid FROM `ipb`
INNER JOIN `r` ON `r`.`join_id` = ipb.`member_id`
GROUP BY ipb.`member_id`

How to join tables with SQL query and take number of tied columns?

I'm having BookTable in database (with foregin hey LibID):
| BookID | BookName | BookPrice | LibID |
-------------------------------------------
| 1 | Book_1 | 200 | 1 |
| 2 | Book_2 | 100 | 1 |
| 3 | Book_3 | 300 | 2 |
| 4 | Book_4 | 150 | 4 |
and also LibraryTable:
| LibID | LibName | LibLocation |
-----------------------------------
| 1 | Lib_1 | Loc_1 |
| 2 | Lib_2 | Loc_2 |
| 3 | Lib_3 | Loc_3 |
| 4 | Lib_4 | Loc_4 |
I need to write SQL query that will return be the info about the library and number of books for that library:
| LibID | LibName | NumberOfBooks|
------------------------------------
| 1 | Lib_1 | 2 |
| 2 | Lib_2 | 1 |
| 3 | Lib_3 | 0 |
| 4 | Lib_4 | 1 |
It should be one SQL query, probably with nested queries or joins.. Not sure how the query should look like:
SELECT L.LibID AS LibID, L.LibName AS LibName, COUNT(B) AS NumberOfBooks
FROM LibraryTable L, BookTable B
WHERE L.LibID = B.LibID
Will that work?
No, this query will not work. COUNT aggregates data, so you must explicitely tell the DBMS for which group of data you want the count. In your case this is the library (you want one result record per library).
COUNT's parameter is a column, not a table, so change this to * (i.e. count records) or a certain column (e.g. LibID).
The join syntax you are using is valid, but deprecated. Use explicit joins instead. In your case an outer join would even show libraries that have no books at all, if such is possible.
select l.libid, l.libname, count(b.libid) as numberofbooks
from librarytable l
left outer join booktable b on b.libid = l.libid
group by l.libid;
You could also do all this without a join at all and get the book count in a subquery instead. Then you wouldn't have to aggregate. That's way simpler and more readable in my opinion.
select
l.libid,
l.libname,
(select count(*) booktable b where b.libid = l.libid) as numberofbooks
from librarytable l;
SELECT lt.LibID AS LibID, lt.LibName AS LibName, count(*) AS NumberOfBooks
FROM BookTable AS bt
LEFT JOIN LibraryTable AS lt ON bt.LibID = lt.LibID
GROUP BY bt.LibID