Multiple join with filtering - mysql

I have 3 table
Group
idG
Student
idS
idG
Rating
idR
idS
date
relations is 1(Group)-∞(Student),1(Student)-∞(Rating)
I need to select all Group where idG is 1(for example) JOIN Student with LEFT JOIN Rating where date is between.
I trying to use query such this
SELECT *
FROM `group` AS g
JOIN student
ON g.idG = 1
LEFT JOIN rating
ON rating.idR = student.idS
WHERE rating.`date` between '2019-02-01' and '2019-02-28';
Result set of this query is student of group which have rating between date,but as I know left join must fetch NULL rating to the student which have no rating between the date. I expecting this result
idG | idS | idR | date|
1 | 1 | NULL | NULL|
1 | 2 | 1 | 2019-02-08|
1 | 3 | 3 | 2019-02-10|
1 | 4 | NULL | NULL|

You could put the criteria for the left joined table in the JOIN.
SELECT grp.idG, stu.idS, rat.idR, rat.`date`
FROM student stu
JOIN `group` AS grp
ON grp.idG = stu.idG
LEFT JOIN rating rat
ON rat.idS = stu.idS
AND rat.`date` BETWEEN '2019-02-01' AND '2019-02-28'
WHERE stu.idG = 1;
The thing with LEFT JOIN is that when you also want those that don't match, that a criteria in the WHERE clause for the joined table will discard all non-matches.
Since it then discards the NULL's on the right side of the join.

Related

LEFT JOIN and IS NULL in nested CONCAT returning NULL only

Each student has multiple time period to pay fee, I want to fetch fee time period in which fee hasn't payed, or in MySQL language - fetch row which is not in another table.
Here, I am using nested GROUP_CONCAT in MySQL, and LEFT JOIN with IS NULL
Table student - lists of student
id | ttl | cls | sec
===============================
1 | Rohit | 1 | 1
2 | Karuna | 2 | 0
Table cls - lists of Class
id | ttl
===========
1 | One
2 | Two
Table sec - lists of section
id | ttl
===========
1 | A
2 | B
Table fee_tm - lists of Fee time Period
id | ttl
===========
1 | Jan
2 | Feb
Table std_fee - lists of Fee period assigned to Student
id | s_id| f_id| fee| f_tm
====================================
1 | 1 | 4 | 100| 1
According to tables structure and row in table, I am expecting following output with my MySQL code.
//(student.id-student.cls-student.sec-student rest of the fee.time(Month1,Month2..))
1-Rohit-One-A-Feb,
2-Karuna-Two-John,Feb
but what I get (I wanna apply NULL and LEFT JOIN only for fee time, so remaining fee time can be fetched, but here it is apply to whole result)
2-Karuna-Two-
SQL Fiddle
MySQL Code
SELECT
GROUP_CONCAT(DISTINCT CONCAT(student.id,'-',student.ttl,'-',cls.ttl,'-',
COALESCE(sec.ttl,''),
COALESCE(CONCAT('-',fee_tm.ttl),''))
ORDER BY student.id) AS stdt
FROM
student
JOIN
cls ON cls.id=student.cls
LEFT JOIN
sec ON sec.id=student.sec
LEFT JOIN
std_fee ON std_fee.s_id = student.id
LEFT JOIN
fee_tm ON fee_tm.id = std_fee.f_tm
WHERE
std_fee.f_tm IS NUll
You can try to write a subquery for std_fee and fee_tm tables and let std_fee.f_tm IS NUll condition in ON to make a result set.
What's the difference let the condition between putting in where and ON?
You are using OUTER JOIN if you don't put conditions in ON you will miss row data by this std_fee.f_tm IS NUll condition, because you match in fee_tm.id = std_fee.f_tm
query looks like this.
Query 1:
SELECT
GROUP_CONCAT(DISTINCT CONCAT(student.id,'-',student.ttl,'-',cls.ttl,'-',
COALESCE(sec.ttl,''),
COALESCE(CONCAT(t1.ttl),''))
ORDER BY student.id) AS stdt
FROM
student
JOIN
cls ON cls.id=student.cls
LEFT JOIN
sec ON sec.id=student.sec
LEFT JOIN
(
select s.id,GROUP_CONCAT(COALESCE(fee_tm.ttl,'')) ttl
FROM
student s
LEFT JOIN
std_fee ON std_fee.s_id = s.id
LEFT JOIN
fee_tm ON fee_tm.id = std_fee.f_tm or std_fee.f_tm IS NUll
GROUP BY s.id
) t1 on t1.id = student.id
group by student.id
Results:
| stdt |
|----------------------|
| 1-Rohit-One-AJan |
| 2-Karuna-Two-Jan,Feb |

SQL left join: how to return the newest from tableB and grouped by another field

I've been trying for two days, without luck.
I have the following simplified tables in my database:
customers:
| id | name |
| 1 | andrea |
| 2 | marco |
| 3 | giovanni |
access:
| id | name_id | date |
| 1 | 1 | 5000 |
| 2 | 1 | 4000 |
| 3 | 2 | 1500 |
| 4 | 2 | 3000 |
| 5 | 2 | 1000 |
| 6 | 3 | 6000 |
| 7 | 3 | 2000 |
I want to return all the names with their last access date.
At first I tried simply with
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id
But I got 7 rows instead of 3 as expected. So I understood I need to use GROUP BY statemet as the following:
SELECT * FROM customers LEFT JOIN access ON customers.id =
access.name_id GROUP BY customers.id
As far I know, GROUP BY combines using a random row. In fact I got unordered access dates with several tests.
Instead I need to group every customer id with its corresponding latest access! How this can be done?
You have to get the latest date from the access table with a group by on the the name_id, then join this result with the customer table. Here is the query:
select c.id, c.name, a.last_access_date from customers c left join
(select id, name_id, max(access_date) last_access_date from access group by name_id) a
on c.id=a.name_id;
Here is a DEMO on sqlfiddle.
I think this is what you'd like to achieve:
SELECT c.id, c.name, max(a.date) last_access
FROM customers c
LEFT JOIN access a ON c.id = a.name_id
GROUP BY c.id, c.name
The LEFT join will return all entries in table customers regardless if the join criteria (c.id = a.name_id) is satisfied. This means that you might get some NULL entries.
Example:
Simply add a new row in the customers table (id: 4, name: manuela). The output will have 4 rows and the newest row will be (id: 4, last_access: null)
I would do this using a correlated subquery in the ON clause:
SELECT a.*, c.*
FROM customers c LEFT JOIN
access a
ON c.id = a.name_id AND
a.DATE = (SELECT MAX(a2.date) FROM access a2 WHERE a2.name_id = a.name_id);
If this statement is true:
I need to group every customer id with its corresponding latest access! How this can be done?
Then you can simply do:
select a.name_id, max(a2.date)
from access a
group by a.name_id;
You do not need the customers table because:
All customers are in access, so the left join is not necessary.
You need no columns from customers.

mysql help to make a report

i have two table here is table_user and table_feedback like below
table_user
| id | name |
|----|------|
| 1 | john |
| 2 | tony |
| 3 | mona |
table_feedback
| id | rate | user_id | date |
|----|------|---------|----------|
| 1 | 1 | 3 |2015-11-2 |
| 2 | 1 | 2 |2015-11-2 |
| 3 | 1 | 3 |2015-11-1 |
I wanted to show report by date from table_feedback including name and id from table_user and all user will be show if table_feedback didn't contain the user id then this will be return blank data. I have idea about inner join and here is my query. problem is that the query return 2 row only but i need 3 row including table_user id 1 with blank column rate.
Here is my query below.
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_feedback
INNER JOIN table_user
ON table_user.id = table_feedback.user_id
WHERE table_feedback = '2015-11-2'
expected_result_table
| user_id | name | rate | date |
|-------- |------|------|----------|
| 1 |jony | |2015-11-2 |
| 2 |tony | 1 |2015-11-2 |
| 3 |mona | 1 |2015-11-2 |
The solution to this is an outer join. Anytime you find yourself thinking along the lines of "I need to see all rows from this table, regardless of a match in another table..." you should look to an outer join.
We can use an outer join to select all users, and link them to the feedbackTable in our JOIN clause. This will return null values for any columns in the table that don't match up. Try this:
SELECT u.id, u.name, t.rate, t.dateCol
FROM userTable u
LEFT JOIN feedbackTable t ON t.user_id = u.id AND t.dateCol = '2015-11-02';
Here is an SQL Fiddle example. As a side note, it is good practice not to name date columns date since that is a keyword in MySQL.
Edit based on your expected results:
To make sure the date column appears in each row, you can hardcode it into your select. If you choose to use a variable, you won't have to update the date twice each time, you can just update the declaration:
SET #reportDate = '2015-11-02';
SELECT u.id, u.name, t.rate, #reportDate
FROM userTable u
LEFT JOIN feedbackTable t ON t.user_id = u.id AND t.dateCol = #reportDate;
Here is an updated SQL Fiddle.
My guess is you need to use LEFT JOIN:
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_user
LEFT JOIN table_feedback
ON table_user.id = table_feedback.user_id
AND table_feedback.date = '2015-11-2'
In order to include user without feedback you need to use LEFT OUTER JOIN
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
'2015-11-2'
FROM table_feedback
LEFT OUTER JOIN table_user
ON table_user.id = table_feedback.user_id
WHERE table_feedback = '2015-11-2'
You need to do three modifications to your query:
Use LEFT JOIN instead of INNER JOIN (to get all the users)
Change the table order (first the table you want to get all rows
from)
As the user 1 (john) does not have any data in the second table, you
cannot limit the rows in WHERE-clause. Do the limitation in JOIN
instead, so it applies only to the rows that are matching the JOIN.
So:
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_user
LEFT JOIN table_feedback ON table_user.id = table_feedback.user_id and table_feedback.date = '2015-11-02'

join one row to all row and returning all row

can I get data like this from my table
| id_outlet| date | count(msisdn) |
| 34.10.1 | 2014-08 | 0 |
| 34.10.1 | 2014-09 | 3 |
| 34.10.1 | 2014-10 | 2 |
| 34.10.2 | 2014-08 | 1 |
| 34.10.2 | 2014-09 | 0 |
| 34.10.2 | 2014-10 | 0 |
So I have 2 tables
1. table outlet (unique)
2. table sales (detail of table outlet)
As u see in my second table there are 3 periode (2014-08, 2014-09, 2014-10)
I want join that periode with id_outlet in first table like that example.
Can I?
Please Help me
Using a CROSS JOIN:-
SELECT
o.id_outlet,
s_main.periode,
o.branch,
count(msisdn)
FROM
(
SELECT DISTINCT SUBSTRING(date,1,7) AS periode
FROM sales
) s_main
CROSS JOIN outlet o
LEFT OUTER JOIN sales s
ON s_main.periode = SUBSTRING(s.date,1,7)
AND o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
GROUP BY s_main.periode, o.branch, o.id_outlet
If you have a table of dates then you can just use that rather than the sub query to get the dates (which also avoids the potential problem of not having a date in the results for a month where there has been zero sales for any outlet).
Don't worry, be happy!
SELECT
o.id_outlet,
SUBSTRING(s.date,1,7) AS periode,
o.branch
FROM outlet o LEFT JOIN sales s ON o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
ORDER BY o.id_outlet, YEAR(s.DATE), MONTH(s.DATE), branch
You need this query:
SELECT
o.id_outlet,
d.period AS periode,
o.branch,
count(msisdn)
FROM dates d LEFT JOIN outlet o ON d.period = SUBSTRING(o.date,1,7) LEFT JOIN sales s ON o.id_outlet = s.id_outlet
WHERE (o.STATUS LIKE 'STREET%')
GROUP BY CONCAT(d.period, '#', s.id_outlet)
ORDER BY o.id_outlet, d.period, branch

left join, return non matching rows, where clause on right table, group by

Sorry about the complicated title.
I have two tables, customers and orders:
customers - names may be duplicated, ids are unique:
name | cid
a | 1
a | 2
b | 3
b | 4
c | 5
orders - pid is unique, join on cid:
pid | cid | date
1 | 1 | 01/01/2012
2 | 1 | 01/01/2012
3 | 2 | 01/01/2012
4 | 3 | 01/01/2012
5 | 3 | 01/01/2012
6 | 3 | 01/01/2012
So I used this code to get a count:
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
where date between '01/01/2012' and '02/02/2012'
group by name,date
which worked fine but didnt give me null rows when the cid of customers didnt match a cid in orders, e.g. name-c, id-5
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date
So I changed the where to apply to the join instead, which works fine, it gives me the null rows.
So in this example I would get:
name | date | count
a | 01/01/2012 | 3
b | null | 1
b | 01/01/2012 | 3
c | null | 1
But because names have different cid's it is giving me a null row even if the name itself does have rows in orders, which I don't want.
So I'm looking for a way for the null rows to only be returned when any other cid's that share the same name also do not have any rows in orders.
Thanks for any help.
---EDIT---
I have edited the counts for null rows, count never returns null but 1.
The result of
select * from (select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date) as t1 group by name
is
name | date | count
a | 01/01/2012 | 3
b | null | 1
c | null | 1
First, select your date grouped by (name, date), excluding NULLs, then join with a set of distinct names:
SELECT names.name, grouped.date, grouped.count
FROM ( SELECT DISTINCT name FROM customers ) as names
LEFT JOIN (
SELECT customers.name, orders.date, COUNT(*) as count
FROM customers
LEFT JOIN orders ON customers.cid = orders.cid
WHERE date BETWEEN '01/01/2012' AND '02/02/2012'
GROUP BY name,date
) grouped
ON names.name = grouped.name
The best approach would be Group them together based on Cid's and then other parameters.
So you would get the proper output with NULL values based on Left Outer Join.