I have 2 tables:
Table1: users
id
name
faculty_id
level_id
1
john
1
1
2
mark
1
1
3
sam
1
2
Table 2: subjects
id
title
faculty_id
1
physics
1
2
chemistry
1
3
english
2
SQL query:
SELECT count(subjects.id) FROM users INNER JOIN subjects ON users.faculty_id = subjects.faculty_id WHERE users.level_id = 1
I'm trying to get count of subjects where users.level_id = 1, Which should be 2 in this case physics and chemistry.
But it's returning more than 2.
Why is that and how to get only 2?
I would recommend exists:
SELECT COUNT(*)
FROM subjects s
WHERE EXISTS (SELECT 1
FROM users u
WHERE u.faculty_id = s.faculty_id AND
u.level_id = 1
);
This counts subjects where a user exists with a level of 1.
You are joining users and subjects on faculty_id; this produces every combination of user and subject rows (2 users and 2 subjects makes 4 combined rows); change your query to SELECT users.*, subjects.* FROM... to see how this works.
count(subjects.id) counts the number of non-null subjects.id values in your results; you can just do count(distinct subjects.id).
The two tables are not directly related as none is parent to the other. The faculty table is parent to both tables and this is what relates the two tables indirectly.
When joining the faculties' students with the faculties' subjects per faculty, you get all combinations (john|physics, mark|physics, sam|physics, john|chemistry, mark||chemistry, ...). Whether John really has the subject Physics cannot even be gathered from the database. We see that John studies a faculty containing the subjects Physics and Chemistry, but does every student have every subject belonging to their faculty? You probably know but we don't. That shows that in order to write proper queries, one should know their database :-)
Now you are joining the tables and get all students per faculty multiplied with all subjects per faculty. You limit this to level_id = 1, which gets you 2 students x 2 subjects = 4. You could use COUNT(*) for this, because you are counting rows. By applying COUNT(subjects.id) instead you are only counting rows for which the subject ID is not null, but that is true for all rows, because all four combined rows have either subject ID 1 (Physics) or 2 (Chemistry). Counting something that cannot be null makes no sense, except for counting distinct, as has already been suggested. You can COUNT(DISTINCT subjects.id) to get the distinct number of subjects matching yur conditions.
This, however, has two drawbacks. First, the query doesn't clearly show your intention. Why do you join all students with all subjects, when your are not really interested in the (four) combinations? Secondly, you are building an unnecessary intermediate result (four rows in your small example) that must be searched for duplicates, so these can be removed from the counting. That means more memory consumed and more work for the DBMS.
What you want to count is subjects. So select from the subjects table. Your condition is that a student exists with level 1 for the same faculty. Conditions belong in the WHERE clause. Use EXISTS as Gordon suggests in his answer or use IN which is slightly shorter to write and may hence be considered a tad more readable (but that boils down to personal preference, as EXISTS and IN express exactly the same thing here).
select count(*)
from subjects
where faculty_id in (select faculty_id from users where level_id = 1);
You can just add "distinct" before subjects.id
your SQL query like:
SELECT count(distinct subjects.id) FROM users INNER JOIN subjects ON users.faculty_id = subjects.faculty_id WHERE users.level_id = 1
You want to count level_id and you have mentioned subject_id in the code. I would suggest first join two tables.
SELECT users.name, users.level_id,
subjects.title
FROM users
INNER JOIN subjects ON
users.faculty_id = subjects.faculty_id as new_table
After joining the table u can get the count.
SELECT level_id, COUNT(level_id)
FROM new_table
GROUP BY level_id
WHERE level_id = 1
(You have not mentioned group by in your code.)
Related
Table: user
id
compId
1
comp1
2
comp1
Table: Company
id
name
comp1
coke
comp2
pepsi
need a MYSQL query which should fetch company record only if it has one or more users, when passed a company id. I will have other where conditions on company table.
Can this be achieved by joins?
example 1: query(comp1) result: coke (atleast one user exists)
example 2: query(comp2) result: no records (Since no user exists who belong to company comp2)
What you're asking for is called a semi-join. This returns one row from company if there are one or more matching rows in user.
If you use a regular join:
SELECT c.* FROM company c JOIN user u ON u.compid = c.id;
This does return the row from company, but you might not like that it returns one row per user. I.e. rows in the result are multiplied by the number of matches.
There are several possible fixes for this, to reduce the results to one row per company.
SELECT DISTINCT c.* FROM company c JOIN user u ON u.compid = c.id;
SELECT c.* FROM company c JOIN (SELECT DISTINCT compid FROM user) u ON u.compid = c.id;
SELECT * FROM company c WHERE c.id IN (SELECT compid FROM user);
SELECT * FROM company c WHERE EXISTS (SELECT * FROM user WHERE compid = c.id);
Which one is best for your app depends on many factors, such as the sizes of the tables, the other conditions in the query, etc... I'll leave it to you to evaluate them given your specific needs.
I want to store data like the following, unique is on user_id and lids, in MySQL:
recordid user_id lids length breadth
------------------------------------------------------------
1 1 l1,l2 10 5
2 1 l1 7 5
3 1 l1,l3,l2 10 10
4 1 l2,l3 25 15
My query patterns are:
Give me length & breadth where lids are l2,l1
Give me length & breadth where lids are l2,l3
Basically, the input of lids can come in any order to search, still it should provide the correct length, breadth.
Since, we should not store the comma separated values in RDBMS.
Question - How should I structure the DB to have unqiue user_id/lids combinations which can provide the correct length & breadth without much string operations?
I came up with a solution to query the DB like this -
select * from table1 where find_in_set('l2', lids) AND find_in_set('l1', lids);
then in code, identify the count to be exact 2 of lids. But it is not the perfect solution. Need guidance regarding it.
AddOn - A SpringBoot + JPA (Hibernate) specific solution will be great, where there is no requirement of writing native sql query
As per comments if I create a table for lids -
recordid(fk) lid
----------------------------------
1 l1
1 l2
2 l1
3 l1
3 l3
3 l2
4 l2
4 l3
Then how will I ensure that just 1 unique combination of lids should be available for the user?
and what will be my select query? Will it be like the following?
select * from table1, lids where main.recordid = lids.recordid and lid IN ('l2','l3');
The IN operator will run a OR query instead of AND which will give wrong results as well.
Do I have to group based on the recordid in lids table then apply where condition? Apologies, I'm totally confused as I have read many articles related to it and got distracted.
Okay the question basically drills down to this - How to find if a list/set is exactly within another list
I want to find recordid having EXACT list of lids to search.
tl;dr: for design problems like this think about entities. relationships, amd sets.
You have two entities, records and lids. They have a many-to-many relationship.
Let's call your second table records_lids, to show that it's a many-to-many association table between records and lids. It has two columns, record_id and lid. When a row exists in that table it means that the record_id mentioned has the lid mentioned.
That table's primary key should be made of both its columns (record_id, lid). Because primary keys are unique, this prevents any record from having the same lid more than once.
Now, finding the set of record_id values with lid l1 is easy. You don't even need your first table.
SELECT record_id FROM records_lids WHERE lid = `l1`
To find records with multiple lids, you need to take the logical intersection of the sets of records with each lid. You can do that like this: (https://www.db-fiddle.com/f/cLf4b6LDwMH9eFRTTheZJr/0)
SELECT record_id
FROM (SELECT record_id FROM records_lids WHERE lid = 'l1') l1
NATURAL JOIN (SELECT record_id FROM records_lids WHERE lid = 'l2') l2
NATURAL JOIN (SELECT record_id FROM records_lids WHERE lid = 'l3') l3
The NATURAL JOIN operations handle the intersection operation; the result only includes rows with matching record_id values. (Some other makes of SQL table server have the INTERSECT operator, but not MySQL, yet...)
You can also do it this way (https://www.db-fiddle.com/f/cLf4b6LDwMH9eFRTTheZJr/1).
SELECT record_id
FROM records_lids
WHERE lid IN ('l1','l2','l3')
GROUP BY record_id
HAVING COUNT(*) = 3
The HAVING clause is how you insist you want records with all three lids.
Once you have the set of record_ids, you can join that to your other table. (https://www.db-fiddle.com/f/cLf4b6LDwMH9eFRTTheZJr/2)
SELECT records.*
FROM (SELECT record_id FROM records_lids WHERE lid = 'l1') l1
NATURAL JOIN (SELECT record_id FROM records_lids WHERE lid = 'l2') l2
NATURAL JOIN (SELECT record_id FROM records_lids WHERE lid = 'l3') l3
NATURAL JOIN records
or (https://www.db-fiddle.com/f/cLf4b6LDwMH9eFRTTheZJr/3)
SELECT *
FROM records
WHERE record_id IN (
SELECT record_id
FROM records_lids
WHERE lid IN ('l1','l2','l3')
GROUP BY record_id
HAVING COUNT(*) = 3
)
Edit: I did not completely understand your question. You want to exclude records without an *exactly( matching set of lids. Try this (https://www.db-fiddle.com/f/cLf4b6LDwMH9eFRTTheZJr/4). It depends on a quirk of MySQL, which is that Boolean expressions like lid IN ('l1', 'l2') have the value 0 when false and 1 when true.
SELECT *
FROM records
WHERE record_id IN (
SELECT record_id
FROM records_lids
GROUP BY record_id
HAVING SUM(lid IN ('l1', 'l2')) = 2
AND COUNT(*) = 2
)
SQL is, at its heart, a language for manipulating sets. The design technique here is
figure out your entities
work out the relationships between them
work out how to get the sets of entities you require
retrieve the rows you need matching the sets
I'm trying to calculate the amount of students from each course, but the amount JOINs required stumbles me a bit.
Tables: student, group and course
students quantity was taken from the group table
SELECT id, SUM(`students quantity`) AS students_all FROM course
JOIN student ON student.group = course.id
JOIN group ON group.`students quantity`=student.id
GROUP BY student.course
ORDER BY `students_all` DESC
What confuses me is this part: I know, I'm doing something wrong, but I can't figure out what exactly
SUM(`students quantity`) AS students_all FROM course
JOIN student ON student.group = course.id
JOIN group ON group.`students quantity`=student.id
The result I have is the single course.id displayed and students_all having all the students overall, instead of the all students from specific course.
What I need to recieve from the query
course.id: 1 2 3 4
students_all: 7 7 6 7
What I get
course.id: 1
students_all: 27
Since I have Russian inteface, I'll post samples this way, to avoid confusion
Group
Student (excluding all the irrelevant data)
As for the course table: the only relevant column there is ID which range is 1-4
It's very late reply of mine, but I solved it myself. No need to answer!
ive been searching for hours but cant find a solution. its a bit complicated so i'll break it down into a very simple example
i have two tables; people and cars
people:
name_id firstname
1 john
2 tony
3 peter
4 henry
cars:
name_id car_name
1 vw gulf
1 ferrari
2 mustang
4 toyota
as can be seen, they are linked by name_id, and john has 2 cars, tony has 1, peter has 0 and henry has 1.
i simply want to do a single mysql search for who has a (1 or more) car. so the anwser should be john, tony, henry.
the people table is the master table, and im using LEFT JOIN to add the cars. my problem arises from the duplicates. the fact that the table im joining has 2 entries for 1 id in the master.
im playing around with DISTINCT and GROUP BY but i cant seem to get it to work.
any help is much appreciated.
EDIT: adding the query:
$query = "
SELECT profiles.*, invoices.paid, COUNT(*) as num
FROM profiles
LEFT JOIN invoices ON (profiles.id=invoices.profileid)
WHERE (profiles.id LIKE '%$id%')
GROUP BY invoices.profileid
";
try this
select distinct p.name_id, firstname
from people p, cars c
where p.name_id = c.name_id
or use joins
select distinct p.name_id, firstname
from people p
inner join cars c
on p.name_id = c.name_id
If you only want to show people that have a car, then you should use a RIGHT JOIN. This will stop any results from the left table (people) to be returned if they didn't have a match in the cars table.
Group by the persons name to remove duplicates.
SELECT firstname
FROM people P
RIGHT JOIN cars C ON C.name_id = P.name_id
GROUP BY firstname
SELECT DISTINCT firstname
FROM people
JOIN cars ON cars.name_id = people.name_id;
If this doesn't work you might have to show us the full problem.
The way to propose it there's no need for a left join since you need at least a car per person. Left join is implicitely an OUTER join and is intended to return the results with 0 corresponding records in the joinned table.
I have a table called friends which has id and name and a self join table called friendship which stores the relationship which includes friend_id and friend2_id .
how do i get the names of related friends if a name of a particular frnd is given
example
id name
1 jack
2 kurt
3 jim
and
friendship
f_id f1_id
1 3
So if i give 'jack' i should get jim back
You could do this in one query or two queries, depending on what you want to accomplish.
A simple one could be:
SELECT
f_id,
f1_id
FROM
friendship
WHERE
f_id=1
OR
f1_id=1
And then you can get the specific friends with a statement like:
SELECT name FROM people WHERE id IN(2,3)
Alternative is a self join but the hard part here is that your id might be in both f_id and f1_id so that would need some UNION command or something like (untested):
SELECT
p1.name,
p2.name,
FROM
friendship
INNER JOIN
people AS p1
ON friendship.f_id = people.id
INNER JOIN
people AS p2
ON friendship.f1_id = people.id
WHERE
p1.id=1 OR p2.id=1
I would thoroughly check the speed of these options since they are quite heavy on huge amounts of records. If you measure you need more performance try some alternative. For example when you always put the smallest people.id in f_id and the bigger one in f1_id you might run 2 queries which you union. Alternative is to denormalize a small bit to cache the results if you need them frequently.
It would save you lots of joings for example if you would add the names into the friendship table:
SELECT
f_id,
f1_id,
f_name,
f1_name
FROM
friendship
WHERE
f_id=1
OR
f1_id=1