MySQL multi-table query - mysql

Greetings! I'm having difficulties creating a query that references multiple tables. I'm new to SQL and joins are stumping me. I've mucked about Google and after searching around I discovered Joining tables in SQL which got me closer to where I want to be! The query I've created isn't doing what I want. I have two tables:
disp_profile
disp_id* name address zip
0001 Profile1 SomeAddress1 11111
0002 Profile2 SomeAddress2 22222
0003 Profile3 SomeAddress3 33333
zipcode
zip_code* state city county
11111 CA City1 County1
22222 WA City2 County2
33333 NV City3 County3
What I am attempting to do is grab the City, State, County from zipcode when the zipcode.zip = disp_profile.zip (filtered by a variable State). Ideally returning something like:
dispId dispName dispAddress dispZip zipState zipCounty zipCity zipCode
001 Profile1 SomeAddress1 11111 CA County1 City1 11111
or
002 Profile2 SomeAddress2 22222 WA County2 City2 22222
SELECT
dp.disp_id AS dispId, dp.name AS dispName, dp.address1 AS dispAddress1, dp.zip AS dispZip, zc.state AS zipState, zc.county AS zipCounty, zc.city AS zipCity, zc.zip_code AS zipCode
FROM
disp_profile dp
INNER JOIN
zipcodes zc
ON
dp.zip = zc.zip_code
WHERE
dp.state = 'CA'
I'm aware this may not be the best way to go about this but I thought it better to store things separately vs redundant information for each item. The dataset in disp_profile is about 1000 records and zip_codes is about 30,000. Would this be done easier with a subquery? Thanks for your time!

No, you are doing it fine. This is what joins where made for, no need to use a subquery here. You could, but it would not perform better (the MySQL Query Optimizer might even translate it to a join internally).
If you worry about speed: Put the word "EXPLAIN" in front of your SELECT statement to see what MySQL is doing:
EXPLAIN SELECT
dp.disp_id AS dispId, dp.name AS dispName, dp.address1 AS dispAddress1, dp.zip AS dispZip, zc.state AS zipState, zc.county AS zipCounty, zc.city AS zipCity, zc.zip_code AS zipCode
FROM
disp_profile dp
INNER JOIN
zipcodes zc
ON
dp.zip = zc.zip_code
WHERE
dp.state = 'CA'
It will tell you how your statement is being executed. Paste the output here if you want us to help interpreting :)

That looks okay to me, at least at first glance. I prefer the old school way of:
select dp.disp_id AS dispId,
dp.name AS dispName,
dp.address1 AS dispAddress1,
zc.state AS zipState,
zc.county AS zipCounty,
zc.city AS zipCity,
zc.zip_code AS zipCode
from disp_profile dp,
zipcodes zc
where dp.state = 'CA'
and dp.zip = zc.zip_code
but that's just a matter of style (and using a DBMS with a very intelligent optimiser - whether MySQL is a match for my DBMS of choice, I couldn't comment (but I doubt it)).
The one change I have made (and which you probably should) is to only get one of the ZIP codes. It's redundant getting the field from both dp and zc since they're identical due to your join.

Related

All records not showing when using a WHERE statement with CONCAT

I have a list of plant, which can be filtered with a CONCAT, originally it was just text, but I have converted it to ID's instead. It was showing all records and could be filtered before I converted to ID's.
This involves 4 tables. (with example data) "" are not used in the fields, they are just to show you that it is a word.
plant
idplant example 1
plantname example "001 Forklift"
idplanttype1 example 1
idlocation1 example 1
iddepartment1 example 1
planttypes
idplanttype example 1
planttype example "Forklift Truck"
locations
idlocation example 1
location example "Preston"
departments
iddepartment example 1
department example "Waste Disposal"
Without the WHERE statement, it shows all records, including nulls. (but the filter doesn't work)
But With the WHERE statement, it is only showing complete records (all of which have no Null fields and the filter works) records with nulls do not show
The issue seems to be the CONCAT. (i've cleaned up the parentheses, but had to add a 1 to make the id's different)
if(isset($_POST['search'])) {$valueToSearch = $_POST['valueToSearch'];}
$sql = "
SELECT idplant, plantname, planttype, location, department
FROM plant
LEFT JOIN planttypes ON idplanttype1 = idplanttype
LEFT JOIN locations ON idlocation1 = idlocation
LEFT JOIN departments ON iddepartment1 = iddepartment
WHERE CONCAT(plantname, planttype, location, department) LIKE
'%".$valueToSearch."%'
ORDER BY plantname";
SOLUTION
The above code works, it was just missing.
WHERE CONCAT_WS
I'm new to Joins, so any help would be greatly appreciated.
Edit: Using Linux Server - Apache Version 2.4.46
Thanks in advance!
Your problem is probably blanks.
WHERE CONCAT(plantname, planttype, location, department)
LIKE '%001 Forklift Forklift Truck Preston Waste Disposal%'
won't find anything for example, as the concated strings result in '001 ForkliftForklift TruckPrestonWaste Disposal', not '001 Forklift Forklift Truck Preston Waste Disposal'.
You want blanks between the substrings, which is easiest to achieve with CONCAT_WS:
SELECT p.idplant, p.plantname, pt.planttype, l.location, d.department
FROM plant p
INNER JOIN planttypes pt ON pt.idplanttype = p.idplanttype1
INNER JOIN locations l ON l.idlocation = p.idlocation1
INNER JOIN departments d ON d.iddepartment = p.iddepartment1
WHERE CONCAT_WS(' ', p.plantname, pt.planttype, l.location, d.department)
LIKE '%001 Forklift Forklift Truck Preston Waste Disposal%'

SQL: return lector, whose ALL students passed with Excellent mark

Following table: student name, student's lector name(one lector can have some students), student mark after course.
Stud Lector Mark
-----------------
Joe Mr.A 5
Steve Mr.A 4
Bob Mr.B 5
Jim Mr.D 5
Kai Mr.C 4
Mo Mr.A 3
Hue Mr.B 3
Mia Mr.D 5
What query will return lector(s), whose ALL students passed course excellently (got 5 mark). Just in our case - Mr.D should be returned as query result.
I think the easiest approach is via aggregation on the lector:
SELECT Lector
FROM yourTable
GROUP BY Lector
HAVING SUM(CASE WHEN Mark < 5 THEN 1 ELSE 0 END) = 0;
The nice thing about this approach is that it reads closely according to the actual logic you want to implement. That is, we simply check the marks for each lector and make sure that no sub-5 marks occurred.
Demo
You have to join the table to itself; once to get the excellent marks and again to get the potentially less than excellent marks. There are a few ways to achieve this, but the best IMHO is this way:
select distinct a.lector
from mytable a
left join mytable b on a.lector = b.lector
and b.mark != 5
where a.mark = 5
and b.mark is null
The trick here is using an outer join from excellent marks to worse marks and using b.mark is null to ensure there are in fact no worse marks.

Show multiple unmatched records in SQL

I have two tables. These two tables may have ID's that do not match. However, also they may have names or addresses that do not match as well. I need to be able to filter out not only ID's but first_name, last_name and street_1 from my list. I can do a JOIN on match ID's but sometimes they match but the other columns may have records that do not match which I would need to show.
Find ID's that do not match. If they do match see if any of the other fields do not match.
Here are my expect results:
id first_name_2 last_name_2 street_1 street_2
3 Teresa White 834 Green Ridge Hill 43 Arapahoe Park
6 Rebecca George 39157 Nelson Hill 7467 Acker Center
7 Ann Hawkins 341 Tennessee Street 8 Bunting Street
8 Joyce Moreno 0277 Bunker Hill Drive 6 Nancy Center
9 Kimberly Alvarez 57332 Di Loreto Lane 0437 Waubesa Avenue
ID 3 & 6 is in the list because the Last Name does not match. ID 7 is last name and street_1. ID 8 & 9 ID's do not match.
Here is my sample data for reference: http://sqlfiddle.com#!9/928568/2
I would do the following: Left joining and treating nulls as blank strings. If you have a legitimate empty string, street_2 for example, it may return false positives:
SELECT *
FROM information I1
LEFT JOIN information_2 I2 ON I1.id = I2.id
WHERE ( I1.first_name_2 <> ifnull(I2.first_name_2, '')
OR I1.last_name_2 <> ifnull(I2.last_name_2, '')
OR I1.street_1 <> ifnull(I2.street_1, '')
OR I1.street_2 <> ifnull(I2.street_2, '')
);
Hi I went through the sample data reference and i feel your requirement is To find all the tuples whose exact copy is not there in there in the second table
You can use the following SQL code I tested this on your feedle and it is giving the expected result
SELECT
i.id, i.first_name_2, i.last_name_2, i.street_1, i.street_2
FROM
information i
LEFT JOIN
information_2 i2
ON
i.id=i2.id AND i.first_name_2=i2.first_name_2 AND i.last_name_2=i2.last_name_2
AND i.street_1=i2.street_1 AND i.street_2 = i2.street_2
where
i2.id is null
There is also a simple way to do this if your database supports MINUS set operator just write
SELECT * FROM information
MINUS
SELECT * FROM information_2
and you will get the same answer

How do I compute a ranking with MySQL stored procedures?

Let's assume we have this very simple table:
|class |student|
---------------
Math Alice
Math Bob
Math Peter
Math Anne
Music Bob
Music Chis
Music Debbie
Music Emily
Music David
Sports Alice
Sports Chris
Sports Emily
.
.
.
Now I want to find out, who I have the most classes in common with.
So basically I want a query that gets as input a list of classes (some subset of all classes)
and returns a list like:
|student |common classes|
Brad 6
Melissa 4
Chris 3
Bob 3
.
.
.
What I'm doing right now is a single query for every class. Merging the results is done on the client side. This is very slow, because I am a very hardworking student and I'm attending around 1000 classes - and so do most of the other students. I'd like to reduce the transactions and do the processing on the server side using stored procedures. I have never worked with sprocs, so I'd be glad if someone could give me some hints on how to do that.
(note: I'm using a MySQL cluster, because it's a very big school with 1 million classes and several million students)
UPDATE
Ok, it's obvious that I'm not a DB expert ;) 4 times the nearly the same answer means it's too easy.
Thank you anyway! I tested the following SQL statement and it's returning what I need, although it is very slow on the cluster (but that will be another question, I guess).
SELECT student, COUNT(class) as common_classes
FROM classes_table
WHERE class in (my_subject_list)
GROUP BY student
ORDER BY common_classes DESC
But actually I simplified my problem a bit too much, so let's make a bit it harder:
Some classes are more important than others, so they are weighted:
| class | importance |
Music 0.8
Math 0.7
Sports 0.01
English 0.5
...
Additionally, students can be more ore less important.
(In case you're wondering what this is all about... it's an analogy. And it's getting worse. So please just accept that fact. It has to do with normalizing.)
|student | importance |
Bob 3.5
Anne 4.2
Chris 0.3
...
This means a simple COUNT() won't do it anymore.
In order to find out who I have the most in common with, I want to do the following:
map<Student,float> studentRanking;
foreach (Class c in myClasses)
{
float myScoreForClassC = getMyScoreForClass(c);
List students = getStudentsAttendingClass(c);
foreach (Student s in students)
{
float studentScoreForClassC = c.classImportance*s.Importance;
studentRanking[s] += min(studentScoreForClassC, myScoreForClassC);
}
}
I hope it's not getting too confusing.
I should also mention that I myself am not in the database, so I have to tell the SELECT statement / stored procedure, which classes I'm attending.
SELECT
tbl.student,
COUNT(tbl.class) AS common_classes
FROM
tbl
WHERE tbl.class IN (SELECT
sub.class
FROM
tbl AS sub
WHERE
(sub.student = "BEN")) -- substitue "BEN" as appropriate
GROUP BY tbl.student
ORDER BY common_classes DESC;
SELECT student, COUNT(class) as common_classes
FROM classes_table
WHERE class in (my_subject_list)
GROUP BY student
ORDER BY common_classes DESC
Update re your question update.
Assuming there's a table class_importance and student_importance as you describe above:
SELECT classes.student, SUM(ci.importance*si.importance) AS weighted_importance
FROM classes
LEFT JOIN class_importance ci ON classes.class=ci.class
LEFT JOIN student_importance si ON classes.student=si.student
WHERE classes.class in (my_subject_list)
GROUP BY classes.student
ORDER BY weighted_importance DESC
The only thing this doesn't have is the LEAST(weighted_importance, myScoreForClassC) because I don't know how you calculate that.
Supposing you have another table myScores:
class | score
Math 10
Sports 0
Music 0.8
...
You can combine it all like this (see the extra LEAST inside the SUM):
SELECT classes.student, SUM(LEAST(m.score,ci.importance*si.importance)) -- min
AS weighted_importance
FROM classes
LEFT JOIN class_importance ci ON classes.class=ci.class
LEFT JOIN student_importance si ON classes.student=si.student
LEFT JOIN myScores m ON classes.class=m.class -- add in myScores
WHERE classes.class in (my_subject_list)
GROUP BY classes.student
ORDER BY weighted_importance DESC
If your myScores didn't have a score for a particular class and you wanted to assign some default, you could use IFNULL(m.score,defaultvalue).
As I understand your question, you can simply run a query like this:
SELECT `student`, COUNT(`class`) AS `commonClasses`
FROM `classes_to_students`
WHERE `class` IN ('Math', 'Music', 'Sport')
GROUP BY `student`
ORDER BY `commonClasses` DESC
Do you need to specify the classes? Or could you just specify the student? Knowing the student would let you get their classes and then get the list of other students who share those classes.
SELECT
otherStudents.Student,
COUNT(*) AS sharedClasses
FROM
class_student_map AS myClasses
INNER JOIN
class_student_map AS otherStudents
ON otherStudents.class = myClasses.class
AND otherStudents.student != myClasses.student
WHERE
myClasses.student = 'Ben'
GROUP BY
otherStudents.Student
EDIT
To follow up your edit, you just need to join on the new table and do your calculation.
Using the SQL example you gave in the edit...
SELECT
classes_table.student,
MIN(class_importance.importance * student_importance.importance) as rank
FROM
classes_table
INNER JOIN
class_important
ON classes_table.class = class_importance.class
INNER JOIN
student_important
ON classes_table.student = student_importance.student
WHERE
classes_table.class in (my_subject_list)
GROUP BY
classes_table.student
ORDER BY
2

Sum() + DCount() combination in SQL not producing correct results

How can I get the total number of Granny Smith apples? I have tried everything and nothing seems to work. This is a cross tab query and I'm trying to count the number of Granny_Smith apples in each shipping container.
MS Access 2007 Expression
Total_Green_Apples: Sum(DCount("[Apple_Type]","[Apples]","[Apple_Type]"='Granny_Smith'))
SQL
TRANSFORM Count(DCount("[Apple_Type]","[Apples]")) AS Apple_Type
SELECT Shipping.Container_number, Sum(DCount("[Apple_Type]","[Apples]","[Apple_Type]"='Granny_Smith')) AS Total_Green_Apples
FROM Shipping INNER JOIN Apples ON Shipping.ID = Apples.ID
GROUP BY Shipping.Container_number
PIVOT Apples.Apple_Type;
Please help.
If I'm reading your question correctly, you just need a count of all Granny Smith apples in all shipping containers?
I'm sort of guessing at your DB structure and here's what I come up with:
SELECT COUNT(Apples.ID) as Total_Green_Apples
FROM Shipping
JOIN Apples ON Apples.ID = Shipping.ID
WHERE Apples.Apple_Type = 'Granny Smith'
If you want the count of all Granny Smith apples in each container it'd be:
SELECT Shipping.ID, COUNT(Apples.ID) as Total_Green_Apples
FROM Shipping
JOIN Apples ON Apples.ID = Shipping.ID
WHERE Apples.Apple_Type = 'Granny Smith'
GROUP BY Shipping.ID
If you provide your table definitions I can tune the query.