Why does my SQL SELECT query output only 1 result - sql

The select function would sometumes output 1 result. Does anyone know why?
SELECT * FROM people
WHERE id = (SELECT person_id FROM stars
WHERE movie_id = (SELECT id FROM movies
WHERE year = 2004))
ORDER BY birth;

You should avoid subqueries. You're best bet is to use something like the following code:
SELECT
ppl.* -- to get just people information
FROM
people ppl,
stars sta,
movies mov
WHERE
ppl.id = sta.person_id
AND sta.movie_id = mov.id
AND mov.YEAR = 2004
ORDER BY
ppl.birth;
If you want to have stars information or movie information you just need to add the desired fields on the return like mov.title (assuming you have a column named title on movies table :P)
EDIT:
As pointed out, I will leave an example using JOIN also.
SELECT
ppl.* -- to get just people information
FROM
people AS ppl
INNER JOIN
stars AS sta ON ppl.id = sta.person_id
INNER JOIN
movies AS mov ON sta.movie_id = mov.id
WHERE
mov.YEAR = 2004;

Related

SQL - ordering table by information from multiple tables

Title of the question may not have been very clear - I am not really sure how to name this question, but I hope that my explanation will make my problem clearer.
I have 3 tables:
[1] score
id
rating_type
1
UPVOTE
2
UPVOTE
3
DOWNVOTE
4
UPVOTE
5
DOWNVOTE
6
DOWNVOTE
[2] post_score
post_id
score_id
1
1
1
2
1
3
2
4
2
5
2
6
and [3] post
id
title
1
title1
2
title2
My goal is to order [3] post table by score.
Assume UPVOTE represents value of 1 and DOWNVOTE value of -1; In this example, post where id = 1 has 3 scores related to it, and the values of them are UPVOTE, UPVOTE, DOWNVOTE, making the "numeric score" of this post: 2;
likewise, post where id = 2, also has 3 scores, and those values are: UPVOTE, DOWNVOTE, DOWNVOTE, making the "numeric score": -1;
How would I order post table by this score? In this example, if I ordered by score asc, I would expect the following result:
id
title
2
title2
1
title1
My attempts didn't go far, I am stuck here with this query currently, which doesn't really do anything useful yet:
WITH fullScoreInformation AS (
SELECT * FROM score s
JOIN post_score ps ON s.id = ps.score_id),
upvotes AS (SELECT * FROM fullScoreInformation WHERE rating_type = 'UPVOTE'),
downvotes AS (SELECT * FROM fullScoreInformation WHERE rating_type = 'DOWNVOTE')
SELECT p.id, rating_type, title FROM post p JOIN fullScoreInformation fsi on p.id = fsi.post_id
I am using PostgreSQL. Queries will be used in my Spring Boot application (I normally use native queries).
Perhaps this data structure is bad and I should have constructed my entities differently ?
My goal is to order post table by score. Assume UPVOTE represents value of 1 and DOWNVOTE value of -1
One option uses a subquery to count the upvotes and downvotes of each post:
select p.*, s.*
from post p
cross join lateral (
select
count(*) filter(where s.rating_type = 'UPVOTE' ) as cnt_up,
count(*) filter(where s.rating_type = 'DOWNVOTE') as cnt_down
from post_score ps
inner join score s on s.id = ps.score_id
where ps.post_id = p.id
) s
order by s.cnt_up - s.cnt_down desc
Perhaps this data structure is bad and I should have constructed my entities differently ?
As it stands, I don't see the need for two distinct tables post_score and score. For the data you have showed, this is a 1-1 relationship, so just one table should be sufficient, storing the post id and the rating type.
You better use a LEFT join, otherwise you wouldn't get posts that have no votes yet. Then aggregate to get the fitered sum of the scores. Then add these sums, apply coalesce() to get 0 for posts without votes and order by the result.
SELECT p.id,
p.title
FROM post p
LEFT JOIN post_score ps
ON ps.post_id = p.id
LEFT JOIN score s
ON s.id = ps.score_id
GROUP BY p.id,
p.title
ORDER BY coalesce(sum(1) FILTER (WHERE rating_type = 'UPVOTE')
+
sum(-1) FILTER (WHERE rating_type = 'DOWNVOTE'),
0);
I second GMB's comment about the superfluous table.

PostgreSQL: How do I get data from table `A` filtered by a column in table `B`

I want to fetch all parents that have kids in a specific grade only in a school.
Below are trimmed down version of the tables.
TABLE students
id,
last_name,
grade_id,
school_id
TABLE parents_students
parent_id,
student_id
TABLE parents
id,
last_name,
school_id
I tried the below query but it doesn't really work as expected. It rather fetches all parents in a school disregarding the grade. Any help is appreciated. Thank you.
SELECT DISTINCT
p.id,
p.last_name,
p.school_id,
st.school_id,
st.grade_id,
FROM parents p
INNER JOIN students st ON st.school_id = p.school_id
WHERE st.grade_id = 118
AND st.school_id = 6
GROUP BY p.id,st.grade_id,st.school_id;
I would think:
select p.*
from parents p
where exists (select 1
from parents_students ps join
students s
on ps.student_id = s.id
where ps.parent_id = p.id and
s.grade_id = 118 and
s.school_id = 6
);
Your question says that you want information about the parents. If so, I don't see why you are including redundant information about the school and grade (it is redundant because the where clause specifies exactly what those values are).

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

SQL query for finding row with same column values that was created most recently

If I have three columns in my MySQL table people, say id, name, created where name is a string and created is a timestamp.. what's the appropriate query for a scenario where I have 10 rows and each row has a record with a name. The names could have a unique id, but a similar name none the less. So you can have three Bob's, two Mary's, one Jack and 4 Phil's.
There is also a hobbies table with the columns id, hobby, person_id.
Basically I want a query that will do the following:
Return all of the people with zero hobbies, but only check by the latest distinct person created, if that makes sense. Meaning if there is a Bob person that was created yesterday, and one created today.. I only want to know if the Bob created today has zero hobbies. The one from yesterday is no longer relevant.
select pp.id
from people pp, (select name, max(created) from people group by name) p
where pp.name = p.name
and pp.created = p.created
and id not in ( select person_id from hobbies )
SELECT latest_person.* FROM (
SELECT p1.* FROM people p1
WHERE NOT EXISTS (
SELECT * FROM people p2
WHERE p1.name = p2.name AND p1.created < p2.created
)
) AS latest_person
LEFT OUTER JOIN hobbies h ON h.person_id = latest_person.id
WHERE h.id IS NULL;
Try This:
Select *
From people p
Where timeStamp =
(Select Max(timestamp)
From people
Where name = p.Name
And not exists
(Select * From hobbies
Where person_id = p.id))