Select where column is null or not in subquery - mysql

I have 2 tables with names and a schedule of dates related to those names. I am trying to get a list of all names that have never been scheduled or have not been scheduled in the last 2 months.
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON n.id = s.nameid
WHERE s.nameid IS NULL
OR s.nameid NOT IN (SELECT nameid
FROM schedule
WHERE date > NOW() - INTERVAL 2 MONTH)
GROUP BY n.id
When I run this query MySQL takes over the CPU and doesn't respond.
When I change it to either of these I get results, but only half the results I am looking for:
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON n.id = s.nameid
WHERE s.nameid IS NULL
GROUP BY n.id
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON n.id = s.nameid
WHERE s.nameid NOT IN (SELECT nameid
FROM schedule
WHERE date > NOW() - INTERVAL 2 MONTH)
GROUP BY n.id
I am not sure how to get this query working to return all the results or why it takes over the CPU.

OR is a notorious poor performer, in every database I can think of. A UNION if often a better alternative:
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON n.id = s.nameid
WHERE s.nameid IS NULL
GROUP BY n.id
UNION
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON n.id = s.nameid
WHERE s.nameid NOT IN (SELECT nameid
FROM schedule
WHERE date > NOW() - INTERVAL 2 MONTH)
GROUP BY n.id
UNION will remove duplicates -- if there's no concern about duplicates, use UNION ALL (it's faster, because it doesn't remove duplicates).
Additionally, the latter query has criteria against the OUTER JOIN'd table in the WHERE clause -- this criteria is applied after the JOIN. You might get different results using:
SELECT n.name, MAX(s.date)
FROM names n
LEFT JOIN schedule s ON s.nameid = n.id
AND s.nameid NOT IN (SELECT nameid
FROM schedule
WHERE date > NOW() - INTERVAL 2 MONTH)
GROUP BY n.id
In the example above, the criteria will be applied before the OUTER JOIN. For INNER JOINs, this can be ignored -- criteria in either spot is equivalent.

Without knowing your full schema, is there something wrong with
SELECT n.name, max(s.date)
FROM names n
LEFT JOIN schedule s
ON n.id = s.nameid
WHERE s.nameid IS NULL
OR s.date <= DATE_SUB(CURDATE(), INTERVAL 2 MONTH)
GROUP BY n.id

Related

Combine two conditions with diffrent tables MySQL

Can I make one query with this two conditions? And if yes, which way should I dig?
SELECT c.ID, c.DateEnd FROM conference c WHERE DateEnd = DATE_ADD(CURDATE(),INTERVAL 1 DAY)
SELECT a.ID, a.IDConf FROM application a GROUP BY a.IDConf HAVING COUNT(a.IDConf) >= 2
if you want join only the result for HAVING COUNT(a.IDConf) >= 2 you could use a inner join on subselect
SELECT c.ID, c.DateEnd
FROM conference c
INNER JOIN (
SELECT a.ID, a.IDConf
FROM application a
GROUP BY a.IDConf
HAVING COUNT(a.IDConf) >= 2
) t ON c.ID=t.IDConf
WHERE c.DateEnd = DATE_ADD(CURDATE(),INTERVAL 1 DAY)
You can use a left join. Try this:
SELECT c.ID, a.ID a_ID, c.DateEnd
FROM conference c LEFT JOIN application a
ON c.ID=a.IDConf
WHERE DateEnd=DATE(DATE_ADD(CURDATE(),INTERVAL 1 DAY))
GROUP BY c.ID, a.ID
HAVING COUNT(a.IDConf)>=2;
See MySQL Join Made Easy for insight on using joins.

MySQL count() within subquery

I am trying to count the number of bookings for the next 7 days with the following query.
select calendarDate,
(
select COUNT(*)
FROM isBooked INNER JOIN booking
ON isbooked.BookingID = booking.bookingID
where specificday between booking.startDate and booking.endDate
)
from calendar as specificday
where calendardate between '2015-08-23' and DATE_ADD('2015-08-23', INTERVAL 6 DAY);
I have used SQL server which allows the use of 'as specificday' however MySQL does not, how would i rewrite the query in mysql.
specificday refers to a table, not a column. You need a column name for the WHERE clause:
select c.calendarDate,
(select COUNT(*)
from isBooked ib INNER JOIN
booking b
ON ib.BookingID = b.bookingID
where c.calendarDate between b.startDate and b.endDate
)
from calendar c
where c.calendardate between '2015-08-23' and DATE_ADD('2015-08-23', INTERVAL 6 DAY);

Using mysql GROUP_CONCAT and WHERE

I have the following tables in my DB
EVENT
ID, TITLE, ...
VOTES
ID, TYPE, ID_EVENT
COMMENTS
ID, COMMENT, ID_EVENT
DATES
ID, DATE, ID_EVENT
One EVENT has many actions, has many comments and has many dates.
I'm using following query to retrieve info from EVENTS table, and for each event retrieve the number of votes, the number of comments and each one of the dates. For events with one of their date = tomorrow (2015-04-03)
SELECT events.id,
events.title,
GROUP_CONCAT(dates.date) AS dates,
COUNT(distinct votes.id) AS votes,
COUNT(distinct comments.id) AS comments
FROM events
LEFT JOIN dates on dates.post_id = events.id
LEFT JOIN votes on votes.post_id = events.id AND votes.type = 1
LEFT JOIN comments on comments.votes_id = votes.id
WHERE dates.date = CURDATE() + INTERVAL 1 DAY
GROUP BY events.id
Result looks like this
id title dates votes comment
33 Event33 2015-04-03,2015-04-03,2015-04-03 4 0
39 Event39 2015-04-03 9 1
Why the dates column repeats the same date (tomorrow)??? The dates of Event33 should be 2015-04-01, 2015-04-02, 2015-04-03.
What is wrong?
You want the events that have one of their dates tomorrow. Your query does that but it also cuts off all other dates.
You need an extra join or an EXISTS subquery.
You also need a DISTINCT on the GROUP_CONCAT(), the same way you used it at the COUNT() aggregate:
SELECT events.id,
events.title,
GROUP_CONCAT(DISTINCT dates.date) AS dates,
COUNT(DISTINCT votes.id) AS votes,
COUNT(DISTINCT comments.id) AS comments
FROM events
LEFT JOIN dates ON dates.post_id = events.id
LEFT JOIN votes ON votes.post_id = events.id AND votes.type = 1
LEFT JOIN comments ON comments.votes_id = votes.id
WHERE EXISTS
( SELECT 1
FROM dates AS dd
WHERE dd.date = CURDATE() + INTERVAL 1 DAY
AND dd.post_id = events.id
)
GROUP BY events.id ;
Another way would be using inline subqueries. No need for GROUP BY or DISTINCT in this. A minor disadvantage in your case, is that the join to comments is through votes, so one subquery has an extra join:
SELECT e.id,
e.title,
( SELECT GROUP_CONCAT(d.date)
FROM dates AS d
WHERE d.post_id = e.id
) AS dates,
( SELECT COUNT(v.id)
FROM votes AS v
WHERE v.post_id = e.id AND v.type = 1
) AS votes,
( SELECT COUNT(c.id)
FROM comments AS c
JOIN votes AS v ON c.votes_id = v.id
WHERE v.post_id = e.id AND v.type = 1
) AS comments
FROM events AS e
WHERE EXISTS
( SELECT 1
FROM dates AS dd
WHERE dd.date = CURDATE() + INTERVAL 1 DAY
AND dd.post_id = events.id
) ;

finding closest date from multiple tables mysql

I have many tables that log the users action on some forum, each log event has it's date.
I need a query that gives me all the users that wasn't active in during the last year.
I have the following query (working query):
SELECT *
FROM (questions AS q
INNER JOIN Answers AS a
INNER JOIN bestAnswerByPoll AS p
INNER JOIN answerThumbRank AS t
INNER JOIN notes AS n
INNER JOIN interestingQuestion AS i ON q.user_id = a.user_id
AND a.user_id = p.user_id
AND p.user_id = t.user_id
AND t.user_id = n.user_id
AND n.user_id = i.user_id)
WHERE DATEDIFF(CURDATE(),q.date)>365
AND DATEDIFF(CURDATE(),a.date)>365
AND DATEDIFF(CURDATE(),p.date)>365
AND DATEDIFF(CURDATE(),t.date)>365
AND DATEDIFF(CURDATE(),n.date)>365
AND DATEDIFF(CURDATE(),i.date)>365
what i'm doing in that query - joining all the tables according to the userId, and then checking each
date column individually to see if it's been more then a year
I was wondering if there is a way to make it simpler, something like finding the max between all dates (the latest date) and compering just this one to the current date
If you want to get best performance, you cannot use greatest(). Instead do something like this:
SELECT *
FROM questions q
JOIN Answers a ON q.user_id = a.user_id
JOIN bestAnswerByPoll p ON a.user_id = p.user_id
JOIN answerThumbRank t ON p.user_id = t.user_id
JOIN notes n ON t.user_id = n.user_id
JOIN interestingQuestion i ON n.user_id = i.user_id
WHERE q.date > curdate() - interval 1 year
AND a.date > curdate() - interval 1 year
AND p.date > curdate() - interval 1 year
AND t.date > curdate() - interval 1 year
AND n.date > curdate() - interval 1 year
AND i.date > curdate() - interval 1 year
You want to avoid datediff() such that MySQL can do index lookup on date column comparisons. Now, to make sure that index lookup works, you should create compound (multi-column) index on (user_id, date) for each one of your tables.
In this compound index, first part (user_id) will be user for faster joins, and second part (date) will be used for faster date comparisons. If you replace * in your SELECT * with only columns mentioned above (like user_id only), you might be able to get index-only scans, which will be super-fast.
UPDATE Unfortunately, MySQL does not support WITH clause for common table expressions like PostgreSQL and some other databases. But, you can still factor out common expression as follows:
SELECT *
FROM questions q
JOIN Answers a ON q.user_id = a.user_id
JOIN bestAnswerByPoll p ON a.user_id = p.user_id
JOIN answerThumbRank t ON p.user_id = t.user_id
JOIN notes n ON t.user_id = n.user_id
JOIN interestingQuestion i ON n.user_id = i.user_id,
(SELECT curdate() - interval 1 year AS year_ago) x
WHERE q.date > x.year_ago
AND a.date > x.year_ago
AND p.date > x.year_ago
AND t.date > x.year_ago
AND n.date > x.year_ago
AND i.date > x.year_ago
In MySQL, you can use the greatest() function:
WHERE DATEDIFF(CURDATE(), greatest(q.date, a.date, p.date, t.date, n.date, i.date)) > 365
This will help with readability. It would not affect performance.

using two cases of exists in sql query

I am trying to make a query that says If the customer has no invoice, but has an appointment in the last 6 months, please give me their clientId and name The following result returns and empty set.
SELECT clients.clientId, clients.studentFirstName, clients.studentLastName
FROM clients, invoices, appointments
WHERE (NOT EXISTS
(SELECT *
FROM invoices, clients
WHERE invoices.clientId = clients.clientId))
AND (EXISTS
(SELECT * FROM appointments, clients
WHERE appointments.clientId = invoices.clientId
AND appointments.date >= DATE_ADD(curdate(), INTERVAL 6 MONTH)));
EDIT: The query that ended up working was created after a little tweaking of john's answer:
SELECT a.clientID,
a.studentFirstName,
a.studentLastName
FROM clients a
LEFT JOIN invoices b
on a.clientID = b.clientID
LEFT JOIN appointments c
on a.clientID = c.clientID
WHERE b.clientId IS NULL AND
c.`date` >= DATE_SUB(curdate(), INTERVAL 6 MONTH)
Use LEFT JOIN instead.
SELECT a.ClientID,
a.studentFirstName,
a.clients.studentLastName
FROM clients a
LEFT JOIN invoices b
on a.ClientID = b.ClientID
LEFT JOIN appointments c
on a.ClientID = c.ClientID
WHERE b.Client IS NULL AND
c.`Date` >= DATE_SUB(curdate(), INTERVAL 6 MONTH)
Are you sure it's supposed to be DATE_ADD and not DATE_SUB ?
You can use joins:
SELECT a.clientId,
a.studentFirstName,
a.studentLastName
FROM clients a
JOIN appointments b ON a.clientId = b.clientId
AND b.date >= CURDATE() - INTERVAL 6 MONTH
LEFT JOIN invoices c ON a.clientId = c.clientId
WHERE c.clientId IS NULL