Filter data from two tables - mysql

Please help to make the request.
There are two tables, artist and album.
I want to select only those artists whose albums contains a picture.
That is, if the actor has 10 albums and only one of them has a picture or does not contain, i want to skip (only if all albums artist has pictures)
table artist:
artist_id
---------|
1 |
2 |
table albums:
artist_id | album_id | picture_id
---------------------------------
1 | 122... | true
1 | 123... | false
2 | 124... | true
2 | 125... | true
So, I want to select only artist where artist_id=2 (because all the albums have pictures);

there are a lot of solutions for this, the first that comes to my mind is a very simple sub-select to exclude the artists that have an album without an image:
SELECT
*
FROM
artist a
WHERE
artist_id NOT IN
(SELECT DISTINCT artist_id FROM album WHERE picture_id = false);
EDIT:
if you have stored artists without albums, these have to be excluded, too (take a look at the comments for explanation). in that case, you'll have to add something like:
AND
1 <= (SELECT COUNT(*) FROM album WHERE artist_id = a.artist_id)

You can use a GROUP BY clause to find the count of albums by artists. You can then compare this count with the made-up count. Aggregate functions can be used inside HAVING clause to eliminate groups:
SELECT artist_id
FROM albums
GROUP BY artist_id
HAVING COUNT(1) = COUNT(CASE WHEN picture_id THEN 1 ELSE NULL END)
Complete example:
SELECT artist.artist_id, artist.name
FROM artist
INNER JOIN albums ON artist.artist_id = albums.artist_id
GROUP BY artist.artist_id, artist.name
HAVING COUNT(album_id) = COUNT(CASE WHEN picture_id THEN 1 ELSE NULL END)

Another solution, without subselects, using the MIN() operator - if the minimum is TRUE, then all values are true:
SELECT artists.artist_id,MIN(picture_id)
FROM artists
INNER JOIN albums ON artists.artist_id = albums.artist_id
GROUP BY artists.artist_id
HAVING MIN(IFNULL(picture_id, 0)) > 0
EDIT
Modified the query to substitute the null values with 0 at the MIN() opertor, it's cleaner this way.

$sql = mysql_query("SELECT DISTINCT table1.id, table2.id, table2.name
FROM table1, table2 WHERE id=id GROUP BY name");
Try this. Sure it works.
Cheers !!!

Related

how to fetch songs based on multiple conditions from joined tables

I have two tables songs and song_clubs. The schema is below:-
songs schema
id available_for song_name status
1 all Song 1 1
2 selection Song 2 1
3 selection Song 3 1
song_clubs schema
song_id club_id
2 1
2 2
3 2
Now i want to fetch the songs of club id 1 and the song is available for all clubs.
My execpted output is like below:-
id available_for song_name
1 all Song 1
2 selection Song 2
I have tried below Query
select id,available_for,song_name from songs
JOIN
song_clubs
on song_clubs.song_id = songs.id
WHERE songs.status =1 and song_clubs.club_id=1 or songs.available_for ='all'
But its only returning one entry that is selection based.
You can do it with EXISTS:
SELECT s.id, s.available_for, s.song_name
FROM songs s
WHERE s.status =1 AND (
s.available_for = 'all'
OR EXISTS (SELECT 1 FROM song_clubs c WHERE c.club_id = 1 AND c.song_id = s.id))
or with the operator IN:
SELECT id, available_for, song_name
FROM songs
WHERE status =1 AND (
available_for = 'all'
OR id IN (SELECT song_id FROM song_clubs WHERE club_id = 1))
Two things.
Use parentheses to group WHERE clauses; otherwise they evaluate left-to-right.
Use LEFT JOIN to avoid losing items from your first table that don't match any items in your second table.
This should work (https://www.db-fiddle.com/f/6dAz91ejhe8AbGECFDihbu/0)
SELECT id,available_for,song_name
FROM songs
LEFT JOIN song_clubs ON songs.id = song_clubs.song_id
WHERE songs.status = 1
AND (song_clubs.club_id=1 or songs.available_for ='all')
ORDER BY id;
you can use this answer too
select unique id,available_for,song from songs,song_clubs
WHERE (song_clubs.song_id = songs.id and songs.status = 1 and song_clubs.club_id=1) or (songs.available_for ='all');
Here I use full join to select all the matches and then select the unique id values for the songs so you can get only the required 2 rows
Note: It is not the best performance query if you have huge tables.
and it is better to use EXISTS or LEFT JOIN.so other answers are more better for performance and this answer is just another way to do that.

How to write SELECT query from 2 different tables based on result from 3rd table, while keeping order?

I have 3 tables where one table has 3 columns with foreign keys to the other two tables.
table album_posters_albums-
+---------+---------+---------+
| album_id|poster_id|albums_id|
+---------+---------+---------+
| 49 | 167 | NULL |
| 49 | NULL | 45 |
+---------+---------+---------+
album_id and albums_id references the album table and poster_id represents the poster table.
I need to
SELECT * FROM poster
WHERE poster_id IN (
SELECT poster_id
FROM album_poster_albums
WHERE album_id=49);
IF the poster_id IS NULL:
SELECT * FROM album
WHERE album_id IN (
SELECT poster_id
FROM album_poster_albums
WHERE album_id=49).
The problem is I need to keep the posters and albums in the same order as they occur in the album_posters_albums table.
I was sending a query to get the list of ids, then looping through each result and querying the db to get either the poster or album but that is obviously very inefficient when I should be able to do it in one query.
It sounds like you want to use INNER JOINS
SELECT album.*, poster.*
FROM album_poster_albums
INNER JOIN album ON album_poster_albums.albums_id = album.album_id
INNER JOIN poster ON album_poster_albums.poster_id = poster.poster_id
WHERE album_poster_albums.album_id = 49
Based on your comment about one row with a poster and one row with an album, UNION ALL might be what you're looking for. (We'd need to see more details about the tables and a few more rows to understand the ordering part.) This should give you an album row then a poster row for each album id.
Caveats: The number and the orders of columns in the album and poster tables must be the same. Also, the data types of those columns must be the same or compatible. (I haven't used a UNION, or UNION ALL, in a very long time.)
SELECT * FROM (
SELECT album.*
FROM album_poster_albums
INNER JOIN album ON album_poster_albums.albums_id = album.album_id
WHERE album_poster_albums.album_id = 49
UNION ALL
SELECT poster.*
FROM album_poster_albums
INNER JOIN poster ON album_poster_albums.poster_id = poster.poster_id
WHERE album_poster_albums.album_id = 49
)
ORDER BY album_id
DECLARE #rowId INT(11);
SET #rowId :=0;
SELECT * FROM(SELECT #rowId:=#rowId+1,t.album_id,album.*
FROM album_poster_albums t
INNER JOIN album ON album.albums_id = t.albums_id
WHERE t.album_id = 49
UNION
SELECT #rowId:=#rowId + 2,t.album_id,poster.*
FROM album_poster_albums s
INNER JOIN poster ON poster.poster_id = t.poster_id
WHERE t.album_id = 49) T
ORDER BY #rowId,t.album_id
I decided to create a new table with an auto increment field based on #beltouche comment. My Mysql is pretty rusty and I thought there may be a way using case or if null. I didn't need the unique id previously with how I wrote the queries.
In hindsight the solution is obvious.
SELECT * FROM (SELECT albums.*, 1 AS type, t.id
FROM album_poster_album t
INNER JOIN albums ON albums.album_id = t.albums_id
WHERE t.album_id = 49
UNION ALL
SELECT poster.*, 2 AS type, s.id
FROM album_poster_album s
INNER JOIN poster ON poster.posterID = s.poster_id
WHERE s.album_id = 49) T
ORDER BY t.id

Optimize MySQL Subquery

I'm rather new to MySQL and am trying to simplify this statement:
SELECT DISTINCT p.user_id, a.artist_id, a.artist_name,
(SELECT COUNT(*) FROM plays WHERE user_id = p.user_id AND artist_id = a.artist_id) as count
FROM plays as p
LEFT OUTER JOIN artists AS a
ON p.artist_id = a.artist_id;
This accomplishes what I need but painfully slowly. There simply must be some way to do this in a more efficient manner. To give you an idea of the schema:
artists
artist_id artist_name
1 ArtistA
2 ArtistB
3 ArtistC
4 ArtistD
plays
user_id artist_id
1 1
1 2
1 2
2 4
2 4
3 3
And I'm trying to make a table like this:
plays per artist by user
user_id artist_id artist_name count
1 1 ArtistA 1
1 2 ArtistB 2
2 4 ArtistD 2
4 3 ArtistC 1
Granted, I'm working with several hundred thousands rows of data. I wasn't able to find anything on SO pertaining to this certain case but any resources/instruction would be hugely appreciated.
Thanks!
Yes, it is called a simple aggregation:
SELECT p.user_id, a.artist_id, a.artist_name, COUNT(*) as cnt
FROM artists a JOIN
plays p
ON p.artist_id = a.artist_id
GROUP BY p.user_id, a.artist_id, a.artist_name;
Because your aggregation has fields from both tables, it seems that you really want a match between the two tables. I changed the LEFT JOIN to an inner join.
Are there indexes on any of your tables? You'll probably want an index on artist_id on your plays table, if you don't already.
Also, I assume that artist_id on artists if a primary key, but if not, you'll want to do that too.
See https://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html for details.
It might help to provide the output of DESC SELECT DISTINCT p.user_id, a.artist_id, a.artist_name,
(SELECT COUNT(*) FROM plays WHERE user_id = p.user_id AND artist_id = a.artist_id) as count
FROM plays as p
LEFT OUTER JOIN artists AS a
ON p.artist_id = a.artist_id; to check whether your query is using indexes or not.
Having said that, you should switch to gordon-linoff#'s query too.

MySQL Select Where In Many to Many

I'm having trouble with a SQL query. My schema describes a many to many relationship between articles in the articles table and categories in the categories table - with the intermediate table article_category which has an id, article_id and category_id field.
I want to select all articles which only have the categories with id 1 and 2. Unfortunately this query will also select any articles which have those categories in addition to any others.
For example, this is a sample output from the SQL (with categories shown for descriptive purposes). You can see that while the query selected the article with id of 10, it also selected the article with an id of 11 despite having one extra category.
+-------+------------+
| id | categories |
+-------+------------+
| 10 | 1,2 |
| 11 | 1,2,3 |
+-------+------------+
This is the output that I want to achieve from selecting articles with only categories 1and 2.
+-------+------------+
| id | categories |
+-------+------------+
| 10 | 1,2 |
+-------+------------+
Likewise, this is the output what I want to achieve from selecting articles with only categories 1, 2 and 3.
+-------+------------+
| id | categories |
+-------+------------+
| 11 | 1,2,3 |
+-------+------------+
This is the SQL I have written. What am I missing to achieve the above?
SELECT articles.id
FROM articles
WHERE EXISTS (
SELECT 1
FROM article_category
WHERE articles.id = article_id AND category_id IN (1,2)
GROUP BY article_id
)
Many thanks!
Assuming you want more than just the article's id:
SELECT a.id
,a.other_stuff
FROM articles a
JOIN article_category ac
ON ac.article_id = a.id
GROUP BY a.id
HAVING GROUP_CONCAT(DISTINCT ac.category_id ORDER BY ac.category_id SEPARATOR ',') = '1,2'
If all you want is the article's id then try this:
SELECT article_id
FROM article_category
GROUP BY article_id
HAVING GROUP_CONCAT(DISTINCT category_id ORDER BY category_id SEPARATOR ',') = '1,2'
See it in action at http://sqlfiddle.com/#!2/9d213/4
Should also add that the advantage of this approach is that it can support the checking of any number of categories without having to change the query. Just make '1,2' a string variable and change what gets passed into the query. So, you could just as easily search for articles with categories 1, 2, and 7 by passing a string of '1,2,7'. No additional joins are needed.
You can left join category_id on category.id and then GROUP_CONCAT to get all categories, like you wrote in explanation (1st table) and than using HAVING match with any set you like ( '1,2' from example)
also with that approach you can easily make this query dynamic with php or any other language
SELECT articles.id
FROM articles
WHERE EXISTS (
SELECT GROUP_CONCAT(c.id) AS grp
FROM article_category
LEFT OUTER JOIN categories AS c ON c.id = article_category.category_id
WHERE articles.id = article_id
GROUP BY article_id
HAVING grp = '1,2'
)
Please Use Below Query You can do the Thing by Using Simple Query.
SELECT a.id, a.name
FROM articles a, categories c, articles_categories ac
WHERE
a.id = ac.article_id AND c.id = ac.category_id
AND c.id = 1 OR c.id = 2;
NOTE- If You have Many to many Relationship between two Tables Remove ID from the article_category table and make composite primary key Using article_id and category_id.
Thank you.
Maybe, something like:
select distinct article_id from article_cathegory
where category_id in (1,2)
minus
select distinct article_id from article_cathegory
where category_id not in (1,2)
Looks like simple solution for this could be the following:
SELECT
ac.article_id
, SUM(ac.category_id IN (1, 2)) AS nb_categories
, COUNT(ac.category_id) AS nb_all_categories
FROM
article_categories ac
GROUP BY
ac.article_id
HAVING
nb_categories=2 AND nb_all_categories=2
Here I count how many required categories we have and also count how many categories we have in total. We only need exactly 2 categories, so both required and total should be equal to 2.
This is quite flexible and for adding more categories just change categories list and numbers in HAVING statement.
SELECT articles.id
FROM articles
INNER JOIN articles_category ac ON articles.id = ac.article_id
WHERE articles.id IN (
SELECT ac1.article_id
FROM article_category ac1
WHERE ac1.category_id = 1;
)
AND ac.article_id = 2;
AND articles.id NOT IN (
SELECT ac2.article_id
FROM article_category ac2
WHERE ac2.category_id NOT IN (1, 2)
)
Far from the prettiest one I have written.
Basically, it limits first by ID that have a category id of 1, then it makes sure the records have a category of 2 as well, finally, it makes sure that it does not have any other categories
I like to approach these queries using group by and having. Here is an example:
select ac.article_id
from article_category ac
group by ac.article_id
having sum(case when category_id = 1 then 1 else 0 end) > 0 and
sum(case when category_id = 1 then 2 else 0 end) > 0;
Each condition in the having clause is testing for the presence of one of the categories.
I find that this approach is the most flexible for answering many different types of "set-within-sets" problems.
EDIT:
A slight variation on the above might be easier to generate:
having sum(category_id in (1, 2, 3)) = count(*) and
count(*) = 3
This will work assuming there are no duplicates in the data. You need to update the 3 to be the number of items in the in list.
to help just without changing your query very much, i think the logic has a bug. you dont want articles where a categegory 1,2 exists. You need articles where does not exist categories different than 1 and 2.
thanks & regards
In SQL Server I would do it with an INTERSECT and an EXCEPT. In MySQL, try this:
SELECT DISTINCT article_id
FROM article_category
WHERE category_id=1
AND article_id IN
(SELECT article_id
FROM article_category
WHERE category_id=2)
AND article_id NOT IN
(SELECT article_id
FROM article_category
WHERE category_id NOT IN (1,2))
Use this SQL query.
SELECT articles.id
FROM articles
WHERE articles.id in (
SELECT *
FROM article_category,articles
WHERE article_category.articles.id = articles.article_id AND article_category.category_id IN (1,2)
)

How to count the number of instances of each foreign-key ID in a table?

Here's my simple SQL question...
I have two tables:
Books
-------------------------------------------------------
| book_id | author | genre | price | publication_date |
-------------------------------------------------------
Orders
------------------------------------
| order_id | customer_id | book_id |
------------------------------------
I'd like to create a query that returns:
--------------------------------------------------------------------------
| book_id | author | genre | price | publication_date | number_of_orders |
--------------------------------------------------------------------------
In other words, return every column for ALL rows in the Books table, along with a calculated column named 'number_of_orders' that counts the number of times each book appears in the Orders table. (If a book does not occur in the orders table, the book should be listed in the result set, but "number_of_orders" should be zero.
So far, I've come up with this:
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(*) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
That's almost right, but not quite, because "number_of_orders" will be 1 even if a book is never listed in the Orders table. Moreover, given my lack of knowledge of SQL, I'm sure this query is very inefficient.
What's the right way to write this query? (For what it's worth, this needs to work on MySQL, so I can't use any other vendor-specific features).
Your query is almost right and it's the right way to do that (and the most efficient)
SELECT books.*, count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id
COUNT(*) could include NULL values in the count because it counts all the rows, while COUNT(orders.book_id) does not because it ignores NULL values in the given field.
SELECT b.book_id,
b.author,
b.genre,
b.price,
b.publication_date,
coalesce(oc.Count, 0) as number_of_orders
from books b
left join (
select book_id, count(*) as Count
from Order
group by book_id
) oc on (b.book_id = oc.book_id)
Change count(*) to count(orders.book_id)
You're counting the wrong thing. You want to count the non-null book_id's.
SELECT
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date,
count(orders.book_id) as number_of_orders
from books
left join orders
on (books.book_id = orders.book_id)
group by
books.book_id,
books.author,
books.genre,
books.price,
books.publication_date
select author.aname,count(book.author_id) as "number_of_books"
from author
left join book
on(author.author_id=book.author_id)
GROUP BY author.aname;