MySQL query to match date and null between two tables - mysql

I have two MySQL-tables like this:
desc students;
+---------------------------+---------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+---------------------------+---------------+------+-----+---------+
| student_id | int(11) | NO | PRI | NULL |
| student_firstname | varchar(255) | NO | | NULL |
| student_lasttname | varchar(255) | NO | | NULL |
+---------------------------+---------------+------+-----+---------+
desc studentabsence;
+---------------------------+-------------+------+-----+---------+
| Field | Type | Null | Key | Default |
+---------------------------+-------------+------+-----+---------+
| student_absence_id | int(11) | NO | PRI | NULL |
| student_id | int(11) | YES | | NULL |
| student_absence_startdate | date | YES | | NULL |
| student_absence_enddate | date | YES | | NULL |
| student_absence_type | varchar(45) | YES | | NULL |
+---------------------------+-------------+------+-----+---------+
Then I have this MySQL- query to list students.
Query:
SELECT s.student_id, s.student_firstname, s.student_lastname,
a.student_absence_startdate, a.student_absence_enddate, a.student_absence_type
FROM students s LEFT JOIN studentabsence a ON a.student_id = s.student_id
Whenever a student has absence information this is displayed in the columns
a.student_absence_startdate a.student_absence_enddatea.student_absence_type
Sometimes a student has two or more rows in the table studentabsence then he is listed two times.
My question is if there is any way to be more specific in the query. I would like to list all students from db.students and if there is a row in db.studentabsence with a date between startdate and enddate (for example 2012-07-30) list the student one time with this absence information. Only if there is a match on date.
So something like...
... WHERE (a.student_absence_startdate OR a.student_absence_enddate) IS NULL OR
'2012-07-30' BETWEEN a.student_absence_startdate AND
a.student_absence_enddate ...
It's kinda hard to explain so let me know if you need more information...

I think that you can arrange it with a JOIN on a subselect/subview :
SELECT s.student_id, s.student_firstname, s.student_lastname,
a.student_absence_startdate, a.student_absence_enddate, a.student_absence_type
FROM students s
LEFT JOIN
(SELECT * FROM studentabsence a1 WHERE ('2012-07-30' BETWEEN a1.student_absence_startdate AND a1.student_absence_enddate) ) a
ON a.student_id = s.student_id

I'd use parameters with default values (01/01/1900 00:00:00), like this:
AND ( a.student_absence_startdate >= #P_startdate OR #P_startdate = '01/01/1900 00:00:00' )
AND ( a.student_absence_enddate <= #P_enddate OR #P_enddate = '01/01/1900 00:00:00' )

Related

MYSQL (MariaDB) - Invalid use of group function

I have two tables called addresses and house_sales
addresses
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| house_number_name | varchar(150) | NO | | NULL | |
| address_line1 | varchar(150) | NO | MUL | NULL | |
| address_line2 | varchar(150) | YES | | NULL | |
| address_line3 | varchar(150) | YES | MUL | NULL | |
| town_city | varchar(150) | NO | MUL | NULL | |
| district | varchar(150) | YES | MUL | NULL | |
| county | varchar(150) | YES | MUL | NULL | |
| post_code | varchar(8) | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
house_sales
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| address_id | int(11) unsigned | NO | MUL | NULL | |
| price | int(11) unsigned | NO | MUL | NULL | |
| date | datetime | NO | MUL | NULL | |
| updated_at | datetime | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
+---------------+------------------------------------------------------------+------+-----+---------+----------------+
I'm trying to select all the addresses grouped by address_line1 and then getting the average price for that street. The query works but I want to only select where there is more than one house on the same street. However when I add the AND count(*) > 1 I get the error "Invalid use of group function". Below is the query
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
AND count(*) > 1
GROUP BY `address_line1`
ORDER BY `average_price` desc
I'm not sure why I'm getting this error. I've tried a sub query so I can use HAVING but haven't got this to work. Any help or pointers would be appreciated
You need a having clause to filter on the aggregate expression:
SELECT count(*) as total_sales, avg(price) as average_price, `address_line1`, `town_city`
FROM `house_sales` `hs`
LEFT JOIN `addresses` `a` ON `hs`.`address_id` = `a`.`id`
WHERE `town_city` = 'London'
GROUP BY `address_line1`, `town_city`
HAVING count(*) > 1
ORDER BY `average_price` desc
MySQL extends the SQL standard by allowing the use of aliases in the having clause, so you can also do:
having total_sales > 1
Side notes:
as commented by jarlh, it is a good practice to qualify (prefix) all column names with the table they belong to
it is also a good practice to put all non-aggregated columns in the group by clause (I added town_city, which was missing in your original query) - newer versions of MySQL do not allow this by default
quoting all identifiers is usually not necessary (unless they contain special characters)
There are two ways to go here. One would be to add town_city to the GROUP BY list:
SELECT
address_line1,
town_city,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1, town_city
HAVING COUNT(*) > 1
ORDER BY average_price DESC;
The other would be to just keep your current query but remove town_city from the select list, since you are restricting to just London anyway.
SELECT
address_line1,
COUNT(*) AS total_sales,
AVG(price) AS average_price
FROM house_sales hs
LEFT JOIN addresses a ON hs.address_id = a.id
WHERE town_city = 'London'
GROUP BY address_line1
HAVING COUNT(*) > 1
ORDER BY average_price DESC;

How to return NULL values when joining multiple tables

here are the contents of the tables.
mysql> desc student;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| name | varchar(20) | NO | | NULL | |
| sex | enum('F','M') | NO | | NULL | |
| student_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+------------+------------------+------+-----+---------+----------------+
mysql> desc grade_event;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| date | date | NO | | NULL | |
| category | enum('T','Q') | NO | | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+----------+------------------+------+-----+---------+----------------+
mysql> desc score;
+------------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+-------+
| student_id | int(10) unsigned | NO | PRI | NULL | |
| event_id | int(10) unsigned | NO | PRI | NULL | |
| score | int(11) | NO | | NULL | |
+------------+------------------+------+-----+---------+-------+
What I'm trying to accomplish is to display which students missed a quiz/test, found under 'category' in the grade_event table.
Here's what I've come up with, but am not generating any results;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score is NULL
group by name, event_id;
I've also gone the route of attempting a subquery;
select name, category, sc.event_id
from student s
join score sc on s.student_id=sc.student_id
join grade_event ge on sc.event_id=ge.event_id
where score not in (select score from score)
group by name, event_id;
Any help would be appreciated.
I think you should just replace your join by left join, join is an inner join in MySQL : http://dev.mysql.com/doc/refman/5.7/en/join.html
And take care with group by event_id, it can be useful to precise group by sc.event_id. I don't know in MySQL but in sql server it wouldn't work.
Your second query is necessarily empty as you ask a column to have its values not in its values :)
You need to use an outer join instead of an inner join to get list of students that do not have a corresponding record in the events after creating a carthesian join of students and events:
select name, category, ge.event_id
from (student s
join grade_event ge) --no join condition creates a carthesian join
left join score sc on s.student_id=sc.student_id and sc.event_id=ge.event_id
where sc.score is NULL

SQL Left Inner Join (mysql)

I have been using the following query:
I am using two tables: (there are some others mentioned but not needed for this question)
assessment_criteria
+-------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+----------------+
| id | mediumint(9) | NO | PRI | NULL | auto_increment |
| scheme_of_work_id | mediumint(9) | NO | | NULL | |
| level | char(255) | YES | | NULL | |
| criteria | char(255) | NO | | NULL | |
+-------------------+--------------+------+-----+---------+----------------+
criteria_completed
+------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+--------------+------+-----+---------+----------------+
| id | mediumint(9) | NO | PRI | NULL | auto_increment |
| student_ID | mediumint(9) | NO | | NULL | |
| assessment_criteria_id | mediumint(9) | NO | | NULL | |
| date_marked | date | NO | | NULL | |
| notes | varchar(255) | YES | | NULL | |
| attainment | varchar(15) | YES | | NULL | |
| effort | varchar(15) | YES | | NULL | |
| marked_by | varchar(20) | NO | | NULL | |
+------------------------+--------------+------+-----+---------+----------------+
I was using a query like this to display a list of assessment criteria that a student HAS NOT completed:
SELECT DISTINCT assessment_criteria.id, assessment_criteria.level, assessment_criteria.criteria FROM assessment_criteria, criteria_completed
WHERE (assessment_criteria.scheme_of_work_id = '17')
AND (assessment_criteria.id NOT IN (SELECT criteria_completed.assessment_criteria_id FROM criteria_completed WHERE (student_ID = '403')))
ORDER BY level;
This query has become incredibly slow to run, I have been trying to make it faster using LEFT JOIN.
SELECT DISTINCT a.id, a.level, a.criteria
FROM assessment_criteria a
LEFT JOIN criteria_completed b
ON a.id = b.assessment_criteria_id
WHERE b.assessment_criteria_id IS NULL
But I am having no success when I try to add in clauses for project and student; ie.
SELECT DISTINCT a.id, a.level, a.criteria
FROM assessment_criteria a
LEFT JOIN criteria_completed b
ON a.id = b.assessment_criteria_id
WHERE b.assessment_criteria_id IS NULL
AND (b.student_ID = '403')
AND (a.scheme_of_work_id = '17');
mysql reports "empty set". I suspect I am referencing these foreign keys incorrectly?
(Just to confirm, you are using b.assessment_criteria_id IS NULL to detect failed joins)
Applying the filters on table b to the WHERE clause will filter out any records where the join has failed, which I believe is the cause of the problem.
You can try moving the b filters into the JOIN condition:
SELECT DISTINCT a.id, a.level, a.criteria
FROM assessment_criteria a
LEFT JOIN criteria_completed b
ON a.id = b.assessment_criteria_id
AND (b.student_ID = 403)
WHERE b.assessment_criteria_id IS NULL
AND (a.scheme_of_work_id = 17);
Although personally, I dislike filtering like this in a JOIN. The alternative would be:
SELECT DISTINCT a.id, a.level, a.criteria
FROM assessment_criteria a
LEFT JOIN criteria_completed b
ON a.id = b.assessment_criteria_id
WHERE (a.scheme_of_work_id = 17)
AND (b.assessment_criteria_id IS NULL OR b.student_ID = 403);

MySQL joining data from 2 tables based on foreign key in a third, without duplicates or group by

I have three tables that look like this:
People:
+------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | | NULL | |
| lname | varchar(32) | NO | | NULL | |
| dob | date | NO | | 0000-00-00 | |
| license_no | varchar(24) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| status | varchar(8) | NO | | Allow | |
+------------+-------------+------+-----+-------------------+----------------+
Units:
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| number | varchar(3) | NO | | NULL | |
| resident | int(11) | NO | MUL | NULL | |
| type | varchar(16) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Visits:
+----------+-----------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vis_id | int(11) | NO | MUL | NULL | |
| unit | int(11) | NO | MUL | NULL | |
| time_in | timestamp | NO | | CURRENT_TIMESTAMP | |
| time_out | timestamp | NO | | 0000-00-00 00:00:00 | |
+----------+-----------+------+-----+---------------------+----------------+
There are multiple foreign keys linking these tables:
units.resident -> people.id
visits.unit -> units.id
visits.vis_id -> people.id
I am able to run this query to find all residents ie - everyone from people that are referenced by the units.resident foreign key:
SELECT concat(p.lname, ', ', p.fname) as 'Resident', p.dob as 'Birthday',
u.number as 'Unit #'
from people p, units u
where p.id = u.resident
order by u.number
It returns the results I want... However, it'd be useful to do the opposite of this to find all the people who are not residents ie- everyone from people who aren't referenced by the units.resident foreign key.
I've tried many different queries, most notably some inner and left joins, but I'm getting waaaaay too many duplicate entries (from what I've read here, this is normal). The only thing I've found that works is using a group by license_no, because as of now the "residents" don't have this information, like this:
SELECT p.id, concat(p.lname, ', ', p.fname) as 'Visitor',
p.license_no as 'License', u.number from people p
left join units u on u.number <> p.id
group by p.license_no order by p.id;
This works for all but one resident, who's u.number is displayed on ALL results. The residents will soon have license_no entries, and I can't have that one odd entry in the returned results all the time, so this query won't work as a long-term solution.
How can I structure a query without a group by that will return the results I want?
This should work
SELECT
p.id
, P.fname
, P.lname
FROM
people AS p
LEFT JOIN
units AS u
ON
p.id = u.resident
WHERE
u.resident IS NULL
Extra hint.
Table people should be called person.
By u.resident you mean a person. so it should be a person_id there in the unit table...
Better logic helps to write SQL better, if your name convention is clear to use.
Use a NOT EXISTS clause to exclude those people who are residents.
SELECT P.id
,P.fname
,P.lname
,etc...
FROM People P
WHERE NOT EXISTS (SELECT 1 FROM Units U WHERE U.resident = P.id)

Finding "duplicate" rows that differ in one column

I have a table like the following in MySQL 5.1:
+--------------+----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+----------------+------+-----+---------+----------------+
| log_id | int(11) | NO | PRI | NULL | auto_increment |
| date | datetime | NO | MUL | NULL | |
| date_millis | int(3) | NO | | NULL | |
| eib_address | varchar(20) | NO | | NULL | |
| ip_address | varchar(15) | NO | | NULL | |
| value | decimal(20,10) | NO | MUL | NULL | |
| application | tinyint(4) | NO | | NULL | |
| phys_address | varchar(20) | NO | | NULL | |
| orig_log_id | bigint(20) | NO | | NULL | |
+--------------+----------------+------+-----+---------+----------------+
In this table, log_id and orig_log_id are always unique. It is possible that two rows may have duplicate values for any of the other fields, though. Ignoring the *log_id fields, our problem is that two rows may be identical in all other columns, but have differing values for value. I am trying to figure out the correct SQL query to identify when two (or more) rows have identical values for date, date_millis and eib_address, but different values for value, log_id and orig_log_id. So far, I've been able to come up with a query that accomplishes the first clause in my previous sentence:
SELECT main.*
FROM sensors_log main
INNER JOIN
(SELECT date, date_millis, eib_address
FROM sensors_log
GROUP BY date, date_millis, eib_address
HAVING count(eib_address) > 1) dupes
ON main.date = dupes.date
AND main.date_millis = dupes.date_millis
AND main.eib_address = dupes.eib_address;
However, I can't seem to figure out when value differs. I at least know that just throwing AND main.value != dupes.value into the ON clause doesn't do it!
I think it's a bit simpler than you're trying to make it. Try this:
SELECT *
FROM SENSORS_LOG s1
INNER JOIN SENSORS_LOG s2
ON (s2.DATE = s1.DATE AND
s2.DATE_MILLIS = s1.DATE_MILLIS AND
s2.EIB_ADDRESS = s1.EIB_ADDRESS)
WHERE s1.VALUE <> s2.VALUE OR
s1.LOG_ID <> s2.LOG_ID OR
s1.ORIG_LOG_ID <> s2.ORIG_LOG_ID;
Share and enjoy.
Maybe I mistook the problem, but can't you just perform a COUNT like this?
SELECT date, date_millis, eib_address, count(*) as nr_dupes
FROM sensors_log
GROUP BY date, date_millis, eib_address
HAVING count(*) > 1
or
SELECT date, date_millis, eib_address,
group_concat(value), group_concat(log_id), group_concat(orig_log_id)
FROM sensors_log
GROUP BY date, date_millis, eib_address
HAVING count(*) > 1