getting wrong values after joining tables - MySQL - PHP - php

I have to generate a report using 4 tables, I tried a lot of joins, but I get wrong values :(
Hope someone will help me.
StaffAssign Table:
----------------------------
| Tid | StaffId | Planned |
----------------------------
| 123 | 2 | 10:00:00 |
| 123 | 4 | 05:00:00 |
| 124 | 2 | 09:00:00 |
----------------------------
ActualEffort Table:
--------------------------------------------
| Tid | StaffId | ActualEffort | logdate |
--------------------------------------------
| 123 | 2 | 05:00:00 |2012-09-01|
| 123 | 4 | 05:00:00 |2012-09-01|
| 123 | 2 | 06:00:00 |2012-09-03|
| 124 | 2 | 09:00:00 |2012-09-04|
--------------------------------------------
ProjectList Table:
-------------------
| ProjectId | Tid |
-------------------
| 1 | 123 |
| 2 | 124 |
-------------------
ProjectName Table:
-------------------
| Id | name |
-------------------
| 1 | project1 |
| 2 | project2 |
-------------------
Below is the Query I'm using:
SELECT P.id AS projectid,P.name,
SEC_TO_TIME(SUM(TIME_TO_SEC(A.planned)))planned,SEC_TO_TIME(SUM(TIME_TO_SEC(E.actualeffort)))actual FROM actualeffort E
INNER JOIN staffassign A ON A.tid = E.tid
INNER JOIN projectlist L ON L.tid = A.tid
INNER JOIN projectname P ON P.id = L.projectid
WHERE E.logdate BETWEEN FROM_UNIXTIME('1346475600') AND FROM_UNIXTIME('1348290000')
GROUP BY P.id
I'm getting this:
---------------------------------------------
| projectid | name | planned | actual |
---------------------------------------------
| 1 | project1 | 15:00:00| 12:00:00 |
| 2 | project2 | 09:00:00| 09:00:00 |
---------------------------------------------
But it should be like:
---------------------------------------------
| projectid | name | planned | actual |
---------------------------------------------
| 1 | project1 | 15:00:00| 16:00:00 |
| 2 | project2 | 09:00:00| 09:00:00 |
---------------------------------------------
I'm very confused that, I don't know where I'm going wrong with the joins.
Someone please help me, I'm struck with this.

The WHERE filter may be wrong. Note that first datetime in the condition starts at '08:00:00' -
SELECT FROM_UNIXTIME('1346475600') dt1, FROM_UNIXTIME('1348290000') dt2;
+---------------------+---------------------+
| dt1 | dt2 |
+---------------------+---------------------+
| 2012-09-01 08:00:00 | 2012-09-22 08:00:00 |
+---------------------+---------------------+
The type of ActualEffort.logdate field is DATE; the value '2012-09-01' is less then '2012-09-01 08:00:00'. So change this condition or use DATE function -
WHERE
E.logdate BETWEEN
DATE(FROM_UNIXTIME('1346475600')) AND DATE(FROM_UNIXTIME('1348290000'))
(edited)
For one thing, when you join StaffAssign and ActualEffort on the Tid column only, you get a mini-Cartesian product for Tid=123 in particular, because both tables contain more than one row with that Tid and there's no other specific condition to establish a 1:1 or, at most, 1:N relationship between the rows.
But in fact, 1:N wouldn't do either: although it wouldn't give you the Cartesian product effect, it would result in duplicated values on one side and, as a consequence, in a distorted SUM.
Therefore, data in StaffAssign and those in ActualEffort must be aggregated separately, then joined, something like this:
SELECT
pl.ProjectId,
pn.name,
sa.Planned,
ae.ActualEffort
FROM ProjectList pl
JOIN ProjectName pn
ON pn.Id = pl.ProjectId
LEFT JOIN (SELECT Tid, SEC_TO_TIME(SUM(TIME_TO_SEC(Planned))) Planned FROM StaffAssign GROUP BY Tid) sa
ON sa.Tid = pl.Tid
LEFT JOIN (SELECT Tid, SEC_TO_TIME(SUM(TIME_TO_SEC(ActualEffort))) ActualEffort FROM ActualEffort GROUP BY Tid) ae
ON ae.Tid = pl.Tid
GROUP BY ProjectId;

There is an error in your SQL:
You wan't to join p.ProjectID not p.id

Try this query
SELECT P.id AS projectid,P.name,
SEC_TO_TIME(SUM(TIME_TO_SEC(A.planned)))planned,SEC_TO_TIME(SUM(TIME_TO_SEC(E.actualeffort)))actual FROM actualeffort E
INNER JOIN staffassign A ON A.tid = E.tid
INNER JOIN projectlist L ON L.tid = A.tid
INNER JOIN projectlist L ON L.tid = E.tid // you have not added this line
INNER JOIN projectname P ON P.id = L.projectid
WHERE E.logdate BETWEEN FROM_UNIXTIME('1346475600') AND FROM_UNIXTIME('1348290000')
GROUP BY P.id
a join between L and E should be present.

Related

How to SELECT 2 joined tables in one MySQL query?

I have 1 master_table and 2 sub_tables. I want the join the 3 columns together (but the problem is the 2 sub_tables do not have any column that share the same value) and then SELECT * based on 2 different columns from the 2 sub_tables.
I've searched and tried many ways of coding, but couldn't find a solution.
SELECT *
FROM (master INNER JOIN sub_1 ON master.id=sub_1.id WHERE sub_1.column_1 = 'Y')
AND (master INNER JOIN sub_2 ON master.id=sub_2.id WHERE sub_2.column_2 = 'Y')
ORDER BY master.id
++++++++++++++++++++++++++++++++++++++++++++++++++
* Finally, solved. See the solution at the bottom of this post. *
++++++++++++++++++++++++++++++++++++++++++++++++++
===========
Edit: explain more about my data, problem and MySQL code
I have 3 tables stored in MySQL as follow
Master_table: regist
------------------------------------------
| reg_no | firstname | lastname | submit |
------------------------------------------
| 1 | first_A | last_A | N |
| 2 | first_B | last_B | A |
| 3 | first_C | last_C | P |
| 4 | first_D | last_D | P |
| 5 | first_E | last_E | A |
| 6 | first_F | last_F | N |
| 7 | first_G | last_G | N |
| 8 | first_H | last_H | A |
------------------------------------------
Sub_1: sub_A Sub_2: sub_P
------------------------------ ------------------------------
| reg_no | A_title | reply_A | | reg_no | P_title | reply_P |
------------------------------ ------------------------------
| 2 | 222 | Y | | 3 | 333 | N |
| 5 | 555 | N | | 4 | 444 | Y |
| 8 | 888 | Y | ------------------------------
------------------------------
I want to create a query that gives result like this
----------------------------------------------------------------------------------
| reg_no | firstname | lastname | submit | A_title | reply_A | P_title | reply_P |
----------------------------------------------------------------------------------
| 2 | first_B | last_B | A | 222 | Y | | |
| 8 | first_H | last_H | A | 888 | Y | | |
| 4 | first_D | last_D | P | | | 444 | Y |
----------------------------------------------------------------------------------
or
-----------------------------------------------------------
| reg_no | firstname | lastname | submit | title | reply |
-----------------------------------------------------------
| 2 | first_B | last_B | A | 222 | Y |
| 8 | first_H | last_H | A | 888 | Y |
| 4 | first_D | last_D | P | 444 | Y |
-----------------------------------------------------------
$sql = "SELECT *
FROM (regist INNER JOIN sub_A ON regist.reg_no = sub_A.reg_no WHERE sub_A.reply_A = 'Y')
AND (regist INNER JOIN sub_P ON regist.reg_no = sub_P.reg_no WHERE sub_P.reply_P = 'Y')
ORDER BY regist.reg_no";
Expected outcome:
ECHO personal data of all registrants who got reply as 'Y'
if($row['submit']=="A") $title = $row['A_title'];
elseif($row['submit']=="P") $title = $row['P_title'];
$result = mysql_query($sql) or die(mysql_error());
while($row = mysql_fetch_array($result))
{
echo $row['reg_no']." / ".$row['firstname']." ".$row['lastname']." / ".$title."<br>";
}
Problem: my SELECT code resulted in error. The code from #GMB and #Rogue didn't error, but echo give nothing.
If it is not possible to code a query as I want, I will just modify the column names (sub_1.reply_A and sub_2.reply_P) to be the same and change the input code in other webpages. However, it would be best if there is a way because I don't know whether the 'reply' columns were used somewhere else.
========================
Solution: a little modification from #Rogue code
SELECT *
FROM master
LEFT OUTER JOIN sub_1
ON master.id=sub_1.id
LEFT OUTER JOIN sub_2
ON master.id=sub_2.id
WHERE sub_1.column_1 = 'Y'
OR sub_2.column_2 = 'Y'
ORDER BY master.id
Do you just want simple JOINs between these 3 tables ?
SELECT m.*, s1.*, s2.*
FROM master m
INNER JOIN sub_1 s1 ON m.id=s1.id AND s1.column_1 = 'Y'
INNER JOIN sub_2 s2 ON m.id=s2.id AND s2.column_2 = 'Y'
ORDER BY m.id;
If you have master records that may not exist in both sub tables, you can switch to LEFT JOIN to avoid filtering them out.
Guidelines :
typical syntax is SELECT ... FROM table1 INNER JOIN table2 ON ... INNER JOIN table3 ON...
better put all conditions related to a JOINed table in the ON clause of the join rather than in the WHERE clause
avoid SELECT * : be specific about the columns you want to select
use table aliases to make the query easier to read
You're a little off syntactically:
SELECT *
FROM master
LEFT OUTER JOIN sub_1
ON master.id=sub_1.id
LEFT OUTER JOIN sub_2
ON master.id=sub_2.id
WHERE sub_1.column_1 = 'Y'
AND sub_2.column_2 = 'Y'
ORDER BY master.id
Personally I would recommend not using SELECT * and only grabbing the data you will need. As for determining what join to use, I like to link to CodingHorror's blog post in these times.
Edit: swapped INNER to LEFT OUTER, per OP's update

mysql: combining columns from for tables

i have this 4 tables
table FRUIT, ID is Primary key
| ID | code | A | B |
--------------------------------------------
| 1 | a01 | apple1 | quava1 |
| 2 | a02 | apple2 | quava2 |
table FLOWER, ID is Primary key
| ID | code | C | D |
--------------------------------------------
| 1 | a01 | Rose1 | Plumer1 |
| 2 | a02 | Rose2 | Plumer2 |
table ANIMAL, ID is Primary key
| ID | code | E | F |
--------------------------------------------
| 1 | a01 | butterfly1 | cat1 |
| 2 | a02 | butterfly2 | cat2 |
table DAY, code is Primary key
| code | G |
-------------------------------------
| a01 | Monday |
| a02 | Tuesday|
i want to call and combined those 4 tables into like this
| ID | A | B | C | D | E | F | G |code |
--------------------------------------------------------------------------------
| 1 | apple1 | quava1 | Rose1 | Plumer1 |butterfly1 | cat1 |Monday |a01 |
| 2 | apple2 | quava2 | Rose2 | Plumer2 |butterfly2 | cat2 |Tuesday |a02 |
this is the code:
SELECT day*, fruit*, flower*, animal*
FROM day
LEFT JOIN fruit
ON day.code = fruit.code
LEFT JOIN flower
ON day.code = flower.code
LEFT JOIN flower
ON day.code = animal.code
ORDER BY day.code DESC;
it is said that column code is ambiguous. and cannot show the table.
i need help. how can i fix this?
I would do it in this manner, using Aliases and JOINs to join all the tables (including the animal table which you forgot), and of course not forgetting to include the periods . where needed:
SELECT fr.ID, fr.A, fr.B, fl.C, fl.D, a.E, a.F, d.G, fr.code
FROM `day` AS d
LEFT JOIN `fruit` AS fr ON d.code = fr.code
LEFT JOIN `flower` AS fl ON d.code = fl.code
LEFT JOIN `animal` AS a ON d.code = a.code
ORDER BY d.code ASC;
you forget to add . between column_name and * like day.*
Try This
SELECT `day`.*, fruit.*, flower.*, animal.*
FROM `day`
LEFT JOIN fruit
ON `day`.code = fruit.code
LEFT JOIN flower
ON `day`.code = flower.code
LEFT JOIN flower
ON `day`.code = animal.code
ORDER BY `day`.code DESC;
Beside the fact that you forgot a dot '.' between the table name and the *, thats becuase you are selecting id and code column from each table, you need to specify where are you selecting from when there is more then 1 options :
SELECT fruit.A,fruit.B, flower.C,flower.D, animal.E,animal.F,day.G,day.code
FROM day
LEFT JOIN fruit
ON day.code = fruit.code
LEFT JOIN flower
ON day.code = flower.code
LEFT JOIN animal
ON day.code = animal.code
ORDER BY day.code DESC;

GROUP BY multiple conditions at once

I have a tables like this:
Users
+----+----------+-------------+
| id | name | other_stuff |
+----+----------+-------------+
| 1 | John Doe | x |
| 2 | Jane Doe | y |
| 3 | Burt Olm | z |
+----+----------+-------------+
Places
+----+------------+-------------+
| id | name | other_stuff |
+----+------------+-------------+
| 1 | Building A | x |
| 2 | Building B | y |
+----+------------+-------------+
Subjects
+----+------------+-------------+
| id | name | other_stuff |
+----+------------+-------------+
| 1 | Math | x |
| 2 | English | y |
+----+------------+-------------+
And a joining table:
PastLectures = lectures that took place
+----+-----------+----------+------------+---------+------------+
| id | id_users | id_place | id_subjects| length | date |
+----+-----------+----------+------------+---------+------------+
| 1 | 1 | 1 | 1 | 60 | 2015-10-25 |
| 2 | 1 | 1 | 1 | 120 | 2015-11-06 |
| 3 | 2 | 2 | 2 | 120 | 2015-11-04 |
| 4 | 2 | 2 | 1 | 60 | 2015-11-10 |
| 5 | 1 | 2 | 1 | 60 | 2015-11-10 |
| 6 | 2 | 2 | 1 | 40 | 2015-11-15 |
| 7 | 1 | 2 | 2 | 30 | 2015-11-15 |
+----+-----------+----------+------------+---------+------------+
I would like to display SUM of all lessons for each user for given month. The SUM should by grouped by each Places and Subjects.
The result in final PHP output should look like this:
November 2015
+------------+-------------+---------------+-------------+
| Users.name | Places.name | Subjects.name | sum(length) |
+------------+-------------+---------------+-------------+
| Burt Olm | - | - | - |
| Jane Doe | Building B | Math | 100 |
| = | = | English | 120 |
| John Doe | Building A | Math | 120 |
| = | Building B | Math | 60 |
| = | = | English | 30 |
+------------+-------------+---------------+-------------+
I have tried creating the full output in pure SQL query using multiple GROUP BY (Group by - multiple conditions - MySQL), but when I do GROUP BY User.id,Places.id it shows each user only once (3 results) no matter the other GROUP BY conditions.
SQL:
SELECT PastLectures.id_users,Users.name AS user,Places.name AS places,Subjects.name AS subjects
FROM PastLectures
LEFT JOIN Users ON PastLectures.id_users = Users.id
LEFT JOIN Places ON PastLectures.id_Places = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
WHERE date >= \''.$monthStart->format('Y-m-d H:i:s').'\' AND date <= \''.$monthEnd->format('Y-m-d H:i:s').'\'
GROUP BY Users.id,Places.id
ORDER BY Users.name,Places.name,Subjects.name
But I don't mind if part of the solution is done in PHP, I just don't know what to do next.
EDIT:
I also have a table Timetable, that stores who regularly teaches what and where. It stores only used combinations of the tables (each valid combination once).
Timetable = lectures that regularly take place
+----+-----------+----------+------------+-------------+
| id | id_users | id_place | id_subjects| other_stuff |
+----+-----------+----------+------------+-------------+
| 1 | 1 | 1 | 1 | x |
| 2 | 1 | 2 | 1 | y |
| 3 | 1 | 2 | 2 | z |
| 4 | 2 | 2 | 1 | a |
| 5 | 2 | 2 | 2 | b |
+----+-----------+----------+------------+-------------+
Is it possible to add only users with combinations that have a row in this table?
In this case it would mean omitting Burt Olm (no id=3 in Timetable). But if Burt has a Timetable entry and still no PastLectures entry, he would show here as in sample result (he should have had a lecture that month, because he is in Timetable, but no lectures took place).
Based on #Barmar's solution I updated the final SQL by making Timetable a primary table and adding one more LEFT JOIN to suffice those needs.
Final SQL:
SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(PastLectures.length)
FROM Timetable
LEFT JOIN PastLectures ON PastLectures.id_users = Timetable.id_users AND PastLectures.id_place = Timetable.id_place AND PastLectures.id_subjects = Timetable.id_subjects
AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
LEFT JOIN Places ON Timetable.id_Place = Places.id
LEFT JOIN Subjects ON Timetable.id_Subjects = Subjects.id
LEFT JOIN Users ON Timetable.id_users = Users.id
GROUP BY Timetable.id,Timetable.id_users,Timetable.id_Place,Timetable.id_Subjects
ORDER BY Users.name,Places.name,Subjects.name
You need to include Subjects.id in the GROUP BY, so you get a separate result for each subject.
Also, you shouldn't use columns in tables that are joined with LEFT JOIN in the GROUP BY column. If you do that, all the non-matching rows will be grouped together, because they all have NULL in that column. Use the columns in the main table.
GROUP BY PastLectures.id_users, PastLectures.id_Place, PastLectures.id_Subjects
DEMO
Note that there's no row for Burt Olm in the demo output, because all his rows are filtered out by the WHERE clause. If you want all users to be shown, you should make Users the main table, not PastLectures. And the date criteria needs to be moved into the ON clause when joining with PastLectures.
SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(length)
FROM Users
LEFT JOIN PastLectures ON PastLectures.id_users = Users.id
AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
LEFT JOIN Places ON PastLectures.id_Place = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
GROUP BY Users.id, PastLectures.id_Place, PastLectures.id_Subjects
ORDER BY Users.name,Places.name,Subjects.name
DEMO
According to standard SQL, you should GROUP BY all the fields you select, except for the aggregated fields (like sum). Althought MySql allows to do otherwise, when it can be done adhering to the standards, it is better to do so (who knows when you need to port your code to another database engine). So write your SQL like this:
SELECT PastLectures.id_users,
Users.name AS user,
Places.name AS places,
Subjects.name AS subjects,
Sum(length)
FROM PastLectures
LEFT JOIN Users ON PastLectures.id_users = Users.id
LEFT JOIN Places ON PastLectures.id_Places = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
WHERE date BETWEEN \''.$monthStart->format('Y-m-d H:i:s').'\'
AND \''.$monthEnd->format('Y-m-d H:i:s').'\'
GROUP BY PastLectures.id_users,
Users.name,
Places.name,
Subjects.name
ORDER BY Users.name,
Places.name,
Subjects.name

Data from three tables duplicates when a JOIN statement is used

when I retrieve data from my tables using JOIN, the rows duplicates. The tables are three in number.
Students
--------
StuID | Name |
1 | Appiah John |
2 | Minister A |
Levels
------
| LevelID | Level | Year | StuID |
| 08 | 100 | 2010 | 2 |
| 83 | 200 | 2011 | 1 |
| 45 | 200 | 2011 | 2 |
Ranks
-----
| RankID | Rank | StuID |
| 101 | 1st | 1 |
| 404 | 4th | 2 |
This is my query statement to select some data from the three tables
SELECT
m.StuID,
n.Level,
n.Year,
o.Rank
FROM
Students m
INNER JOIN
Levels n
ON
m.StuID=n.StuID
INNER JOIN
Ranks o
ON
m.StuID=o.StuID
WHERE
m.StuID=2;
OUTPUT
The query above produces a duplicate answer
| StuID | Level | Year |Rank |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
DESIRED OUTPUT
I therefore wish that the output would be like below
| StuID | Level | Year |Rank |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
QUESTIONS
Where am I going wrong?
Is join the best way to select data from three tables like this?
How can I make a query to get the desired output?
Believe it or not I think the comma between Students m and INNER JOIN is doing it. You're selecting from two separate tuples now, joined on any clause rather than joining the first table to the second to the third.
Try doing a left join instead of an inner join:
SELECT m.StuID,
n.Level,
n.Year,
o.Rank
FROM Students m
LEFT JOIN Levels n ON (m.StuID = n.StuID)
LEFT JOIN Ranks o ON (m.StuID = o.StuID)
WHERE m.StuID = 2
How about using select distinct m.StuID?
You can try something like this
SELECT distinct m.StuID, n.Level, n.Year, o.Rank
FROM Students m INNER JOIN Levels n ON m.StuID=n.StuID
INNER JOIN Ranks o
ON m.StuID=o.StuID WHERE m.StuID=2;

Can't get my head around GROUP_CONCAT

I cannot seem to get an GROUP_CONCAT query to work.
I have this tables, which I joined:
stud
id | stud_name |
-----------------
1 | Class1 |
2 | Class2 |
note
id | stud_id | mat_id | Note |
------------------------------------------------
1 | 1 | 1 | 10 |
2 | 1 | 2 | 9 |
3 | 1 | 1 | 10 |
mat
id | mat_name |
----------------
1 | Porc |
2 | Vaca |
Here is what I did to join them.
SELECT
`stud`.`id`
, `mat`.`mat_name`
, `note`.`note`
FROM
`stud`
LEFT JOIN
`note`
ON
(`stud`.`id` = `note`.`id_stud`)
LEFT JOIN
`mat`
ON
(`note`.`id_mate` = `mat`.`id`)
Here is what I want them to look.
mat.id | mat_name | Note |
-----------------------------
1 | Porc | 10,10 |
2 | Vaca | 9 |
Here is how they look.
mat.id | mat_name | Note |
-----------------------------
1 | Porc | 10 |
2 | Vaca | 9 |
1 | Port | 10 |
I tried doing this.
SELECT
`mat`.`mat_name`,
GROUP_CONCAT(`note`.`note`) AS `note`.`note`,
FROM ( "Here is what I did to join them." )attr_groups
GROUP BY `mat`.`mat_name`;
ORDER BY `mat`.`mat_name`;
Any ideas?
EDIT: If I add group by or ORDER by, no results are found. If I don't add them, the query works but its not concatenating them.
SELECT mat.id,
mat.mat_name,
GROUP_CONCAT(note.note) as note
FROM mat
LEFT JOIN note ON mat.id = note.mat_id
LEFT JOIN stud ON note.stud_id = stud.id
GROUP BY mat.id, mat.mat_name
ORDER BY mat.mat_name
SQLFiddle demo
You have error in group by.
GROUP BY `mat`.`mat_name`;
------------------------^^^---
remove ; from query
SELECT
`stud`.`id`
, `mat`.`mat_name`
, GROUP_CONCAT(`note`.`note`) AS Note
FROM
`stud`
LEFT JOIN
`note`
ON
(`stud`.`id` = `note`.`id_stud`)
LEFT JOIN
`mat`
ON
(`note`.`id_mate` = `mat`.`id`)
GROUP BY
`stud`.mat_id
ORDER BY
`mat`.`mat_name`
Try this:
SELECT
m.id,
m.mat_name,
GROUP_CONCAT(n.Note) as grades
FROM
mat m
LEFT JOIN note n
ON n.mat_id = m.id
GROUP BY
m.id

Categories