mysql left join with count(on right table attribute) group by - mysql

I have two tables, one with tags and another one with the actually selected tags of an article (table relation). I want to add the condition to select the amount of the actually selected tags of the current article. example selecting article 1:
--------------------------
| id | name | selected |
--------------------------
| 0 | this | 1 |
| 1 | is | 0 |
| 2 | sparta | 1 |
--------------------------
Ive got to this far:
SELECT t.*, count(t.id) as `selected`
FROM tag t LEFT JOIN relation r ON t.id = r.tid
GROUP BY t.id
table tag:
---------------
| id | name |
---------------
| 0 | this |
| 1 | is |
| 2 | sparta |
---------------
table relation:
-------------
| tid | aid |
-------------
| 0 | 2 |
| 0 | 1 |
| 2 | 1 |
-------------

EDIT : The first query returns the selected tags for an article and that's not the desired behaviour
Is that the query you're looking for?
SELECT A.id
, COUNT(T.id) AS [nb selected tags]
FROM article A
LEFT OUTER JOIN relation R ON R.aid = A.id
LEFT OUTER JOIN tag T ON T.id = R.tid
AND T.selected = 1
GROUP BY A.id
Hope this will help you.
PS: If you just want the result of a specific Article, you juste have to add a WHERE clause before the GROUP BY.
Query returning the desired data:
SELECT T.id
,T.name
, CASE
WHEN R.tid IS NOT NULL THEN 1
ELSE 0
END AS [selected]
FROM tag T
LEFT OUTER JOIN relation R ON R.tid = T.id
AND R.selected = 1
AND R.aid = ...
I assume that the table relationhas a unicity on tid and aid.
Let me know if the query returns the expected result.

Related

Mysql Join get multiple rows even if one of them not meet search condition

I am trying to get multiple rows with mysql join but if one of the rows doesn't meet the search condition didn't get it.
example :
I have a products table and orders table and order_products table
when someone order like 2 products I insert the order information in the order table and insert every product with its quantity in order_products table with order id
this is the schema I have
products table
| id | title |
---------------
| 1 | p1 |
| 2 | p2 |
| 3 | p3 |
| 4 | p4 |
Orders table
| id | client |
---------------
| 1 | c1 |
| 2 | c2 |
| 3 | c3 |
order_products table
| id | order_id| product_id |
-----------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 4 |
| 4 | 3 | 3 |
my query is
SELECT `orders`.*, GROUP_CONCAT(`products`.`title` SEPARATOR ' ') AS products FROM `orders` LEFT JOIN `order_products` ON `order_products`.`order_id` = `orders`.`id` LEFT JOIN `products` ON `products`.`id` = `order_products`.`product_id` GROUP BY `orders`.`id`
this query is working fine and as I intended to but the problem is when I search by product title example p1
the result I got
| id | client | info |
-------------------------
| 1 | c1 | p1 |
| 2 | c2 | p1 |
the result I want to get is like this even if the product p2 doesn't appear in search but it's in the order and I am selecting orders not products
| id | client | products|
-------------------------
| 1 | c1 | p1 p2 |
| 2 | c2 | p1 |
You want to use a having clause, not a where clause:
SELECT o.*, GROUP_CONCAT(p.title SEPARATOR ' ') AS products
FROM orders o JOIN
order_products op
ON op.order_id = o.id JOIN
products p
ON p.id = op.product_id
GROUP BY o.id
HAVING SUM( p.title LIKE ? ) > 0;
The ? is a parameter placeholder for what you are looking for.
This returns all products for orders that contain the product you are interested in.
Note the other changes I made to the query:
I introduced table aliases, so the query is easier to write and to read.
I removed the backticks, for the same reason.
I changed the LEFT JOIN to JOIN because an outer join is not necessary. Your logic requires at least one match to a product.

SQL - Display value in column when left join fails

Let's say I want to join on 2 tables, one is filled like this.
| Category |
|-----------|
| id | name |
|----|------|
| 1 | Foo |
| 2 | Bar |
| 3 | Baz |
The other like this:
| Page |
|-----------|
| id | cat |
|----|------|
| 1 | 0 |
| 2 | 1 |
| 3 | 3 |
As you can see cat 0 in the Page table is not present in the Category table. Our system is unfortunately like this and I can't add the category with id 0 to the Category table, due to other code.
Now comes the one million dollar question: Is it possible to join category.id on page.cat and set an if statement when page.cat equals 0 to show the category name as Default?
If only 0 is the one missing do a left join and use COALESCE to decode the nulls.
SELECT Page.*, COALESCE(name , 'Default')
FROM Page
LEFT JOIN Category
ON Page.cat = Category.id;
or
SELECT P.*, IFNULL(C.name , 'Default') as Name
FROM Page P
LEFT JOIN Category C ON P.cat= C.id
Try this out:
SELECT
p.id,
p.cat,
case when p.cat = 0 then "DEFAULT" ELSE c.name END AS cat_name
FROM
cat c RIGHT JOIN page p
ON c.id = p.cat
ORDER BY p.id

MySQL JOIN with LIMIT query results

I have 2 tables, products and origins
Products:
p_id | name | origin_id
------------------------
1 | P1 | 1
2 | P2 | 2
3 | P3 | 1
Origins:
o_id | name
-------------
1 | O1
2 | O2
I am using the following query :
SELECT * FROM `products` LEFT OUTER JOIN `origins`
ON ( `products`.`origin_id` = `origins`.`o_id` ) LIMIT 2
I am getting the below results
p_id | name | origin_id | o_id | name
-----------------------------------------
1 | P1 | 1 | 1 | O1
3 | P3 | 1 | 1 | O1
I was wondering how the LEFT OUTER JOIN affects the result where I am getting the first and the third row rather than the first and the second row?
When you are not using ORDER BY Clause, there is no guarantee of a specific order for your SELECT query.
So we should use ORDER BY when we need any specific order.
See this: MySQL Ref: What is The Default Sort Order of SELECT with no ORDER BY Clause
You don't control the inherent ordering of rows in a table. It behaves like a set. If you want to order it, use order by clause.
SELECT * FROM `products` p LEFT OUTER JOIN `origins` o
ON ( p.`origin_id` = o.`o_id` ) ORDER BY p.`name` LIMIT 2
Output :
p_id | name | origin_id | o_id | name
-----------------------------------------
1 | P1 | 1 | 1 | O1
2 | P2 | 2 | 2 | O2

Mysql join on 3 tables output

I am learning joins and have the following tables.
Student
| ID | NAME |
-------------
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
Pass
| ID | MARKS |
--------------
| 2 | 80 |
| 3 | 75 |
Fail
| ID | MARKS |
--------------
| 1 | 25 |
| 4 | 20 |
The output I want is this:
| NAME | MARKS |
----------------
| B | 80 |
| C | 75 |
| A | 25 |
| D | 20 |
I wrote a query like this:
select s.id,s.name,p.marks from student s
left join pass p on s.id=p.id
left join (select f.marks,f.id from fail f ) as nn on s.id=nn.id
order by marks desc;
The output I got is this:
| id | name | Marks|
--------------------
| 1 | B | 80 |
| 2 | C | 75 |
| 3 | A | Null |
| 4 | D | NUll |
Cant figure out why Null is coming. Any pointers?
You can use CASE statement for that:
SELECT Name,
CASE WHEN P.Marks IS NULL THEN f.Marks ELSE P.Marks END AS Marks
FROM Student s
LEFT JOIN Pass p ON s.ID = p.ID
LEFT JOIN Fail f ON s.ID = f.ID
ORDER BY Marks DESC;
Or you can also use IF statement:
SELECT Name,
IF(P.Marks IS NULL, F.Marks, P.Marks) AS Marks
FROM Student s
LEFT JOIN Pass p ON s.ID = p.ID
LEFT JOIN Fail f ON s.ID = f.ID
ORDER BY Marks DESC;
Output
| NAME | MARKS |
----------------
| B | 80 |
| C | 75 |
| A | 25 |
| D | 20 |
See this SQLFiddle
To learn more about JOINs see: A Visual Explanation of SQL Joins
You select only the passed marks, this is the reason of null-s appears near falied results.
If you want to select the failed marks you can use IF condition
select s.id,s.name,IF(p.marks = null, nn.marks, p.marks) as markss
from student s
left join pass p on s.id=p.id
left join fail nn on s.id=nn.id
order by markss desc;
Or you can use union of the passed and failed results.
select s.id,s.name, u.marks
from student s
left join ( (SELECT * FROM pass) UNION (SELECT * FROM fail) ) as n ON n.id = s.id
order by marks desc;
You need to understand how the different joins work to understand why you receive NULL for the marks column.
Take a look here:A Visual Explanation of SQL Joins
The relevant example for you is:
LEFT OUTER JOIN:
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name
id name id name
-- ---- -- ----
1 Pirate 2 Pirate
2 Monkey null null
3 Ninja 4 Ninja
4 Spaghetti null null
The Null values you received for the marks column are rows that have no match in the left joined tables.
(the left part of the Venn diagram) the values that does have a value are the cross section between the tow groups of the Venn Diagram.
specifics for your example:
select s.id,s.name,p.marks
from student s
left join pass p on s.id=p.id
left join (select f.marks,f.id from fail f ) as nn on s.id=nn.id
order by marks desc;
The output i got is this:
id | name | Marks
-------------------
1 | B | 80
2 | C | 75
3 | A | Null
4 | D | NUll
This will return all student rows when.
students that have a passing gtade will display the grade and thous who don't will display null.
Try the below Query, use COALESCE
select s.id,s.name,COALESCE(p.marks , nn.marks) as marks
from student s
left join pass p on s.id=p.id
left join fail nn on s.id=nn.id
order by marks desc;
SQL Fiddle

How to build a query involving several different tables with different relationships

I'm currently trying to write a query which involves 5 main tables, 2 of which are referring to a 3rd with foreign keys, but not relating to each-other... and one of the first 2 tables is the main subject of the query. Here's a basic synopsis.
instance user
-------- ----
id id
name name
user_id
def def_map
--- ------
id id
name instance_id
user_id def_id
def_data
--------
id
name
def_id
user_id
What I want to do is get a list of all of the 'def_map's for a single user. In each row I'd like the associated def_data to be displayed as well. So the rows would be like:
instance.id, def.id, def.name, def_data.name, user.id
I can figure out how to get all info except def_data.name in the result, or all info except for instance.id ... but can't figure out how to get then all together using one query. Is it possible? I think part of the problem is I don't know if there is a special word that describes this type of query so I would know what to read up on.
I'm using DQL, but examples in SQL would be just as useful. Thanks in advance for any help.
If you can pull the data individually using 2 queries you simply need to UNION them together
SELECT user.id, i.id, d.id, dd.name
FROM user u
INNER JOIN instance i ON u.id=i.user_id
INNER JOIN def d ON dm.user_id = u.id
INNER JOIN def_data dd ON dd.def_id = d.id
UNION ALL
SELECT u.id, i.id AS instance_id, d.id, dd.name
FROM instance i
INNER JOIN user u ON u.id=i.user_id
INNER JOIN defmap dm ON dm.instance_id=i.id
INNER JOIN def_data dd ON dd.def_id=dm.def_id
select I.id, D.id, D.name, DD.name, U.id
from user U inner join instance I on I.user_id = U.id
Inner join def D on D.user_id = U.id
inner join def_map DM on DM.def_id = D.id AND I.id = DM.instance_id
inner join def_data DD on DD.def_id = D.id AND U.id = DD.user_id
Test data:
USER
+----+-------------------------+
| id | name |
+----+-------------------------+
| 1 | Name1 |
+----+-------------------------+
Instance
+----+------+---------+
| id | name | user_id |
+----+------+---------+
| 1 | I1 | 1 |
+----+------+---------+
def_map
+--------+-------------+--------+
| id | instance_id | def_id |
+--------+-------------+--------+
| 1 | 1 | 1 |
+--------+-------------+--------+
def
+--------------+------+
| id | name | user_id |
+--------------+------+
| 1 | df1 | 1 |
+--------------+------+
def_data
+--------+------+--------+---------+
| id | name | def_id | user_id |
+--------+------+--------+---------+
| 1 | dd1 | 1 | 1 |
+--------+------+--------+---------+
Result
+-------------+--------+----------+---------------+---------+
| instance.id | def.id | def.name | def_data.name | user.id |
+-------------+--------+----------+---------------+---------+
| 1 | 1 | df1 | dd1 | 1 |
+-------------+--------+----------+---------------+---------+