I have two tables in my MySQL database, one is a library of all of the books in the database, and the other is containing individual rows corresponding to which books are in a user's library.
For example:
Library Table
`id` `title`...
===== ===========
1 Moby Dick
2 Harry Potter
Collection Table
`id` `user` `book`
===== ====== =======
1 1 2
2 2 2
3 1 1
What I want to do is run a query that will show all the books that are not in a user's collection. I can run this query to show all the books not in any user's collection:
SELECT *
FROM `library`
LEFT OUTER JOIN `collection` ON `library`.`id` = `collection`.`book`
WHERE `collection`.`book` IS NULL
This works just fine as far as I can tell. Running this in PHPMyAdmin will result in all of the books that aren't in the collection table.
However, how do I restrict that to a certain user? For example, with the above dummy data, I want book 1 to result if user 2 runs the query, and no books if user 1 runs the query.
Just adding a AND user=[id] doesn't work, and with my extremely limited knowledge of JOIN statements I'm not getting anywhere really.
Also, the ID of the results being returned (of query shown, which doesn't do what I want but does function) is 0-- how do I make sure the ID returned is that of library.id?
You'll have to narrow down your LEFT JOIN selection to only the books that a particular user has, then whatever is NULL in the joined table will be rows(books) for which the user does not have in his/her collection:
SELECT
a.id,
a.title
FROM
library a
LEFT JOIN
(
SELECT book
FROM collection
WHERE user = <userid>
) b ON a.id = b.book
WHERE
b.book IS NULL
An alternative is:
SELECT
a.id,
a.title
FROM
library a
WHERE
a.id NOT IN
(
SELECT book
FROM collection
WHERE user = <userid>
)
However, the first solution is more optimal as MySQL will execute the NOT IN subquery once for each row rather than just once for the whole query. Intuitively, you would expect MySQL to execute the subquery once and use it as a list, but MySQL is not smart enough to distinguish between correlated and non-correlated subqueries.
As stated here:
"The problem is that, for a statement that uses an IN subquery, the
optimizer rewrites it as a correlated subquery."
How about this? It's just off the top of my head - I don't have access to a database to test on right now. (sorry)
SELECT
*
FROM
library lib
WHERE
lib.id NOT IN (
SELECT
book
FROM
collection coll
WHERE
coll.user =[id]
)
;
Related
Hi im trying to make a left join on this database.
Table category:
// `category` table has 3 columns
id,business_id (FK from business),category.
Table business:
// `business` table has 12 columns
id, name, etc (all info for that business)
What I need is to filter all the business that are Restaurants and join it to the respective business ID.
Problem is that each business have multiple category and when I do a select/join the result doesnt return DISTINCT businesses.
Here is one of the query i tried:
SELECT category.category,business.*
FROM category
INNER JOIN business
ON category.business_id = business.id;
I also tried left, right joins. But my pc is either taking forever or not working.
P.S the dataset is 8.6G
![database output]: https://i.imgur.com/eF4zYOr.png
Your query looks okay, but it is lacking that you are only looking for restaurants. However, firstly, I would make sure I have an index built on category.id
create index your_index_name on your_table_name(your_column_name);
Then you can simplify your query this way:
select a.id, a.category, b.* from category a left join business b on a.business_id=b.id WHERE a.category='Restaurants';
Even with that, a table that's 8.6G is going to take time. Since you said "pc" instead of "server" it might take a long time.
Try this
SELECT *
FROM Business b
WHERE EXISTS (
SELECT 1
FROM Category
WHERE category = 'Restaurants' AND business_id = b.id
)
Basically, I have a table which contains two fields: [id, other] which have user tokens stored in them. The goal of my query is to select a random user that has not been selected before. Once the user is selected it is stored in the table shown above. So if Jack selects Jim randomly, Jack cannot select Jim again, and on the flip side, Jim cannot select Jack.
Something like this is what comes to mind:
SELECT * FROM users
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?));
Well, first of all I've read that uses sub-queries like this is extremely inneficient, and I'm not even sure if I used the correct syntax, the problem is however, that I have numerous tables in my scenario which I need to filter by, so it would look more like this.
SELECT * FROM users u
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM other_table WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM diff_table WHERE (id=? AND value=?))
AND u.type = 'BASIC'
LIMIT = 1
I feel like there's a much, much more efficient way of handling this.
Please note: I don't want a row returned at all if the users id is present in any of the nested queries. Returning "null" is not sufficient. The reason I have the OR clause is because the user's id can be stored in either the id or the other field, so we need to check both.
I am using Postgre 9.5.3, but I added the MySQL tag as the code is mostly backwards comptable, Fancy Postgre only solutions are accepted(if any)
You can left join to another table, which produces nulls where no record is found:
Select u.* from users u
left selected s on s.id = u.id or s.other = u.other
where s.id is null
The or in a join is different, but should work. Example is kinda silly...but as long as you understand the logic. Left join first table to second table, where second table column is not null means there was atleast one record found that matched the join conditions. Where second table column is null means no record was found.
And you are right...avoid the where field = (select statement) logic when you can, poor performer there.
Use an outer join filtered on missed joins:
SELECT * FROM users u
LEFT JOIN selected s on u.id in (s.id, s.other) and ? in (s.id, s.other)
WHERE u.id != ?
AND s.id IN NULL
LIMIT 1
Here is the scenario. I have a MySQL table called modules which contains one or more entries each identified by a unique string - the module ID (mid).
There are a few other tables (scripts,images,sets...) which contain objects each of which"belong" to one of the modules - identified by the column 'mid' in each table.
Prior to allowing a user to drop a module entry, I need to check that the operation will not leave any orphaned objects in any of the other tables. Here is an example to make this clearer
Table modules
mname mid
Mod1 abcd1234
Mod2 wxyz9876
Table scripts
sname mid
A abcd1234
B wxyz9876
Table images
iname mid
A abcd1234
Table sets
sname mid
One or more of the tables may contain no, or no matching, entries.
I have written and tested a spot of SQL to handle this.
SELECT COUNT(*) FROM `images` WHERE mid = 'abcd1234'
UNION
SELECT COUNT(*) FROM `sets` WHERE mid = 'abcd1234'
UNION
SELECT COUNT(*) FROM `scripts` WHERE mid = 'abcd1234'
which very obligingly returns 1 implying that the module is "in use" and cannot be dropped. However, my SQL skills are pretty basic. I would much appreciate anyone who could tell me if this is a safe way to do things.
Not really a good way.
The UNION without ALL removes duplicate results. That would give you 1 if you had 3 rows returning 1. UNION ALL will make it return 3 rows with the count for each table, even when they are duplicate. After that you SUM them and you get the final count.
You should do:
SELECT SUM(cnt) FROM (
SELECT COUNT(*) as cnt FROM `images` WHERE mid = 'abcd1234'
UNION ALL
SELECT COUNT(*) FROM `sets` WHERE mid = 'abcd1234'
UNION ALL
SELECT COUNT(*) FROM `scripts` WHERE mid = 'abcd1234'
) a
You could build something around the following concept, given that there is a one-to-many relation between modules and the other tables.
select mid
,count(scripts.sname) as scripts
,count(images.iname) as images
,count(sets.sname) as sets
from modules
left join images using(mid)
left join sets using(mid)
left join scripts using(mid)
where mid = 'abcd1234'
group
by mid;
You could for example add the count(..) together, or including a HAVING clause.
I have two tables say: user and library. The library contains books sorted in a
certain way; a user can elect to sort his books in a certain way as well. The
structure (and sample data) for the two tables will look thus:
library
bookid position
10 1
12 2
14 3
16 4
user
userid bookid position
12669 12 1
12669 10 2
I want a query to return all the books for user, 12669, sorted by position i.e:
select bookid from user where userid = 12669 group by position
After it has return these sorted books, it should return the other bookids (not present in user) that are in the library. No bookid should be repeated. The result of these scenario will look thus:
12
10
14
16
In other words: All the books in library should be returned by this query but
with the books selected by user sorted according to user.position
I reckon I may need some kind of join statement for this. I tried:
select bookid from user u right join library l on u.bookid = l.bookid where u.userid = 12669 group by u.position
However, I get a syntax error for this. What is the best way to solve this 'problem'? Many thanks.
First of all, your posted query includes in its projection a column name which doesn't belong to either of the tables involved.
Secondly, you are using GROUP BY where sorting is carried out by ORDER BY.
Third point: As #Romil points out, the reference to the USER table in the WHERE clause overrides the outer join, and effectively enforces an inner join. So you need to select from an inline view on the USER table.
Finally, to get the sort order you want, you need to band all the USER.POSITIONs first. This version of your query uses IFNULL to assign an extremely high value to any rows which don't have a joined USER record. So it will sort by all the returned USER.POSITION values then by LIBRARY.POSITION for all the remaining books.
select l.bookid
from ( select * from user
where userid = 12669 ) u
right join library l
on (u.bookid = l.bookid)
order by ifnull(u.position, 999999999) , l.position
NB: if you are indexing the Library of Babel and so might have enough shelves to support more than 999999999 positions just bump up that subsituted value.
"This didn't work for me. It didn't return the bookids in the
arrangement I wanted. "
Frankly I find that surprising. Here is a SqlFiddle which definitely returns your sample data in the order you specify. So I repeat my earlier question: do your posted table descriptions match your actual tables exactly ?
This should do the trick :)
SELECT l.bookid
FROM library l
LEFT OUTER JOIN user u on u.bookid = l.bookid
WHERE u.userid = 12669
ORDER BY isnull(u.position, 99999), l.position
SELECT library.bookid bookid
FROM library
LEFT JOIN (SELECT *
FROM [user]
WHERE [USER].userid = 12669) users
ON [USERs].bookid = library.bookid
ORDER BY [USERs].userid DESC,
[USERs].position,
library.position
Thanks APC, Romil and aF. I finally figured out a solution that didn't use a join.
This worked for me:
(
SELECT bookid
FROM user
WHERE userid = 12669
ORDER BY position
)
UNION (
SELECT bookid
FROM library
)
The first query selects the ordered bookids from user then the union statement selects every other book from library and adds this to the result set. The arrangement of this later group wasn't important.
I have a database with a table for details of ponies, another for details of contacts (owners and breeders), and then several other small tables for parameters (colours, counties, area codes, etc.). To give me a list of existing pony profiles, with their various details given, i use the following query:
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
ORDER BY profiles.ProfileYearOfBirth ASC $limit
In the above sample, the 'profiles' table is my primary table (holding the Ponies info), 'contacts' is second in importance holding as it does the owner and breeder info. The lesser parameter tables can be identified by their prm_ prefix. The above query works fine, but i want to do more.
The first big issue is that I wish to GROUP the results by gender: Stallions, Mares, Geldings... I used << GROUP BY prm_breedgender.BreedGender >> or << GROUP BY ProfileBreedGenderID >> before my ORDER BY line, but than only returns two results from all my available profiles. I have read up on this, and apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause. How to do this however, gets me verrrrrrry confused. Step by step help here would be fantabulous.
As a further note on the above - You may have noticed the $limit var at the end of my query. This is for pagination, a feature I want to keep. I shouldn't think that's an issue however.
My secondary issue is more of an organisational one. You can see where I have pulled my Owner information from the contacts table here:
LEFT JOIN contacts
ON profiles.ProfileOwnerID = contacts.ContactID
I could add another stipulation:
AND profiles.ProfileBreederID = contacts.ContactID
with the intention of being able to list a pony's Owner and Breeder, where info on either is available. I'm not sure how to echo out this info though, as $row['ContactName'] could apply in either the capacity of owner OR breeder.
Is this a case of simply running two queries rather than one? Assigning a variable $foo to the first run of the query, then just run another separate query altogether and assign $bar to those results? Or is there a smarter way of doing it all in the one query (e.g. $row['ContactName']First-iteration, $row['ContactName']Second-iteration)? Advice here would be much appreciated.
And That's it! I've tried to be as clear as possible, and do really appreciate any help or advice at all you can give. Thanks in advance.
##########################################################################EDIT
My query currently stands as an amalgam of that provided by Cularis and Symcbean:
SELECT *
FROM (
profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
)
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
It works insofar as the results are being arranged as I had hoped: i.e. by age and gender. However, I cannot seem to get the alias' to work in relation to the contacts queries (breeder and owner). No error is displayed, and neither are any Owners or Breeders. Any further clarification on this would be hugely appreciated.
P.s. I dropped the alias given to the final LEFT JOIN by Symcbean's example, as I could not get the resulting ORDER BY statement to work for me - my own fault, I'm certain. Nonetheless, it works now although this may be what is causing the issue with the contacts query.
GROUP in SQL terms means using aggregate functions over a group of entries. I guess what you want is order by gender:
ORDER BY prm_breedgender.BreedGender ASC, profiles.ProfileYearOfBirth ASC $limit
This will output all Stallions, etc. next to each other.
To also get the breeders contact, you need to join with the contacts table again, using an alias:
LEFT JOIN contacts AS owners
ON profiles.ProfileOwnerID = owners.ContactID
LEFT JOIN contacts AS breeders
ON profiles.ProfileBreederID = breeders.ContactID
To further expand on what #cularis stated, group by is for aggregations down to the lowest level of "grouping" criteria. For example, and I'm not doing per your specific tables, but you'll see the impact. Say you want to show a page grouped by Breed. Then, a user picks a breed and they can see all entries of that breed.
PonyID ProfileGenderID Breeder
1 1 1
2 1 1
3 2 2
4 3 3
5 1 2
6 1 3
7 2 3
Assuming your Gender table is a lookup where ex:
BreedGenderID Description
1 Stallion
2 Mare
3 Geldings
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
select
BG.Description,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
group by
BG.Description
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description CountPerBreed
Geldings 1
Mare 2
Stallion 4
change the "order by" clause to "order by CountsPerBreed Desc" (for descending) and you would get
Description CountPerBreed
Stallion 4
Mare 2
Geldings 1
To expand, if you wanted the aggregations to be broken down per breeder... It is a best practice to group by all things that are NOT AGGREGATES (such as MIN(), MAX(), AVG(), COUNT(), SUM(), etc)
select
BG.Description,
BR.BreaderName,
count(*) as CountPerBreed
from
Profiles P
join prm_BreedGender BG
on p.ProfileGenderID = BG.BreedGenderID
join Breeders BR
on p.Breeder = BR.BreaderID
group by
BG.Description,
BR.BreaderName
order by
BG.Description
would result in something like (counts are only coincidentally sequential)
Description BreaderName CountPerBreed
Geldings Bill 1
Mare John 1
Mare Sally 1
Stallion George 2
Stallion Tom 1
Stallion Wayne 1
As you can see, the more granularity you provide to the group by, the aggregation per that level is smaller.
Your join conditions otherwise are obviously understood from what you've provided. Hopefully this sample clearly provides what the querying process will do. Your group by does not have to be the same as the final order... its just common to see so someone looking at the results is not trying to guess how the data was organized.
In your sample, you had an order by the birth year. When doing an aggregation, you will never have the specific birth year of a single pony to so order by... UNLESS.... You included the YEAR( ProfileYearOfBirth ) as BirthYear as a column, and included that WITH your group by... Such as having 100 ponies 1 yr old and 37 at 2 yrs old of a given breed.
It would have been helpful if you'd provided details of the table structure and approximate numbers of rows. Also using '*' for a SELECT is a messy practice - and will cause you problems later (see below).
What version of MySQL is this?
apparantly need to reorganise my query to accomodate GROUP within my primary SELECT clause
Not necessarily since v4 (? IIRC), you could just wrap your query in a consolidating select (but move the limit into the outer select:
SELECT ProfileGenderID, COUNT(*)
FROM (
[your query without the LIMIT]
) ilv
GROUP BY ProfileGenderID
LIMIT $limit;
(note you can't ORDER BY ilv.ProfileYearOfBirth since it is not a selected column / group by expression)
How many records/columns do you have in prm_breedgender? Is it just Stallions, Mares, Geldings...? Do you think this list is likely to change? Do you have ponies with multiple genders? I suspect that this domain would be better represented by an enum in the profiles table.
with the intention of being able to list a pony's Owner and Breeder,
Using the code you suggest, you'll only get returned instances where the owner and breeder are the same! You need to add a second instance of the contacts table with a different alias to get them all, e.g.
SELECT *
FROM (
SELECT *
FROM profiles
INNER JOIN prm_breedgender
ON profiles.ProfileGenderID = prm_breedgender.BreedGenderID
LEFT JOIN contacts ownerContact
ON profiles.ProfileOwnerID = ownerContact.ContactID
INNER JOIN prm_breedcolour
ON profiles.ProfileAdultColourID = prm_breedcolour.BreedColourID
) ilv LEFT JOIN contacts breederContact
ON ilv.ProfileBreederID = breederContact.ContactID
ORDER BY ilv.ProfileYearOfBirth ASC $limit