Mysql query for selecting friends - mysql

I am trying to solve this "issue", however still without success. What I'd like to achieve is, create a query that will select all friends of specific actor. Let's say I want to get list of First name, Last name and age of Jason Statham's friends.
Below is an image of tables.
PS: Are those tables correctly organized ? (especially those foreign keys)
Thanks in advance

Does this do what you're looking for?
SELECT actors.first_name,
actors.last_name
FROM actors
WHERE actors.login IN
(
SELECT friendslist.loginf
FROM friendslist
WHERE friendslist.logina = 'xstad'
)

You'll need to include the Actors table twice - once for the focus person (Jason Statham) and once for his friends.
SELECT CONCAT(A.first_name," ",A.last_name) AS Actor, CONCAT(B.first_name," ",B.last_name) AS Friend
FROM Actors AS A
JOIN [Friends List] AS F on A.login=F.loginA
JOIN Actors AS B on F.loginB=B.login
ORDER BY A.last_name, B.last_name

If Friends_List is rows where "[loginA] and [loginF] are friends" then rows should appear in pairs, < a1,a2> and < a2,a1>. I'll assume it means "[loginA] considers [loginF] a friend" and that in your query "all friends of an actor" means "all actors that an actor considers a friend".
You can do this with natural join. It joins on common columns and returns only one column with that name. If you want two differently named columns joined to each other then you have to rename one. Unfortunately in SQL you can't do that by just mentioning the one column, you must mention them all.
// first_name, last_name of rows satisfying [login] has name [first_name] [last_name] AND 'xstad' considers [login] a friend
SELECT first_name,last_name
FROM Actors
NATURAL JOIN
(SELECT loginA, loginF AS login
FROM
(SELECT * FROM Friends_List WHERE loginA='xstad')
)
(You could collapse the nested selects but I am illustrating the structure.)
You could also use NATURAL JOIN without renaming but explicity equating columns:
// <firstname,last_name> of rows satisfying [login] has name [first_name] [last_name] AND [login] considers [login] a friend AND loginA='xstad' AND login=loginF
SELECT first_name,last_name
FROM Actors
NATURAL JOIN Friends_List
WHERE loginA='xstad' AND login=loginF
EDIT
A certain design & programming style not familiar to or understood by most SQL programmers is supported by natural join. This approach lets relational algebra operators parallel logic operators: result rows satisfy the statement that is the AND of the statements that argument rows satisfy; UNION the OR; EXCEPT the AND NOT; PROJECT on all but some columns {C,...} the EXISTS C,..; etc.) This is simpler than having to deal with the dotted duplicate columns from SQL INNER JOIN. Unfortunately SQL does not give all the relevant support (eg rename columns, project out columns, no SQL pseudo-3VL, optimizations).
EDIT corrected 1st query

Related

SQL: Column Must Appear in the GROUP BY Clause Or Be Used in an Aggregate Function

I'm doing what I would have expected to be a fairly straightforward query on a modified version of the imdb database:
select primary_name, release_year, max(rating)
from titles natural join primary_names natural join title_ratings
group by year
having title_category = 'film' and year > 1989;
However, I'm immediately running into
"column must appear in the GROUP BY clause or be used in an aggregate function."
I've tried researching this but have gotten confusing information; some examples I've found for this problem look structurally identical to mine, where others state that you must group every single selected parameter, which defeats the whole purpose of a group as I'm only wanting to select the maximum entry per year.
What am I doing wrong with this query?
Expected result: table with 3 columns which displays the highest-rated movie of each year.
If you want the maximum entry per year, then you should do something like this:
select r.*
from ratings r
where r.rating = (select max(r2.rating) where r2.year = r.year) and
r.year > 1989;
In other words, group by is the wrong approach to writing this query.
I would also strongly encourage you to forget that natural join exists at all. It is an abomination. It uses the names of common columns for joins. It does not even use properly declared foreign key relationships. In addition, you cannot see what columns are used for the join.
While I am it, another piece of advice: qualify all column names in queries that have more than one table reference. That is, include the table alias in the column name.
If you want to display all the columns you can user window function like :
select primary_name, year, max(rating) Over (Partition by year) as rating
from titles natural
join primary_names natural join ratings
where title_type = 'film' and year > 1989;

Using the WHERE clause in conjunction with NATURAL JOIN SQL?

I am reading a book on SQL and I am stuck on an example which is related to a database schema as shown below in the image.
The example below solves the query as stated in the book :
Suppose we wish to answer the query “List the names of instructors
along with the titles of courses that they teach.” The query can be written in
SQL as follows:
select name , title
from instructor natural join teaches , course
where teaches.course id = course.course id;
Now the book states that
" Note that teaches.course id
in the where clause refers to the course id field of the natural join result, since this field in turn came from the teaches relation. "
Again the book states in BOLD that :
"It is not possible to use attribute names containing the original relation names, for instance instructor.name or teaches.course id, to refer to attributes in the natural join result; we can, however, use attribute names such as
name and course id, without the relation names."
(Refering to the query above)If it is not possible then how come the author was able to write the query as
teaches.course id = course.course id
How can teaches.course refer to the natural join attribute "Course" , the author has so ambiguously put forth his arguments.Please explain me the author's point of view.
Ignore what the book has to say about NATURAL JOIN. Just avoid it. NATURAL JOIN is a bug waiting to happen. Why? The join keys are defined simply by naming conventions on columns in the tables -- any columns that happen to have the same names are used. In fact, NATURAL JOIN ignores properly defined FOREIGN KEY relationships; and they hide the keys actually used for matching.
So, be explicit and use the ON or USING clauses instead. These are explicit about the keys being used and the code is more understandable and maintainable.
Then, follow a simple rule: Never use commas in the FROM clause; always use explicit JOIN syntax.
So, a good way to write your query would be something like this:
select i.name, c.title
from instructor i inner join
teaches t
on t.instructor_id = i.instructor_id inner join
course c
on t.course_id = i.course_id;
Note that there is no where clause and all the columns are qualified, meaning that they specify the table they are coming from.
Also, I don't see an instructor_id column in the teaches table, so this is just an example of what reasonable code would look like.

Select from a table the ones that don't have a relationship with another table

The specific problem is listing the names of the teachers that never graded.
I have 'teachers' table with the columns 'Name' and 'ID'.
And 'grades' table with the column 'IDTeacher' and 'Grade'.
Don't get why this doesn't work:
Select Name from teachers where not exists(Select * from grades, teachers)
You can just join it with the grades table and use the ones where the join returns "null" for the right side:
SELECT
name
from
teachers t
LEFT JOIN
grades g
on
t.teacher = g.teacher
WHERE
ISNULL(g.teacher)
edit: Thought about a right join instead, but no, the right join might not work, if the teacher has no entry in the grades table. (Then you would miss him completely, even if he is in the teacher table)
You could also use WHERE IN for this:
SELECT
name
FROM
teachers
WHERE
name
NOT IN (SELECT name from grades)
BUT the MySQL Optimizer will rewrite this to exactly the correlated subquery #Gordon Linoff has written. Using WHERE NOT IN is just easier to read imho.
Your query does work, it just doesn't do what you think it should. The subquery creates a cartesian product between the two tables. If both tables have rows, then the cartesian product has rows and the where clause will always be true.
You can take this approach, but you need a correlated subquery:
Select Name
from teachers t
where not exists (Select 1 from grades g where g.idteacherid = t.id);
Note that this query only has one table in the subquery.
There are other ways to write this query, but this seems to be the approach you are heading in. And, not exists is a very reasonable approach.

Using two SELECT statements in SQL?

I have two tables, one is 'points' which contains ID and points. The other table is 'name' and contains ID, Forename, and Surname.
I'm trying to search for the total number of points someone with the forename Anne, and surname Brown, scored.
Would I have to do a join? If so, is this correct?
SELECT Name.Forename, Name.Surname
FROM Name
FULL OUTER JOIN Points
ON Name.ID=Points.ID
ORDER BY Name.Forename;
But then I also have to add the points, so would I have to use:
SELECT SUM (`points`) FROM Points
Then there is also the WHERE statement so that it only searches for the person with this name:
WHERE `Forename`="Anne" OR `Surname`="Brown";
So how does this all come together (based on the assumption that you do something like this)?
SELECT Name.ID, Forename, Surname, SUM(Points)
FROM Name
INNER JOIN Points ON Name.ID = Points.ID
/* Optional WHERE clause:
WHERE Name.ForeName = 'Anne' AND Name.Surname='Brown'
*/
GROUP BY Name.ID, Name.Forename, Name.Surname
So, first, your answer:
select sum(points) as Points
from
Points
inner join Name on Name.ID = Points.ID
where
Name.Forename ='Anne' and Name.SurName='Brown'
Secondly, FULL JOINS are bad since they pull all values from both sets even those without matches. If you want to only return values that match your criteria (A & B) you must use an INNER JOIN.
Thirdly, here is the MySQL reference documentation on SQL statement syntax. Please consider reading up on it and familiarizing yourself at least with the basics like JOINs, aggregation (including GROUP BY and HAVING), WHERE clauses, UNIONs, some of the basic functions provided, and perhaps subqueries. Having a good base in those will get you 99% of the way through most MySQL queries.
You can write it like this with a subquery.
SELECT Name.Forename, Name.Surname, Name.ID,
(SELECT SUM (`points`) FROM Points where Points.ID = Name.ID) as total_points
FROM Name ORDER BY Name.Forename;
However, I would like to point out, that it appears that your linking of the tables is incorrect. I can not be completely sure without seeing the tables, but I imagine it should be where points.userid = name.id

MySQL -- joining then joining then joining again

MySQL setup: step by step.
programs -> linked to --> speakers (by program_id)
At this point, it's easy for me to query all the data:
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
Nice and easy.
The trick for me is this. My speakers table is also linked to a third table, "books." So in the "speakers" table, I have "book_id" and in the "books" table, the book_id is linked to a name.
I've tried this (including a WHERE you'll notice):
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
LIMIT 5
No results.
My questions:
What am I doing wrong?
What's the most efficient way to make this query?
Basically, I want to get back all the programs data and the books data, but instead of the book_id, I need it to come back as the book name (from the 3rd table).
Thanks in advance for your help.
UPDATE:
(rather than opening a brand new question)
The left join worked for me. However, I have a new problem. Multiple books can be assigned to a single speaker.
Using the left join, returns two rows!! What do I need to add to return only a single row, but separate the two books.
is there any chance that the books table doesn't have any matching columns for speakers.book_id?
Try using a left join which will still return the program/speaker combinations, even if there are no matches in books.
SELECT *
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
LEFT JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
LIMIT 5
Btw, could you post the table schemas for all tables involved, and exactly what output (or reasonable representation) you'd expect to get?
Edit: Response to op author comment
you can use group by and group_concat to put all the books on one row.
e.g.
SELECT speakers.speaker_id,
speakers.speaker_name,
programs.program_id,
programs.program_name,
group_concat(books.book_name)
FROM programs
JOIN speakers on programs.program_id = speakers.program_id
LEFT JOIN books on speakers.book_id = books.book_id
WHERE programs.category_id = 1
GROUP BY speakers.id
LIMIT 5
Note: since I don't know the exact column names, these may be off
That's typically efficient. There is some kind of assumption you are making that isn't true. Do your speakers have books assigned? If they don't that last JOIN should be a LEFT JOIN.
This kind of query is typically pretty efficient, since you almost certainly have primary keys as indexes. The main issue would be whether your indexes are covering (which is more likely to occur if you don't use SELECT *, but instead select only the columns you need).