MySQL SELECT JOIN with empty row returns - mysql

I have the following MySQL tables:
[users]
| id | name |
|----|---------|
| 1 | John |
| 2 | Anna |
| 3 | Peter |
[times]
| user_ID | date | time |
|---------|------------|--------|
| 1 | 2020-03-20 | 07:00 |
| 1 | 2020-03-21 | 08:00 |
| 3 | 2020-03-22 | 09:00 |
my query look like:
SELECT name, date, time
FROM users
INNER JOIN times ON times.user_ID = users.id
WHERE date = '2020-03-22';
what i get is:
| name | date | time |
|---------|------------|--------|
| Peter | 2020-03-22 | 09:00 |
what i want is:
| name | date | time |
|---------|------------|--------|
| John | | |
| Anna | | |
| Peter | 2020-03-22 | 09:00 |
is there a way to join non existent lines (not fields!) in the times table with the users table?

Use LEFT JOIN. And then you need to put the restrictions on the second table into the ON clause.
SELECT name, date, time
FROM users
LEFT JOIN times ON times.user_ID = users.id AND date = '2020-03-22';

use left join and move where clause to on:
SELECT name, date, time
FROM users
left JOIN times ON times.user_ID = users.id and date = '2020-03-22';

Related

mysql - WHERE records does not have records in another table

I have some tables from which I need to get data.
Here is my structure:
employees
| id | name |
+----+---------+
| 1 | Michael |
| 2 | Sarah |
reports
| id | employee_id | month | year | value | group_id |
+----+-------------+-------+------+-------+----------+
| 1 | 1 | 01 | 2018 | 35 | 1 |
| 2 | 1 | 02 | 2018 | 12 | 1 |
| 3 | 2 | 02 | 2018 | 2 | 2 |
groups
| id | name | employee_id |
+----+------+-------------+
| 1 | G11 | 1 |
| 2 | Z15 | 2 |
Now I need to get groups with employee WHERE employee with group_id AND month AND year DON'T HAVE REPORT, eg.
When I look for 01.2018, it should returns me only Z15 but when I look for 04.2018 it should return Z15 and G11.
How can I do this? At this moment I have sth like this:
SELECT
groups.*,
employees.*,
-- all fields from reports
FROM
groups
INNER JOIN
employees
ON
employees.id = groups.employee_id
My column names are slightly different from yours. That's deliberate...
SELECT g.*
FROM groups g
LEFT
JOIN reports r
ON r.group_id = g.group_id
AND r.yearmonth = 201801
WHERE r.report_id IS NULL;

MySQL Count Comma Delimited

I have 3 tables like this:
table_events
+------+----------+----------------------+
| ID | Title | Employees |
+------+----------+----------------------+
| 1 | Event1 | john,james |
+------+----------+----------------------+
| 2 | Event2 | sarah,jessica |
+------+----------+----------------------+
table_check_in
+------+----------+----------+---------------------+
| ID | Time | EventID | By |
+------+----------+----------+---------------------+
| 1 | 08:30 | 1 | john |
+------+----------+----------+---------------------+
| 2 | 08:30 | 1 | james |
+------+----------+----------+---------------------+
| 3 | 09:30 | 1 | john |
+------+----------+----------+---------------------+
| 4 | 10:30 | 2 | sarah |
+------+----------+----------+---------------------+
| 5 | 10:35 | 2 | sarah |
+------+----------+----------+---------------------+
table_problems
+------+----------------+----------+---------------------+
| ID | Comment | EventID | By |
+------+----------------+----------+---------------------+
| 1 | Broken door | 1 | john |
+------+----------------+----------+---------------------+
| 2 | Slippery floor | 1 | john |
+------+----------------+----------+---------------------+
| 3 | Leaking tap | 1 | john |
+------+----------------+----------+---------------------+
| 4 | Broken window | 2 | jessica |
+------+----------------+----------+---------------------+
| 5 | Broken glass | 2 | jessica |
+------+----------------+----------+---------------------+
I would like to print something like this:
+------+----------+---------------+-------------------+-------------------+
| ID | Title | Employees | Count_Check_In | Count_Problems |
+------+----------+---------------+-------------------+-------------------+
| 1 | Event1 | john,james | john:2,james:1 | john:3,james:0 |
+------+----------+---------------+-------------------+-------------------+
| 2 | Event2 | sarah,jessica | sarah:2,jessica:0 | sarah:0,jessica:2 |
+------+----------+---------------+-------------------+-------------------+
I know this problem would be trivial if the database was designed properly, but we don't have the luxury of an application rewrite at the moment.
You need to initially get all the employees for each event id from check in and problem tables by using a union.
Then left join the counts from each of check in and problems table to the previous result to get the 0 counts as well.
Finally use a group_concat to get the result in one row for each event id.
select te.id,te.title,te.employees
,group_concat(concat(t.`By`,':',coalesce(tccnt.cnt,0))) count_check_in
,group_concat(concat(t.`By`,':',coalesce(tpcnt.cnt,0))) count_problems
from table_events te
left join (select eventid,`By` from table_check_in
union
select eventid,`By`from table_problems) t on te.id = t.eventid
left join (select eventid,`By`,count(*) cnt from table_check_in group by eventid,`By`) tccnt on tccnt.eventid = t.eventid and tccnt.`By`=t.`By`
left join (select eventid,`By`,count(*) cnt from table_problems group by eventid,`By`) tpcnt on tpcnt.eventid = t.eventid and tpcnt.`By`=t.`By`
group by te.id,te.title,te.employees
Sample Demo (thanks to #valex for setting up the schema)
You can use GROUP_CONCAT to get a result. Here is an example. The only thing missed is employees with 0 check ins or problems.
SELECT ID, Title,Employees,
GROUP_CONCAT(DISTINCT CONCAT(check_in.`By`,':',check_in.cnt))
as Count_Check_In,
GROUP_CONCAT(DISTINCT CONCAT(problems.`By`,':',problems.cnt))
as Count_Problems
FROM table_events
LEFT JOIN (SELECT EventID,`By`, COUNT(*) as cnt
FROM table_check_in
GROUP BY EventID,`By`) as check_in
ON table_events.ID = check_in.EventID
LEFT JOIN (SELECT EventID,`By`, COUNT(*) as cnt
FROM table_problems
GROUP BY EventID,`By`) as problems
ON table_events.ID = problems.EventID
GROUP BY table_events.id
Demo

Grouping by date and combining data from 3 tables

The following are the three tables I have where session.id = signup.session_id AND session.loc_id = location.id. The max override is as the name suggest override the default max capacity for the location hence IFNULL(session.max_override, location.max_cap).
mysql> SELECT * FROM session;
+----+---------------------+---------------+--------+
| id | date_time | max_override | loc_id |
+----+---------------------+---------------+--------+
| 1 | 2014-02-04 10:30:00 | 35 | 2 |
| 2 | 2014-02-04 17:00:00 | | 2 |
| 3 | 2014-02-06 11:30:00 | 50 | 2 |
| 4 | 2014-02-09 13:30:00 | | 1 |
+----+---------------------+---------------+--------+
mysql> SELECT * FROM location;
+-----------------+---------+
| id | location | max_cap |
+-----------------+---------+
| 1 | up | 20 |
| 2 | down | 103 |
| 3 | right | 50 |
| 4 | left | 50 |
+-----------------+---------+
mysql> SELECT * FROM signups;
+-----------------+------------+
| id | name | session_id |
+-----------------+------------+
| 1 | test | 3 |
| 2 | admin | 1 |
| 3 | meme | 2 |
| 4 | anna | 4 |
+-----------------+------------+
The report I am trying to create looks simple but I am not sure how to approach the problem. The following is how I would like the report/output to look like..
mysql> query ouput;
+------------+----------+-----------+----------+----------+-----------+----------+
| date | am_time | am_ses_id | am_spots | pm_time | pm_ses_id | pm_spots |
+------------+----------+-----------+----------+----------+-----------+----------+
| 2014-02-04 | 10:30 AM | 1 | 34 | 05:00 PM | 2 | 102 |
| 2014-02-06 | 11:30 AM | 3 | 49 | | | |
| 2014-02-09 | | | | 01:30 PM | 4 | 49 |
+------------+----------+-----------+----------+----------+-----------+----------+
I can group the date and time correctly and also managed to get the session_id to match since it is all within one table but to calculate the am/pm spots which is nothing but counting the records in signups table for a particular session and deducting the value from either the max_cap or max_override depending on the situation.
THIS is what I tried
Using the following query
SELECT
DATE_FORMAT(a.date_time,'%m/%d/%Y') AS ses_date,
DATE_FORMAT(a.date_time,'%r') AS ses_time,
a.id,
COUNT(b.id) as signed_up,
IFNULL(a.max_override,c.max_cap) AS cap
FROM
test.session a
LEFT JOIN
test.signups b
ON (b.session_id = a.id)
LEFT JOIN
test.location c
ON (c.id = a.loc_id)
GROUP BY b.session_id
I get the following output
+------------+----------+--------+-----------+------+
| date | ses_time | ses_id | signed_up | cap |
+------------+----------+--------+-----------+------+
| 2014-02-04 | 10:30 AM | 1 | 1 | 35 |
| 2014-02-04 | 05:00 PM | 2 | 1 | 103 |
| 2014-02-06 | 10:30 AM | 3 | 1 | 50 |
| 2014-02-09 | 10:30 AM | 4 | 1 | 50 |
+------------+----------+--------+-----------+------+
But I cannot seem to find a way to group it only by the date so the output would appear like desired! I don't know if I should union two queries either.
Here is a very convoluted way of doing it...
sqlfiddle: http://sqlfiddle.com/#!2/d85ca/11
select c.ses_date `date`, a.ses_time am_time, a.id am_ses_id, a.cap-a.signed_up am_spots,
b.ses_time pm_time, b.id pm_ses_id, b.cap-b.signed_up pm_spots
from (
select distinct DATE_FORMAT(a.date_time,'%m/%d/%Y') ses_date
from session a) c
left join (
SELECT
DATE_FORMAT(a.date_time,'%m/%d/%Y') AS ses_date,
DATE_FORMAT(a.date_time,'%r') AS ses_time,
a.id,
COUNT(b.id) as signed_up,
IFNULL(a.max_override,c.max_cap) AS cap
FROM
session a
LEFT JOIN
signups b
ON (b.session_id = a.id)
LEFT JOIN
location c
ON (c.id = a.loc_id)
where date_format(a.date_time, '%p') = 'AM'
GROUP BY b.session_id) a on c.ses_date = a.ses_date
left join (
SELECT
DATE_FORMAT(a.date_time,'%m/%d/%Y') AS ses_date,
DATE_FORMAT(a.date_time,'%r') AS ses_time,
a.id,
COUNT(b.id) as signed_up,
IFNULL(a.max_override,c.max_cap) AS cap
FROM
session a
LEFT JOIN
signups b
ON (b.session_id = a.id)
LEFT JOIN
location c
ON (c.id = a.loc_id)
where date_format(a.date_time, '%p') = 'PM'
GROUP BY b.session_id) b on c.ses_date = b.ses_date;
You need to use the JOIN operator to let the SQL DB know the relationship between the tables.
In this case also, it may be easier to do a subquery to get the count (to avoid GROUP BY). I've not separated out AM and PM by day but you could do that.
SELECT session.date_time,
IFNULL(session.max_override,location.max_cap)-(
SELECT COUNT(signups.id)
FROM signups
WHERE signups.session_id = session.id) as avail_spots
FROM session LEFT JOIN location ON session.loc_id = location.id;
Note the LEFT JOIN will include 2014-02-04 17:00:00 with a NULL avail_spots since neither max_override nor max_cap have a value, whereas INNER JOIN would not report that session at all.
fiddle
EDIT: once you have the information by day you can use it on output. Trying to pivot out the times but grouping on the date adds a lot of complexity to the query that could be solved a lot more simply by whatever program you are using for your UI.

Join multiple tables and result multiple records

I have 3 tables with information.
Table1: Orders
+---------+----------------+------------+---------------+
| OrderID | OrderDate | Community | Status |
+-------------------------------------------------------+
1 | 1 march 2013 | S1 | Approved
2 | 5 march 2013 | S2 | Aporoved
3 | 7 march 2013 | Z1 | Approved
+-------------------------------------------------------+
Table2: OrderArtickles
+------------------------------------------------------------------+
|Ordertitem | OrderID | ArtikelID | UnitPrice | Delivered |
+------------------------------------------------------------------+
| 1 | 1 | 20 | 5 | yes
| 2 | 1 | 20 | 5 | yes
| 3 | 2 | 21 | 10 | yes
| 4 | 3 | 30 | 50 | yes
+-------------------------------------------------------------------+
Table3: users
+-----------------------------------------------------+
| Userid | Username | Community | Department |
+-----------------------------------------------------+
| 1 | User1 | S1 | S
| 2 | User2 | S2 | S
| 3 | User3 | Z1 | Z
+-----------------------------------------------------+
I need a MySQL query that give the following output:
+--------------------------------------+
| Department | TotalPriceOfArtikels
+--------------------------------------+
| S | 20
| Z | 50
+--------------------------------------+
I tried with JOIN, SUM, GROUP BY but without result. The problem that I have is that the one order gives multiple articles. Who can help me?
try this
select Department , sum(UnitPrice) as TotalPriceOfArtikels
from users u
inner join Orders o
on o.Community = u.Community
inner join OrderArtickles oa
on oa.OrderId = o.OrderId
group by Department
DEMO HERE
OUTPUT:
Department TotalPriceOfArtikels
S 20
Z 50
Something like this:
select us.Department,
sum ( art.UnitPrice ) as TotalPriceOfArtikels
from user us
left join orders ors
on ( us.Community = ors.Community)
left join OrderArtickles art
on ( ors.OrderID = art.OrderID)
group by us.Department
------------------EDITED------------------------
I copy the same values and structures of your tables in my mysql, and the result is fine, it give me:
Department TotalPriceOfArtikels
S 20
Z 50
Maybe you want to check a condition, like if delivered = yes or status = aprobed??....with this query the result it's the same that you have posted ;)
Saludos ;)

help in forming mysql query to find free(available) venues/resources for a give date range

I have tables & data like this:
venues table contains : id
+----+---------+
| id | name |
+----+---------+
| 1 | venue 1 |
| 2 | venue 2 |
---------------
event_dates : id, event_id, event_from_datetime, event_to_datetime, venue_id
+----+----------+---------------------+---------------------+----------+
| id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+----------+---------------------+---------------------+----------+
| 1 | 1 | 2009-12-05 00:00:00 | 2009-12-07 00:00:00 | 1 |
| 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+----------+---------------------+---------------------+----------+
This is my requirement: I want venues that will be free on 2009-12-06 00:00:00
i.e.
I should get
|venue_id|
|2 |
Currently I'm having the following query,
select ven.id , evtdt.event_from_datetime, evtdt.event_to_datetime
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where evtdt.venue_id is null
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime);
+----+---------------------+---------------------+
| id | event_from_datetime | event_to_datetime |
+----+---------------------+---------------------+
| 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 |
| 2 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 |
| 3 | NULL | NULL |
| 5 | NULL | NULL |
+----+---------------------+---------------------+
If you note the results, its not including venue id 1 where date is in between 2009-12-06 00:00:00 but showing other bookings.
Please help me correct this query.
Thanks in advance.
SELECT *
FROM venue v
WHERE NOT EXISTS
(
SELECT NULL
FROM event_dates ed
WHERE ed.venue_id = v.id
AND '2009-12-06 00:00:00' BETWEEN ed.event_from_datetime AND ed.event_to_datetime
)
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime);
12/6/2009 is between 12/5/09 and 12/7/09... that's why venue_id 1 is being excluded... what is it you're trying to extract from the data exactly?
The join query you've constructed says, take the venues table and for each row of it that has a matching venue_id make a copy of the venue table row and append the matching row. So if you just did:
select *
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id);
It would yield:
+----+---------+------+----------+---------------------+---------------------+----------+
| id | name | id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+---------+------+----------+---------------------+---------------------+----------+
| 1 | venue 1 | 1 | 1 | 2009-12-05 00:00:00 | 2009-12-07 00:00:00 | 1 |
| 1 | venue 1 | 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 2 | venue 2 | 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+---------+------+----------+---------------------+---------------------+----------+
If you then added your condition, which states the date of interest is not between the from and to date of the event, the query looks like:
select *
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where not ('2009-12-06' between evtdt.event_from_datetime and evtdt.event_to_datetime)
Which yields a result of:
+----+---------+------+----------+---------------------+---------------------+----------+
| id | name | id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+---------+------+----------+---------------------+---------------------+----------+
| 1 | venue 1 | 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 2 | venue 2 | 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+---------+------+----------+---------------------+---------------------+----------+
These are my actual experimental results with your data in MySQL.
If you want to get the venue_ids that are free on the proposed date then you would write something like:
select ven.id, SUM('2009-12-06' between evtdt.event_from_datetime and evtdt.event_to_datetime) as num_intersects
from venues ven left join event_dates evtdt on (ven.id=evtdt.venue_id)
group by ven.id
having num_intersects = 0;
which yields:
+----+----------------+
| id | num_intersects |
+----+----------------+
| 2 | 0 |
+----+----------------+
this also comes up with the right answer (without modification) in the case where you have a venue with no events in the event_date table.
At a guess, if you remove not from
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime)
this will then return row 1 from event dates but not the other event date rows.
I say "at a guess" because your where clause is a bit hard to understand. Maybe you mean
select ven.id , evtdt.event_from_datetime, evtdt.event_to_datetime
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where '2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime;