I'm rather new to normalizing tables and I'm having some troubles wrapping my head around getting the correct information out of 3 tables. I made an example involving reserving books out of different libraries. I have 3 tables. Books, locations, reservations (listed below):
//SQL query:
$sql =
"SELECT * FROM books
JOIN (location LEFT JOIN reservations ON location.locID = reservations.locID)
ON books.bookID = location.bookID
WHERE location.locID=2
";
and the output I was hoping to achieve if I were to list the books in Campus B:
title |locName |status
Book 1|Campus B|1
Book 2|Campus B|0
Book 3|Campus B|0
Book 4|Campus B|0
Book 5|Campus B|1
For some reason I'm definitely not getting the output I think I should, and I was curious if anyone has some suggestions. I'm sure once I see whats going on I'll understand what I did incorrectly.
table: books
bookID|title
1 | Book 1
2 | Book 2
3 | Book 3
4 | Book 4
5 | Book 5
table: location
locID|locName
1 | campus A
2 | campus B
3 | campus C
table: reservations
bookID|locID|status
1 | 1 | 1
3 | 1 | 1
4 | 1 | 1
1 | 2 | 1
5 | 2 | 1
4 | 3 | 1
5 | 3 | 1
I think this is more in line with what you're looking for:
SELECT *
FROM books b
LEFT JOIN reservations r ON b.bookID = r.bookID
JOIN location l on r.locID = l.locID
WHERE l.locID = 2
This will return a list of the books reserved at the location with locID=2.
In this case I have a LEFT JOIN to preserve your original query, however given your WHERE clause any records with NULL in the location.locID field will not be selected.
Therefore, I could re-write your query with all INNER joins, like so:
SELECT *
FROM books b
JOIN reservations r ON b.bookID = r.bookID
JOIN location l on r.locID = l.locID
WHERE l.locID = 2
Other queries that you might find interesting:
Get ALL books, regardless of whether or not they are reserved anywhere:
SELECT *
FROM books b
LEFT JOIN reservations r ON b.bookID = r.bookID
JOIN location l on r.locID = l.locID
Get ALL locations, regardless of whether or not there are books reserved there:
SELECT *
FROM books b
JOIN reservations r ON b.bookID = r.bookID
RIGHT JOIN location l on r.locID = l.locID
Get ALL books and ALL locations:
SELECT *
FROM books b
LEFT JOIN reservations r ON b.bookID = r.bookID
RIGHT JOIN location l on r.locID = l.locID
SELECT
books.title
, location.locName
, IFNULL(reservations.status, 0) status
FROM
books
JOIN location
LEFT JOIN reservations ON (
location.locID = reservations.locID
AND books.bookID = location.bookID
)
WHERE location.locID = 2
Related
Main table:
(trip)
id | name
1 | USA
2 | Europe
3 | Asia
Childrens:
(plane)
id | trip_id | name | other
1 | 1 | aaa | w
2 | 1 | bbb | e
3 | 3 | ccc | rr
(boat)
id | trip_id | name
1 | 2 | jjj
2 | 2 | kkk
3 | 3 | lll
If I want to get trips with planes, then I can:
SELECT trip.* FROM trip INNER JOIN plane ON plane.trip_id = trip.id
If I want to get trips with boats, then I can:
SELECT trip.* FROM trip INNER JOIN boat ON boat.trip_id = trip.id
But how to get in one query all trips with planes or boats (or both)?
Simply use UNION ALL:
SELECT trip.* FROM trip INNER JOIN plane ON plane.trip_id = trip.id
UNION ALL
SELECT trip.* FROM trip INNER JOIN boat ON boat.trip_id = trip.id
You can achieve by LEFT join
SELECT trip.*,ifnull(plane.Name,'NA') as planetrip, ifnull(boat.Name,'NA') boattrip FROM trip
LEFT JOIN plane ON plane.trip_id = trip.id
LEFT JOIN boat ON boat.trip_id = trip.id
WHERE (plane.Name IS NOT NULL OR boat.Name IS NOT NULL)
SQL DEMO: http://sqlfiddle.com/#!9/2ae5c/10
Try left joining the trips table to the other two tables. The trick here is that we aggregate at the end by trip and detect if, for each trip, there was a match in the plane or boat tables.
SELECT t.id, t.name,
CASE WHEN COUNT(p.trip_id) > 0 THEN 'yes' ELSE 'no' END AS has_plane,
CASE WHEN COUNT(b.trip_id) > 0 THEN 'yes' ELSE 'no' END AS has_boat
FROM trip t
LEFT JOIN plane p
ON t.id = p.trip_id
LEFT JOIN boat b
ON t.id = b.trip_id
GROUP BY t.id;
id name has_plane has_boat
1 1 USA yes no
2 2 Europe no yes
3 3 Asia yes yes
Demo
You can simply use Left Join like following
select trip.*, isnull(plane.name, '-') as PlaneName,
isnull(boat.name, '-') as BoatName from trip
left join plane ON plane.trip_id = trip.id
left JOIN boat ON boat.trip_id = trip.id
It will return following table, feel free to use any field from any of these 3 tables...
If you want all the result on several rows you can use UNION
but Your table plane and boat have not the same number of column so you should use union with explict column name eg:
SELECT trip_id, plane.name, trip.name
from plane
inner join trip on trip.id = plane.trip_id
UNION
SELECT trip_id, plane.name, trip.name
from bout
inner join trip on trip.id = boat.trip_id
or if you need all values you must use a null column where needed
SELECT trip_id, plane.name, trip.name, other
from plane
inner join trip on trip.id = plane.trip_id
UNION
SELECT trip_id,plane.name, trip.name, null
from bout
inner join trip on trip.id = boat.trip_id
I have a database with 3 tables records, categories, relational.
records (id, lat, lng)
categories (c_id, c_value)
relational (r_id, c_id)
records
id | lat | lng
----------------------
1 23.57258 -35.28412
2 23.54855 -35.18881
3 23.74128 -35.17469
categories
c_id | c_value
---------------
100 groceries
101 bags
102 drinks
relational
id | c_id
------------
1 100
1 102
2 101
3 100
The relational.r_id = records.id and the relational.c_id = categories.c_id
I want to take pairs from records with different c_value, so I want to make a self join in records and inner join in categories and relational.
I've made this without the self join in records
SELECT id, lat, lng, c_value
FROM records
JOIN relational
ON records.id = relational.id
JOIN categories
ON relational.c_id = categories.c_id
WHERE c_value = "V1"
I tried something like this but it didn't work. I have problem with the R1.c_value.
SELECT R1._id, R1.lat, R1.lng, R1.c_value, R2._id, R2.lat, R2.lng,
R2.c_value
FROM records R1, records R2
JOIN relational
ON records.id = relational.id
JOIN categories
ON relational.c_id = categories.c_id
WHERE R1.c_value = "groceries" AND R2.c_value = "bags"
Do you know how can I combine those 3 joins in order to take 2 rows from records with the criteria of the other tables?
I want to have an output like this:
For "groceries" and "bags" as c.value
1 | 23.57258 | -35.28412 | groceries | 2 | 23.54855 | -35.18881 | bags
You can self join records table along with categories and relational like this:
SELECT r1.id, r1.lat, r1.lng, r2.id, r2.lat, r2.lng, c.c_value
FROM records r1 JOIN relational rl ON r1.id = rl.r_id
JOIN records r2 ON r2.id = rl.r_id
JOIN categories c ON rl.c_id = c.c_id
WHERE r1.id <> r2.id;
I finally found exactly what I wanted. My query should be like this. Thanks for the help and the idea anyway.
SELECT r1.id, r1.lat, r1.lng, c1.c_value, r2.id, r2.lat, r2.lng, c2.c_value
FROM records r1 JOIN relational rl1 ON rl1.id = r1.id
JOIN categories c1 ON rl1.c_id = c1.c_id
JOIN records r2 JOIN relational rl2 ON rl2.id = r2.id
JOIN categories c2 ON rl2.c_id = c2.c_id
WHERE c1.c_value = "groceries" AND c2.c_value = "bags"
I have a MySQL table
Booktable
+--------+-------------+-----+
| bookno | bookname | ... |
+--------+-------------+-----+
| 1 | FINALFANTASY| ... |
+--------+-------------+-----+
Authortable
+--------+-------------+-----+
| bookno | Authorname | ... |
+--------+-------------+-----+
| 1 | SQUARE | ... |
+--------+-------------+-----+
| 1 | ENIX | ... |
+--------+-------------+-----+
so I would like to make a search condition to get the book that match with the result.
I try with
select b.bookname,a.authorname from booktable as b
left outer join authortable a on b.bookno = a.bookno
where a.authorname = "square" and a.authorname = "enix"
It only work with only one where condition.but when I try with two authorname there is no result found. what should I do ?
(this query it working with "OR" but not "AND" but I really want the value that match the search condition or if there are some search condition that not match but not blank it should not be showing(so or it not working in this case)
Use aggregation to identify which books have both the authors you want:
SELECT t1.bookname,
t2.authorname
FROM booktable t1
INNER JOIN authortable t2
ON t1.bookno = t2.bookno
INNER JOIN
(
SELECT bookno
FROM authortable
WHERE authorname IN ('square', 'enix')
GROUP BY bookno
HAVING COUNT(DISTINCT authorname) = 2
) t3
ON t1.bookno = t3.bookno
Demo here:
SQLFiddle
Tim Biegeleisen's answer is great, but in case you need exactly match, the the last SQL in the following is correct:
SELECT * FROM book;
SELECT * FROM author;
/* this SQL will return book's author name more than 2 also true */
SELECT b.bookname, a.authorname
FROM book AS b
JOIN author AS a ON b.bookno = a.bookno
JOIN (
SELECT bookno FROM author
WHERE authorname in ('SQUARE', 'ENIX')
GROUP BY 1
HAVING count(*) = 2
) AS a2 ON b.bookno = a2.bookno;
/* this sQL will return only 2 and all matched authors: */
SELECT b.bookname, a.authorname
FROM book AS b
JOIN author AS a ON b.bookno = a.bookno
JOIN (
SELECT bookno FROM author
WHERE authorname in ('SQUARE', 'ENIX')
GROUP BY 1
HAVING count(*) = 2
) AS a2 ON b.bookno = a2.bookno
JOIN (
SELECT bookno FROM author
GROUP BY 1
HAVING count(distinct authorname) = 2
) AS a3 ON b.bookno = a3.bookno
PS1 - no need left join
PS2 - no need count distinct - unless your author table not design properly
If title is FANTASY genre is Adventure,fantasy, and search condition is
[ADVENTURE] = found
[FANTASY] = found
[ADVENTURE,FANTASY] = found
[ADVENTURE,FANTASY,ACTION] = not found
Then the SQL will be:
SELECT b.bookname, a.authorname
FROM book AS b
JOIN author AS a ON b.bookno = a.bookno
JOIN author AS a1 ON b.bookno = a1.bookno AND a1.authorname = 'SQUARE'
JOIN author AS a2 ON b.bookno = a2.bookno AND a2.authorname = 'ENIX'
Above is working, and I m wondering if there is a performance improvement
Table transport
Id | FirstLevSubcat | SecondLevSubcat | ThirdLevSubcat
--------------------------------------------------------
8 | 4 | 27 | 1418
Table categories
Id | CategoriesUrl
--------------------
4 | cars
27 | audi
1418 | audi-100
Query if not to use categories table (without inner join) would be like
SELECT count(*) FROM transport
WHERE FirstLevSubcat = 4 AND SecondLevSubcat = 27 AND ThirdLevSubcat = 1418
Trying to get the same result using INNER JOIN
SELECT count(*) FROM transport main_table
INNER JOIN categories cat_table_first ON cat_table_first.IdRows = main_table.FirstLevSubcat
INNER JOIN categories cat_table_second ON cat_table_second.IdRows = main_table.SecondLevSubcat
INNER JOIN categories cat_table_third ON cat_table_third.IdRows = main_table.ThirdLevSubcat
WHERE
cat_table_first.CategoriesUrl = 'cars'
AND cat_table_second.CategoriesUrl = 'audi'
AND cat_table_third.CategoriesUrl = 'audi-100'
At first sight all works
But is such query ok? May be can improve something?
Your query is correct. You can also do it in following way:
SELECT count(*) FROM transport main_table
INNER JOIN categories cat_table_first ON cat_table_first.IdRows = main_table.FirstLevSubcat and cat_table_first.CategoriesUrl = 'cars'
INNER JOIN categories cat_table_second ON cat_table_second.IdRows = main_table.SecondLevSubcat and cat_table_second.CategoriesUrl = 'audi'
INNER JOIN categories cat_table_third ON cat_table_third.IdRows = main_table.ThirdLevSubcat and cat_table_third.CategoriesUrl = 'audi-100'
You can also do it using 3 EXISTS block.
SELECT count(*) FROM transport main_table
WHERE
EXISTS (SELECT NULL FROM categories WHERE main_table.FirstLevSubcat=categories.IdRows AND categories.CategoriesUrl ='cars')
AND
EXISTS (SELECT NULL FROM categories WHERE main_table.SecondLevSubcat=categories.IdRows AND categories.CategoriesUrl ='audi')
AND
EXISTS (SELECT NULL FROM categories WHERE main_table.ThirdLevSubcat=categories.IdRows AND categories.CategoriesUrl ='audi-100')
My goal is to get a table that counts the correct answers from a game.
For example I want this
| G.Name | E.Action
| game 1 | correctAnswer
| game 1 | correctAnswer
| game 2 | correctAnswer
| game 3 | correctAnswer
| game 3 | correctAnswer
to become this
| G.Name | Count(*)
| game 1 | 2
| game 2 | 1
| game 3 | 2
the problem is that im getting this instead:
| G.Name | Count(*)
| game 1 | 5
| game 2 | 5
| game 3 | 5
where 5 is the sum of 2+1+2
This is my query
SELECT G.Name, Count(*)
FROM enduser EU
INNER JOIN prescription P
ON EU.UserRefID = P.EndUserRefID
INNER JOIN prescriptiongame PG
ON PG.PrescriptionRefID = P.PrescriptionID
INNER JOIN games G
ON G.GameID = PG.GameRefID
INNER JOIN session S
ON S.PrescriptionRefID = P.PrescriptionID
INNER JOIN entries E
ON E.SessionGameRefID = S.Session
WHERE P.EndUserRefID = 889
AND E.Action = 'correctAnswer'
GROUP BY G.Name
ORDER BY G.Name
How can I solve this issue, when I googled people are using the same method but they get good results
Thanks in advance
I'd bet money that one of your joins is inflating the result. Strip your query down to just the critical tables: prescription, session, and entries. Do you still get excessive results? If not, try adding joins back in, one at a time, until you get over-counting.
Your joins are somehow multiplying the number of rows. This happens when you join tables along different dimensions that are not related to each other. This is a common problem, but I don't understand your data structure so the following is a best-guess.
Your example can be fixed by using count(distinct) on something. So try this:
SELECT G.Name, Count(distinct SessionGameRefID)
Should there be a relationship between session and game?
I wonder if this simplification of your query would fix the problem (it removes the first two tables):
SELECT G.Name, Count(*)
FROM prescriptiongame PG
INNER JOIN games G
ON G.GameID = PG.GameRefID
INNER JOIN session S
ON S.PrescriptionRefID = PG.PrescriptionRefID
INNER JOIN entries E
ON E.SessionGameRefID = S.Session
WHERE PG.EndUserRefID = 889
AND E.Action = 'correctAnswer'
GROUP BY G.Name
ORDER BY G.Name;
supposing all the joins are needed try this query:
SELECT G.Name, Count(G.Name)
FROM enduser EU
INNER JOIN prescription P
ON EU.UserRefID = P.EndUserRefID
INNER JOIN prescriptiongame PG
ON PG.PrescriptionRefID = P.PrescriptionID
INNER JOIN games G
ON G.GameID = PG.GameRefID
INNER JOIN session S
ON S.PrescriptionRefID = P.PrescriptionID
INNER JOIN entries E
ON E.SessionGameRefID = S.Session
WHERE P.EndUserRefID = 889
AND E.Action = 'correctAnswer'
GROUP BY G.Name
ORDER BY G.Name
select G.Name , count(*) from enduser EU, prescription P,prescriptiongame PG ,
games G ,session S ,entries E where P.EndUserRefID = 889 and E.action='correctAnswer' and (EU.UserRefID = P.EndUserRefID) and (PG.PrescriptionRefID = P.PrescriptionID) and (G.GameID = PG.GameRefID) and (S.PrescriptionRefID = P.PrescriptionID) and (E.SessionGameRefID = S.Session)
GROUP BY G.Name
ORDER BY G.Name