sort sql query by values in another table - sql

I am querying a table named artists, but I would like to sort the response based on a table named paintings (an artist has_many paintings - the painting table has an artist_id column).
To be more specific, I want to sort the artists by their most recent painting (paintings have a column named date_created). Does anyone know how this could be done?

Ideally this should be done using ANSI joins:
SELECT DISTINCT a.artist
FROM artists a
INNER JOIN paintings p
ON a.artistID = p.artistID
ORDER BY p.date_created desc

Perhaps something like this, depending on the specifics of your schema?
SELECT DISTINCT artists.* FROM
artists, paintings
WHERE artists.id = paintings.artist_id
ORDER BY paintings.painting_date DESC;
This will join the two tables on the artist id, and then order by their painting dates. DISTINCT ensures you only get one row per artist.

This will only return each artist once, with the latest date_created value for that artist.
SELECT artists.name, paintings.date_created
FROM artists JOIN (
SELECT artist_id, MAX(date_created) as date_created FROM paintings GROUP BY artist_id
) paintings ON artists.id = paintings.artist_id
ORDER BY paintings.date_created DESC

If I understand your requirement correctly:
1) Write an aggregation query that returns each artist and his/her latest painting;
2) Use it as a sub-query, joining it to the artists table;
3) SELECT columns from the join, ordering by the date of latest painting.

You can create a query:
select artistid, max(paintingdate)
from paintings
group by artistid
and then join to that as an inline-view:
select artistname from artist
inner join
(
select artistid, max(paintingdate) as latestdate
from paintings
group by artistid
) as Foo
on artist.artistid = Foo.artistid
order by latestdate desc

Related

Oracle sql query with "non-attribute" function. Ora-00935

I have simple database:
Paintings {
PAINTING_ID
PAINTING_NAME
AUTHOR
MUSEUM
}
Museums {
MUSEUM_ID
MUSEUM_NAME
}
Authors {
AUTHOR_ID
AUTHOR_NAME
}
AUTHOR and MUSEUM in paintings are foreign keys.
I have the task:
Display name for museum, that has the largest number of paintings of author id 6.
I tried some things:
SELECT MUSEUM.MUSEUM_NAME
FROM PAINTINGS
INNER JOIN AUTHORS
ON AUTHORS.AUTHOR_ID = PAINTINGS.AUTHOR
INNER JOIN MUSEUMS
ON MUSEUMS.MUSEUM_ID = PAINTINGS.MUSEUM
--WHERE AUTHORS.AUTHOR_ID = 6
GROUP BY MUSEUM_ID
HAVING MAX(COUNT(AUTHORS.AUTHOR_ID = 6)) // Ora-00935
or
SELECT COUNT()
FROM PAINTINGS
WHERE PAINTINGS.AUTHOR = 6
It looks like I must use aggregate function or sub-query function instead attribute, that actually impossible.
From Oracle 12, you can find the ID of the museum(s) with largest number of paintings using:
SELECT museum_id
FROM paintings
GROUP BY museum_id
ORDER BY COUNT(*) DESC
FETCH FIRST ROW WITH TIES
to restrict it to a single author you can add a WHERE filter:
SELECT museum_id
FROM paintings
WHERE author = 6
GROUP BY museum_id
ORDER BY COUNT(*) DESC
FETCH FIRST ROW WITH TIES
If you want the name of the museum then you can JOIN to the museum table (and either include museum_name in the GROUP BY clause or, because there is a correspondence from museum_id to museum_name, you can use an aggregation function to find the name):
SELECT MAX(museum_name) AS museum_name
FROM paintings p
INNER JOIN museums m
ON (p.museum_id = m.museum_id)
WHERE p.author = 6
GROUP BY p.museum_id
ORDER BY COUNT(*) DESC
FETCH FIRST ROW WITH TIES

SELECT AVG subquery with condition

I have the follwing tables:
teachers , teacher_rating and cities.
teachers has the following columns:
teacherID,location,name,othercities,status
teacher_rating has the following columns:
ratingId, teacherId,content,ratingNumber,created,status,userName
cities has the following columns:
id,name
I'm trying to sort all of the teachers (rows from teachers where status=0) by their average rating, this is my php variable with the SQL:
$q="SELECT
*,
AVG(pr.ratingNumber) AS rating_average
FROM teachers as p
LEFT JOIN teacher_rating pr
ON pr.teacherId = p.teacherID
WHERE p.location=(SELECT `name` FROM `cities` WHERE `id`=:location) AND p.status=0 OR p.othercities REGEXP CONCAT('[[:<:]](', :location,')( |)[[:>:]]') AND p.status=0
GROUP BY p.teacherID
ORDER BY rating_average DESC
";
It works fine, the only problem is that the rating average includes ratings from the teacher_rating table where status=1, I want it to calculate the average rating only using the values from teacher_rating where status=0.
I'm not sure how to approach this problem, thanks for the help!
How about adding pr.status=0 in the join?
ON pr.teacherId = p.teacherID AND pr.status=0
You are mixing AND and OR, so you should use parentheses. I think you intend:
WHERE p.status = 0 AND
pr.status = 0 AND
(p.location = (SELECT `name` FROM `cities` WHERE `id`=:location) OR
p.othercities REGEXP CONCAT('[[:<:]](', :location,')( |)[[:>:]]')
)

Is there a way to count and loop with plain SQL?

I have the following tables:
album:
albumID, albumTitle, albumReleaseDate, albumLabel, albumDigitalImg, albumCoverStory
albumTrack:
trackID, albumID, recordingID, albumTrackNumber, cd
italic = primary key
bold = foreign key
The question is as follows:
List the total number of tracks on each album that has any. Give the
column containing the total number of tracks a sensible name and use
albumId to identify each total.
My only idea would be to iterate over each albumID and check how many tracks are assigned to it but obviously loops aren't a thing in plain SQL? Is there any way to do this using plain SQL?
This only checks how many tracks are assigned for albumID 1, not for all albums and I'm really lost as to how I could do this without a loop.
SELECT COUNT(albumID)
FROM albumTrack
WHERE albumID = 1;
I'm using Oracle.
Below query will give you the number of tracks against each albumID,if any.
SELECT albumID,count(trackID) as NumberOfTracks
FROM albumTrack
Group By albumID;
You can try:
Select ab.albumID,
Count(Distinct trackID) As trackCount
From album ab
Inner Join albumTrack at
on at.albumID = ab.albumID
Group By ab.albumID;
This will give the count of tracks for each album.
The question asks for the counts per albumID.
For just that, a select on albumTrack alone with a GROUP BY on the albumID should be sufficient.
SELECT albumID, COUNT(*) AS TotalTracks
FROM albumTrack
GROUP BY albumID
ORDER BY albumID
But if albumTrack.albumID can be NULL, or isn't a foreign key on album.albumID?
Then an INNER JOIN on the "album" table should still be used.
To make sure that it only counts for albumID's that actually exist in the "album" table.
SELECT
tracks.albumID,
COUNT(*) AS TotalTracks
FROM albumTrack AS tracks
JOIN album ON album.albumID = tracks.albumID
GROUP BY tracks.albumID
ORDER BY tracks.albumID
And if you'd like to show the counts per albumTitle:
SELECT
a.albumTitle,
COUNT(t.trackID) AS TotalTracks
FROM album a
JOIN albumTrack t ON t.albumID = a.albumID
-- WHERE a.albumID = 1
GROUP BY a.albumID, a.albumTitle
ORDER BY a.albumTitle

Oracle sql - referencing tables

My school task was to get names from my movie database actors which play in movies with highest ratings
I made it this way and it works :
select name,surname
from actor
where ACTORID in(
select actorid
from actor_movie
where MOVIEID in (
select movieid
from movie
where RATINGID in (
select ratingid
from rating
where PERCENT_CSFD = (
select max(percent_csfd)
from rating
)
)
)
);
the output is :
Gary Oldman
Sigourney Weaver
...but I'd like to also add to this select mentioned movie and its rating. It accessible in inner selects but I don't know how to join it with outer select in which i can work just with rows found in Actor Table.
Thank you for your answers.
You just need to join the tables properly. Afterwards you can simply add the columns you´d like to select. The final select could be looking like this.
select ac.name, ac.surname, -- go on selecting from the different tables
from actor ac
inner join actor_movie amo
on amo.actorid = ac.actorid
inner join movie mo
on amo.movieid = mo.movieid
inner join rating ra
on ra.ratingid = mo.ratingid
where ra.PERCENT_CSFD =
(select max(percent_csfd)
from rating)
A way to get your result with a slightly different method could be something like:
select *
from
(
select name, surname, percent_csfd, row_number() over ( order by percent_csfd desc) as rank
from actor
inner join actor_movie
using (actorId)
inner join movie
using (movieId)
inner join rating
using(ratingId)
(
where rank = 1
This uses row_number to evaluate the "rank" of the movie(s) and then filter for the movie(s) with the highest rating.

Matching similar entities based on many to many relationship

I have two entities in my database that are connected with a many to many relationship. I was wondering what would be the best way to list which entities have the most similarities based on it?
I tried doing a count(*) with intersect, but the query takes too long to run on every entry in my database (there are about 20k records). When running the query I wrote, CPU usage jumps to 100% and the database has locking issues.
Here is some code showing what I've tried:
My tables look something along these lines:
/* 20k records */
create table Movie(
Id INT PRIMARY KEY,
Title varchar(255)
);
/* 200-300 records */
create table Tags(
Id INT PRIMARY KEY,
Desc varchar(255)
);
/* 200,000-300,000 records */
create table TagMovies(
Movie_Id INT,
Tag_Id INT,
PRIMARY KEY (Movie_Id, Tag_Id),
FOREIGN KEY (Movie_Id) REFERENCES Movie(Id),
FOREIGN KEY (Tag_Id) REFERENCES Tags(Id),
);
(This works, but it is terribly slow)
This is the query that I wrote to try and list them:
Usually I also filter with top 1 & add a where clause to get a specific set of related data.
SELECT
bk.Id,
rh.Id
FROM
Movies bk
CROSS APPLY (
SELECT TOP 15
b.Id,
/* Tags Score */
(
SELECT COUNT(*) FROM (
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = bk.Id
INTERSECT
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = b.Id
) Q1
)
as Amount
FROM
Movies b
WHERE
b.Id <> bk.Id
ORDER BY Amount DESC
) rh
Explanation:
Movies have tags and the user can get try to find movies similar to the one that they selected based on other movies that have similar tags.
Hmm ... just an idea, but maybe I didnt understand ...
This query should return best matched movies by tags for a given movie ID:
SELECT m.id, m.title, GROUP_CONCAT(DISTINCT t.Descr SEPARATOR ', ') as tags, count(*) as matches
FROM stack.Movie m
LEFT JOIN stack.TagMovies tm ON m.Id = tm.Movie_Id
LEFT JOIN stack.Tags t ON tm.Tag_Id = t.Id
WHERE m.id != 1
AND tm.Tag_Id IN (SELECT Tag_Id FROM stack.TagMovies tm WHERE tm.Movie_Id = 1)
GROUP BY m.id
ORDER BY matches DESC
LIMIT 15;
EDIT:
I just realized that it's for M$ SQL ... but maybe something similar can be done...
You should probably decide on a naming convention and stick with it. Are tables singular or plural nouns? I don't want to get into that debate, but pick one or the other.
Without access to your database I don't know how this will perform. It's just off the top of my head. You could also limit this by the M.id value to find the best matches for a single movie, which I think would improve performance by quite a bit.
Also, TOP x should let you get the x closest matches.
SELECT
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title,
COUNT(*) AS matched_tags
FROM
Movie M
INNER JOIN TagsMovie TM1 ON TM1.movie_id = M.movie_id
INNER JOIN TagsMovie TM2 ON
TM2.tag_id = TM1.tag_id AND
TM2.movie_id <> TM1.movie_id
INNER JOIN Movie SM ON SM.movie_id = TM2.movie_id
GROUP BY
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title
ORDER BY
COUNT(*) DESC