SQL search in relations - mysql

I have a main table with two relations.
Data structure and example:
A/Employee
id fields
1 Mike Miller
2 Lisa Miller
B/Skill
aid name
1 SQL
1 PHP
C/Language
aid name
1 German
I need a query which shows results from the main table and searches for a keyword in the relation tables.
Search for Miller -> Mike Miller, Lisa Miller
Search for SQL -> Mike Miller
Search for German -> Mike Miller
There are 10.000 rows in the main table and 100.000 relations.
I tried it with JOIN but the query is really slow.
Also the same row from the main table is displayed a view several times when there are more than one relations for this row:
Search for Miller
Returns: Mike Miller, Mike Miller
(Mike Miller displayed more than one time)
SELECT fields
FROM A
JOIN B ON id = B.aid JOIN C ON id = C.aid
WHERE fields LIKE '%"+$search+"%' OR B.name LIKE '%"+$search+"%' OR C.name LIKE '%"+$search+"%'"
I tried to fix the second problem with DISTINCT but now rows without relations are not displayed.
I want to display every row from the main table exactly one time. Which query do I need?

The problem with your first query, as you mention yourself, is that you get several duplicate rows returned. Not strange, since I guess the relation between table A and table B & C is one-to-many.
In your next attempt you added DISTINCT, and that will indeed get rid off the duplicates, but the regular join (or inner join) will only return matches where data can be joined, i.e. where data exists in both joined tables.
Introducing LEFT JOIN:
SELECT DISTINCT fields
FROM A
LEFT JOIN B ON id = B.aid
LEFT JOIN C ON id = C.aid
WHERE fields LIKE '%"+$search+"%' OR B.name LIKE '%"+$search+"%' OR C.name LIKE '%"+$search+"%'"
This will always search all data from table A, and those from table B & C where joins can be made. The DISTINCT will make sure that only unique rows are returned. You could also use GROUP BY for the same result, but that's usually used for aggregate methods.

Use LEFT JOIN.
Example:
SELECT distinct e.empname FROM Employee e
LEFT JOIN skill s ON s.aid = e.id
LEFT JOIN lang l ON l.aid = e.id
WHERE e.empname LIKE '%Miller%' OR s.name LIKE '%Miller%' OR l.name LIKE '%Miller%'
Proof SQL Fiddle

Related

MYSQL subquery with a join

I am trying to retrieve records from 4 tables and want to use a sub query within a left Join. So basically, i have
table a is the table for openings
o,id, o.namesdesc
table b is the table of all the projects
b.id, b.titledesc,b,status,b.edate
table a_b is the table for all the opening that have changed to projects acts like a junction table for one to many
o.id, b.id
table br is the relationship table with other projects
b.id b_rel_code (this is also a b.id as in related to which other projects)
I want to get all the openings,check whether they have project codes from the table a_b table get the projects codes relationship with other project codes check the status of the projects and get all the end_dates of the projects
I would really appreciate your help on this. I have been banging my head from ages. I think i am just not seeing it through
SELECT
o.id,
o.namesdesc,
GROUP_CONCAT(ab.pcode) as junctionpcode,
GROUP_CONCAT(br.scode) as proj_link_code,
GROUP_CONCAT(b.status)
FROM table_ab as ab
Left JOIN tablea as o on ab.oid = o.id
Left Join tableb as b on ab.pcode= b.id
LEFT Join tablebr as br on br.b_rel_code=b.id
where b.status='E'
GROUP BY o.id order by junctionpcode
Many Thanks,
Ok, Probably i have not explained well. May be sample data should help this
Tablea lists description of openings
id,namedesc
1,test desc1
2,test desc2
Tableb list of all the projects
pcode,pname,status,edate
001,test pr1,E,30-10-2017
002,test pr2,E,30-05-2017
003,test pr3,A,30-10-2018
Table br is the relationship table for the projects that are related to other projects
brid pcode rel_pcode
1 001 003
2 003 001
Table a_b which is the junction table between tablea and tableb
abid pcode id
1 001 1
2 002 2
3 003 3
So what i want to achieve is get all the openings(from tablea) that has project codes associated with it(from tablea_b) and get the status and enddates of the projects for only with status E(from table b). But it has needs to consider if that project code has the link to the project code(from table br) which has status and edate.
The output that i am expecting is
a.id,concat(pcode+relcodeany),concat(pstatus,relstatus),concat(pedate,relcodeedate)
1 ,(001,003),(E,A),(30-10-2017,30-10-2018)
I hope i have provided enough info.
Sorry for the long post
What was the problem with the query?
You did not add any sample data or expected result, but couple of notes from the query:
table tablebr is joined through table tableb (br.b_rel_code=b.id), if tableb does not contain any rows for ab.pcode nor you will get any rows from tablebr
The b.status='E' where clause turns the left join for tableb into ordinary join (only rows that match will be included). If you want the left join to be effective move the condition after the ON (on ab.pcode= b.id and b.status='E')
Please provide sample data and expected result.

why natural join give output as empty set in following situation?

In my info DB i have 4 tables, from which TWO are named as girls & boys and both have id column. boys contain 12 id(12 records) andgirls contain 8 id. when i apply this query:
SELECT * FROM
boys
NATURAL JOIN
girls
i get output as
Empty Set
How could this happen? (both id column is INT, NOT NULL, AUTO_INCREMENT).
NATURAL JOIN is shorthand for an INNER JOIN with equality predicates on columns of the same name.
For example, given two tables "m" and "f", which have columns named "id" and "name" in common, a NATURAL JOIN like this
SELECT ...
FROM m
NATURAL
JOIN f
Is equivalent to:
SELECT ...
FROM m
JOIN f
ON f.id = m.id
AND f.name = m.name
Only rows that "match" in the two tables will be returned in the result, all other rows are not returned.
For example, if we had table contents:
m: id name
-- ---------------
1 peter
2 paul
4 michael jackson
f: id name
-- ---------------
3 mary
4 michael jackson
The queries above would return only the rows from "m" and "f" that have id=4 (as a single row), because those are the only rows that "match".
Natural join will only work if the same named columns' values are same. ie if ID and Name both are same in both the tables.
If you need result like this :
Then you must name the 'name' col different in both the tables.
If you want a set of all boys and girls, you should use UNION.
select * from boys
union
select * from girls
Example :
Boys Table
Girls Table
Natural Join
Union Result
Natural Join The NATURAL [LEFT] JOIN of two tables is defined to be semantically equivalent to an INNER JOIN or a LEFT JOIN with a USING clause that names all columns that exist in both tables.
What this means is if you have any column name in common between the two tables, those are also being compared, and those rows are likely eliminated.

How to count rows in one table based on another table in mysql 2

I checked this answer but I have a problem.
SQL Fiddle (I changed INNER JOIN to LEFT JOIN)
What if there is a row in my departments (DDD for example) and no association in the courses table .
As you can see in the fiddle the count for DDD department is 1 like the BUS department has even if there is no data for DDD department. I know that LEFT JOIN does this but how can I modify this ?
How do I get count = 0 for the DDD department?
Try COUNT(c.section) so when section results null for no association then in count it evaluates to zero
SELECT d.abbreviation
, COUNT(c.section) num
FROM departments d
LEFT JOIN courses c ON c.section LIKE CONCAT(d.abbreviation, "%")
GROUP BY d.abbreviation
Demo

mysql left join duplicates

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.

JOINS in the query end up displaying too many rows

Below is my query:
$query = "
SELECT DISTINCT gr.SessionId, t.TeacherUsername, t.TeacherForename,
t.TeacherSurname, cm.ModuleId, m.ModuleName,
cm.CourseId, c.CourseName, st.Year, st.StudentUsername,
st.StudentForename, st.StudentSurname, gr.Mark, gr.Grade
FROM Teacher t
INNER JOIN Session s ON t.TeacherId = s.TeacherId
JOIN Grade_Report gr ON s.SessionId = gr.SessionId
JOIN Student st ON gr.StudentId = st.StudentId
JOIN Course c ON st.CourseId = c.CourseId
JOIN Course_Module cm ON c.CourseId = cm.CourseId
JOIN Module m ON cm.ModuleId = m.ModuleId
WHERE
('".mysql_real_escape_string($sessionid)."' = '' OR gr.SessionId = '".mysql_real_escape_string($sessionid)."')
ORDER BY $orderfield ASC
";
You don't need to worry about the WHERE clause and ORDER BY clause. My problem is that the query result shows 26 rows when it should show 13 rows.
I know the reason for this and it is because the Course_Module table is a cross reference table between Course table and Module table and is needed so that it is able to link Course table and Module table together.
But Course Table uses CourseId to JOIN another table and so does Course_Module Table. So CourseId is used twice in the JOINS section and because of this it is duplicating rows again. So there should be 13 rows but because each row is duplicate it shows 26.
I tried GROUP BY cm.CourseId but it ends up displaying 2 rows which are two different CourseId which is not what I want at all.
So what my question is that is there are way I can use the Course_Module table to JOIN tables but ignore it when it comes to displaying results?
If query was this:
$query = "
SELECT DISTINCT gr.SessionId, t.TeacherUsername, t.TeacherForename,
t.TeacherSurname, cm.ModuleId, m.ModuleName,
cm.CourseId, c.CourseName, st.Year, st.StudentUsername,
st.StudentForename, st.StudentSurname, gr.Mark, gr.Grade
FROM Teacher t
INNER JOIN Session s ON t.TeacherId = s.TeacherId
JOIN Grade_Report gr ON s.SessionId = gr.SessionId
JOIN Student st ON gr.StudentId = st.StudentId
JOIN Course c ON st.CourseId = c.CourseId;
This query shows 13 rows but it means there is no link to Module Table so don't know name of Modules taken for each grade reort.
Below is example of result I am getting at moment:
Student Session Module Course Grade
S1 AAA CHT2520 ICT A
S1 AAA CHT2520 ICT A
S2 AAA CHT2520 ICT B
S2 AAA CHT2520 ICT B
S3 AAB CHT2220 BIT D
S3 AAB CHT2220 BIT D
S4 AAC CHI2250 COMP A
S4 AAC CHI2250 COMP A
It should be:
Below is result I am getting at moment:
Student Session Module Course Grade
S1 AAA CHT2520 ICT A
S2 AAA CHT2520 ICT B
S3 AAB CHT2220 BIT D
S4 AAC CHI2250 COMP A
Thank You
Your select clause asks for 14 columns. The results you showed only had 5. If you limit your select clause to those 5 columns, you'll get the 13 rows that you want.
To include all 14 columns, look at the other columns in the results. Realize that right now, you don't have 26 rows in your result set so much as you have 13 pairs of rows. Look carefully at each pair, and somewhere you'll find a column that's different — something that distinguishes one record in a matched pair from the other. Add a condition to the join on the table that hosts this column to prevent one of the values from making it to your results, and you'll get the right number of rows. This may require a derived table or correlated sub-query in the join condition to limit the join to only the first match (for some definition of "first" determined by the sub query).
I am not a big expert but why are you using two different joins in your case? Stick to INNER JOIN throughout the query and it might fix the issue.