Getting JOIN and WHERE to work together - mysql

Ok, I have an example table with the following information and query.
First up is the data, with the question following at the end.
Here's the SQL Dump:
http://pastie.org/private/o7zzajdpm6lzcbqrjolgg
Or you can use the included a visual below:
Purchases Table
| id | brand | date |
1 b1 2000-01-01
2 b1 2000-01-03
3 b2 2000-01-04
4 b3 2000-01-08
5 b4 2000-01-14
Owners Table
id | firstname | lastname | purchaseid | itemCoupon | itemReturned | Accessories
1 Jane Doe 1 yes no 4
2 Jane Doe 2 yes no 2
3 Jane Doe 3 no no 1
4 Jane Doe 4 no no 3
5 Jane Doe 5 no yes 6
The Query
SELECT brand, COALESCE( SUM( inTime.Accessories ) , 0 ) AS acessory_sum
FROM purchases
INNER JOIN owners AS person ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
LEFT JOIN owners AS inTime ON person.id = inTime.id
AND purchases.date
BETWEEN DATE( '2000-01-01' )
AND DATE( '2000-01-05' )
GROUP BY purchases.brand
This gives the following expected result:
| brand | accessory_sum
b1 6
b2 1
b3 0
b4 0
The question
Now, I would like to add to the query:
WHERE itemCoupon = 'yes' OR itemReturned = 'yes'
But this overrides the last join and when I do the same search above I get:
| brand | accessory_sum
b1 6
b2 1
Similarly I still want it to return No results found for 2000-01-04, 2000-01-08 using WHERE itemCoupon = 'yes' OR itemReturned = 'yes'. Removing the WHERE gives me zeros for all brands if I try to do it another way.
Basically I want to keep the way the WHERE behaves but also keep the format that I described in the first example of the expected output.
As it is now, using WHERE destroys the way the last LEFT JOIN works with COALESCE which fills the remaining brand rows with zeros.

Your WHERE turns the outer join into an inner join.
You need to move your additionally condition into the LEFT JOIN condition:
LEFT JOIN owners as inTime
ON person.id = inTime.id
AND purchases.date between purchases.date DATE ('2000-01-01') and DATE ('2000-01-05')
AND (inTime.itemCoupon = 'yes' or inTime.itemReturned = 'yes')

the ON clause when doing a JOIN is similar to the WHERE clause. So instead of trying to use WHERE, just add another AND to your query (and don't forget to use the parenthesis in the OR clause):
SELECT brand,
COALESCE(SUM(Time.purchasedAccessories),0) as acessory_sum
FROM purchases
INNER JOIN owners AS person
ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
AND (person.itemCoupon = 'yes' OR person.itemReturned = 'yes')
LEFT JOIN owners AS inTime
ON person.id= inTime.id
AND purchases.date
BETWEEN purchases.date
DATE( '2000-01-01' )
AND
DATE( '2000-01-05' )
GROUP BY purchases.brand

Related

Query: I have 4 rows, need to add the results from 3 rows into one, and leave the last row untouched

I have a kind of tricky question for this query. First the code:
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
What Am I doing?
For example, I am counting police reports of robbery, made from different kind of users. In my example, "admin" users reported 6 incidents of code "2" (robbery) and so on, as is showed in 'where' clause (incident must be robbery, also code 2).
this brings the following result:
+-----------------------+----------+
| user_type_description | Quantity |
+-----------------------+----------+
| Admin | 6 |
| Moderator | 8 |
| Fully_registered_user | 8 |
| anonymous_user | 9 |
+-----------------------+----------+
Basically Admin,Moderator and Fully_registered_user are appropriately registered users. I need to add them in a result where it shows like:
+--------------+------------+
| Proper_users | Anonymous |
+--------------+------------+
| 22 | 9 |
+--------------+------------+
I am not good with sql. Any help is appreciated. Thanks.
You can try to use condition aggregate function base on your current result set.
SUM with CASE WHEN expression.
SELECT SUM(CASE WHEN user_type_description IN ('Admin','Moderator','Fully_registered_user') THEN Quantity END) Proper_users,
SUM(CASE WHEN user_type_description = 'anonymous_user' THEN Quantity END) Anonymous
FROM (
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
) t1
You just need conditional aggregation:
SELECT SUM( ut.user_type_description IN ('Admin', 'Moderator', 'Fully_registered_user') ) as Proper_users,
SUM( ut.user_type_description IN ('anonymous_user') as anonymous
FROM incident i INNER JOIN
user u
ON i.user_id = u.user_id INNER JOIN
user_type ut
ON u.user_type = ut.user_type
WHERE i.code = 2;
Notes:
Table aliases make the query easier to write and to read.
This uses a MySQL shortcut for adding values -- just just adding the booelean expressions.
I would solve it with a CTE, but it would be better to have this association in a table.
WITH
user_type_categories
AS
(
SELECT 'Admin' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Moderator' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Fully_registered_user' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'anonymous_user' AS [user_type_description] , 'Anonymous' AS [user_type_category]
)
SELECT
CASE WHEN utc.[user_type_category] = 'Proper_users' THEN
SUM(incident.user_id)
END AS [Proper_Users_Quantity]
, CASE WHEN utc.[user_type_category] = 'Anonymous' THEN
SUM(incident.user_id)
END AS [Anonymous_Quantity]
FROM
[incident]
INNER JOIN [user] ON [incident].[user_id] = [user].[user_id]
INNER JOIN [user_type] ON [user].[user_type] = [user_type].[user_type]
LEFT JOIN user_type_categories AS utc ON utc.[user_type_description] = [user_type].[user_type_description]
WHERE
[incident].[code] = 2

GROUP BY & COUNT with multiple parameters

I have a simple configuration :
2 tables linked in a many-to-many relation, so it gave me 3 tables.
Table author:
idAuthor INT
name VARCHAR
Table publication:
idPublication INT,
title VARCHAR,
date YEAR,
type VARCHAR,
conference VARCHAR,
journal VARCHAR
Table author_has_publication:
Author_idAuthor,
Publication_idPublication
I am trying to get all the authors name that have published at least 2 papers in conference SIGMOD and conference PVLDB.
Right now I achieved this but I still have a double result. My query :
SELECT author.name, publication.journal, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication = publication.idPublication
GROUP BY publication.journal, author.name
HAVING COUNT(*) >= 2
AND (publication.journal = 'PVLDB' OR publication.journal = 'SIGMOD');
returns
+-------+---------+----------+
| name | journal | COUNT(*) |
+-------+---------+----------+
| Renee | PVLDB | 2 |
| Renee | SIGMOD | 2 |
+-------+---------+----------+
As you can see the result is correct but doubled, as I just want 1 time the name.
Other question, how to modify the number parameter for only one conference, for example get all the author that published at least 3 SIGMOD and at least 1 PVLDB ?
If you don't care about the journal , don't select it, it is splitting your results. Also, normal filters need to be placed in the WHERE clause, not the HAVING clause :
SELECT author.name, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication =
publication.idPublication
WHERE publication.journal IN('PVLDB','SIGMOD')
GROUP BY author.name
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 2
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 2;
For the second question, use this HAVING() clause :
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 3
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 1;

MySQL query to select the max date for the relation table based on criteria at second level

Here is my SQLFIDDLE
Basically I have three tables, A issues, journals and journal details.
I would like to have in a single query the following way of representation.
id | status_id | X |
90001 | 12 | NULL |
90002 | 12 | NULL |
90003 | 12 | 2015-01-06 |
90004 | 12 | 2015-01-09 |
The rule applied is for X is the max 'journals' created date at which the 'fixed_version_id' == 55 exists.
Please help.
Thank You,
I recommend you start by getting the details of all the journals that meet your requirement like this:
SELECT *
FROM journal_details
WHERE property = 'fixed_version_id' AND value = '55';
Then you can use those values to get the created date of the journal rows that meet this requirement:
SELECT j.issue_id, MAX(j.created_on) AS created_on
FROM journals j
JOIN journal_details jd ON jd.journal_id = j.id AND jd.property = 'fixed_version_id' AND jd.value = '55'
GROUP BY j.issue_id;
From these results, you can join in to get all issues. If you use an outer join, you'll get null for any journals that didn't meet the criteria:
SELECT i.id, i.status_id, tmp.created_on
FROM issues i
LEFT JOIN(
SELECT j.issue_id, MAX(j.created_on) AS created_on
FROM journals j
JOIN journal_details jd ON jd.journal_id = j.id AND jd.property = 'fixed_version_id' AND jd.value = '55'
GROUP BY j.issue_id
) tmp ON tmp.issue_id = i.id;
Here is an SQL Fiddle example.

Select from one table but filtering other two

Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2 something like this
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, with my current DB, the data at the begining is just a sample. SQLFiddle
Any help will be really appreciated.
Solution using EXISTS:
select *
from book b
where exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 1)
and exists (select 'x'
from book_categ x
where x.idbook = b.idbook
and x.idcateg = 2)
Solution using join with an inline view:
select *
from book b
join (select idbook
from book_categ
where idcateg in (1, 2)
group by idbook
having count(*) = 2) x
on b.idbook = x.idbook
You could try using ALL instead of IN (if you only want values that match all criteria to be returned):
SELECT book.*
FROM book, book_categ
WHERE book_categ.idCateg = ALL(1 , 2)
One way to get the result is to do join to the book_categ table twice, something like
SELECT b.*
FROM book b
JOIN book_categ c1
ON c1.book_id = b.id
AND c1.idCateg = 1
JOIN book_categ c2
ON c2.book_id = b.id
AND c2.idCateg = 2
This assumes that (book_id, idCateg) is constrained to be unique in the book_categ table. If it isn't unique, then this query can return duplicate rows. Adding a GROUP BY clause or the DISTINCT keyword will eliminate any generated duplicates.
There are several other queries that can get generate the same result.
For example, another approach to finding book_id that are in two categories is to get all the rows with idCateg values of 1 or 2, and then GROUP BY book_id and get a count of DISTINCT values...
SELECT b.*
FROM book b
JOIN ( SELECT d.book_id
FROM book_categ d
WHERE d.idCateg IN (1,2)
GROUP BY d.book_id
HAVING COUNT(DISTINCT d.idCateg) = 2
) c
ON c.book_id = b.id

Mysql left join table not between dates

I have the following issue, I've got 3 tables the first one is called courses where I have
courses| id | start | end |
--------------------------------------
1 2012-10-12 | 2012-11-12 |
students| id | available_start | available_end |
-------------------------------------------------
1 2012-10-13 2012-11-11
2 2012-11-06 2012-11-08
students_to_courses | student_id | course_id |
-------------------------------------------------
1 1
So I'm trying to find which students are available for courses periods. So if the student is added to student_to_courses and dates are between the course dates I don't need it.
I've got the feeling that the query should be with a sub query but I really don't understand them. My query now is looking like this but doesn't work properly.
SELECT s.id
FROM (`students` s)
LEFT JOIN `student_to_course` s2c ON `s2c`.`student_id` = `s`.`id`
LEFT JOIN `courses` assigned_2_course ON `s2c`.`course_id` = `assigned_2_course`.`id`
LEFT JOIN `courses` c ON `c`.`id` = 1
WHERE
(
(s.available_start NOT BETWEEN assigned_2_course.start AND assigned_2_course.end
AND
s.aviailable_end NOT BETWEEN assigned_2_course.start AND assigned_2_course.end
) OR assigned_2_course.end IS NULL)
AND
`s`.`available_start` BETWEEN c.start AND c.end
AND `s`.`available_end` <= c.end
GROUP BY `s`.`id`
ORDER BY `s`.`id` desc
Here is http://sqlfiddle.com/#!2/49c11/1
now works, but doesn't remove the students which are assigned in other courses with same dates how you can see I'm trying to get available students for course 3 which starts 02-03 and ends 02-08, student 2 is in course 3 so is not shown, student 1 is in course 2 which starts 01-03 and ends 03-01 so shouldn't be available.
Any help will be appreciated.
I used your SQL fiddle (but added another student record) http://sqlfiddle.com/#!2/246645/1
try this to find all students that could attend course 3 because they are not in a class during that time:
SELECT student.*
FROM student
JOIN course
ON course.id = 3
AND student.available_start <= course.`start`
AND student.available_end >= course.`end`
WHERE NOT EXISTS
(SELECT *
FROM student_to_course
JOIN course AS c
ON student_to_course.course_id = c.id
WHERE student.id = student_to_course.student_id
AND (course.`start` BETWEEN c.`start` AND c.`end`
OR
course.`end` BETWEEN c.`start` AND c.`end`
OR
c.`start` BETWEEN course.`start` AND course.`end`));