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
Related
I am working on a project to catalogue laptops and as such am trying to re-use as much information as possible. A simplified version of the MySQL tables are:
Table: laptop
|----------------------------------|
| id | make | line | model |
| 1 | 1 | 1 | Late 2015 13" |
|----------------------------------|
Table: make
|----------------------------------|
| id | name | other info |
| 1 | Apple | |
|----------------------------------|
Table: line
|----------------------------------|
| id | name | other info |
| 1 | MacBook Pro | |
|----------------------------------|
Table: networking
|----------------------------------|
| id | name | other info |
| 1 | A wifi card | |
| 2 | Another card | |
| 3 | Yet another | |
|----------------------------------|
Table: laptop_networking
|----------------------------------|
| id | networking | laptop |
| 1 | 3 | 1 |
| 2 | 1 | 1 |
|----------------------------------|
So far I used the current statement to retrieve the data in PHP
$statement = $dbc->prepare("
SELECT l.id
, m.id AS makeID
, m.name AS makeName
, n.id AS lineID
, n.name AS lineName
, l.model
FROM laptop l
JOIN make m
ON l.make = m.id
JOIN line n
ON l.line = n.id
WHERE l.id = :laptop);
$statement->bindParam(':laptop', $anID, PDO::PARAM_INT);
$statement->execute();
$theLaptop = $statement0>fetch();
At present running this code with $anID = 1 returns
|---------------------------------------------------------------|
| id | makeID | makeName | lineID | lineName | Model |
| 1 | 1 | Apple | 1 | MacBook Pro | Late 2015 13" |
|---------------------------------------------------------------|
What I would like to do is append another column to the table which returns all names from Networking which have an ID equal to a row in laptop_networking where the laptop field is equal to the ID from the retrieved laptop row
Such as:
|------------------------------------------------------------------------------------------|
| id | makeID | makeName | lineID | lineName | model | networking |
| 1 | 1 | Apple | 1 | MacBook Pro | Late 2015 13" | Yet another, A wifi card |
|------------------------------------------------------------------------------------------|
Is this possible as my many attempts at different types of JOINs have not yielded the desired results.
Thank you
Try this query:
SELECT laptop.id,
make.id AS makeID,
make.name AS makeName,
line.id AS lineID,
line.name AS lineName,
laptop.model,
t.networking
FROM laptop
INNER JOIN make
ON laptop.make = make.id
INNER JOIN line
ON laptop.line = line.id
INNER JOIN
(
SELECT t1.laptop, GROUP_CONCAT(t2.name) AS networking
FROM laptop_networking t1
INNER JOIN networking t2
ON t1.networking = t2.id
GROUP BY t1.laptop
) t
ON laptop.id = t.laptop
WHERE laptop.id = :laptop
Demo here:
Rextester
I am using caldera forms in my WordPress site to collect data for new admissions for our club. Form functions as expected. I am trying to view a list of open applications and a page to view them. Following is my SQL table tor entries (cf_form_entries).
+----+-----------------+--------+
| ID | Form ID | Status |
+----+-----------------+--------+
| 1 | CF5852c23e56b1d | active |
+----+-----------------+--------+
| 2 | CF5852c23e56b1d | active |
+----+-----------------+--------+
| 3 | CF5852c23e56b1d | active |
+----+-----------------+--------+
Following table contains all the information submitted by the form (cf_form_entry_values);
+----+----------+---------------+--------------------+
| id | entry_id | slug | value |
+----+----------+---------------+--------------------+
| 1 | 1 | branch | Branch A |
| 2 | 1 | full_name | asdasd asdasd |
| 3 | 1 | email_address | b2196363#trbvn.com |
| 4 | 1 | phone | 111111111 |
| 5 | 2 | branch | Branch A |
| 6 | 2 | full_name | Full Name |
| 7 | 2 | email_address | asdasd#asdas.com |
| 8 | 2 | phone | 111111111 |
| 9 | 3 | branch | Branch A |
| 10 | 3 | full_name | Namwe |
| 11 | 3 | email_address | wert#wertwert.com |
| 12 | 3 | phone | 111111111 |
+----+----------+---------------+--------------------+
I can run a simple select query and get the open application details of a given branch, inner joining tables.
SELECT cf_form_entries.id,
cf_form_entries.form_id,
cf_form_entries.status,
cf_form_entry_values.slug,
cf_form_entry_values.value
FROM cf_form_entry_values
INNER JOIN cf_form_entries
ON cf_form_entry_values.entry_id = cf_form_entries.id
WHERE cf_form_entry_values.slug = 'branch'
AND cf_form_entry_values.value LIKE '%Branch A%'
Above query results in the following table;
+----+-----------------+--------+--------+----------+
| id | form_id | status | slug | value |
+----+-----------------+--------+--------+----------+
| 1 | CF5852c23e56b1d | active | branch | Branch A |
| 3 | CF5852c23e56b1d | active | branch | Branch A |
+----+-----------------+--------+--------+----------+
My question is, How can I display (echo) the other details such as the name, email address, etc. of the selected tables?
As such my end result shall display all details of the open applications in a table. (not just the branch name)
I tried a while loop. But I can only echo the branch names as they are the only data selected in my inner joined table.
One method is conditional aggregation:
SELECT fe.id, fe.form_id, fe.status,
MAX(CASE WHEN fev.slug = 'branch' THEN fev.value END) as branch,
MAX(CASE WHEN fev.slug = 'full_name' THEN fev.value END) as full_name,
. . .
FROM cf_form_entries fe INNER JOIN
cf_form_entry_values fev
ON fev.entry_id = fe.id
GROUP BY fe.id, fe.form_id, fe.status;
The nice thing about this approach is that adding a new value only requires adding a new expression in the SELECT.
Try Following Query By Using LEFT JOIN ON Two Tables.
SELECT cfev.*,cfm.Form ID as entry_id
FROM cf_form_entry_values cfev
LEFT JOIN cf_form_entries cfm ON cfm.id = cfev.entry_id
WHERE cfev .slug = 'branch' AND vfm.value LIKE '%Branch A%'
This Might Be Helpful. And You Get Proper Output...
This should give you the first two values, and you can use the same pattern for the rest:
SELECT entries.id,
entries.form_id,
entries.status,
(SELECT value FROM cf_form_entry_values as v
WHERE slug='branch' and entry_id=entries.entry_id) as branch,
(SELECT value FROM cf_form_entry_values as v
WHERE slug='full_name' and entry_id=entries.entry_id) as full_name
FROM cf_form_entry_values
INNER JOIN (
SELECT * FROM cf_form_entry_values
INNER JOIN cf_form_entries
ON cf_form_entry_values.entry_id = cf_form_entries.id
WHERE cf_form_entry_values.slug = 'branch'
AND cf_form_entry_values.value LIKE '%Branch A%'
) as entries
ON cf_form_entry_values.entry_id = entries.id
GROUP BY cf_form_entries.id
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
I have the following code where i need to select all items from personabisna table and count items with the same personalbisnaId from another table where the both tables share the personalbisnaid
$query="select c.BusinessLogo,
c.PersonalBisnaId,
c.account_id,
AS Ads from personalbisna As c INNER JOIN myads AS b on b.PersonalBisnaId=c.PersonalBisnaId GROUP BY c.PersonalBisnaId LIMIT $itemfrom,$dataperpage";
These are the tables
Personalbisna table
| PersonalBisnaId| account_id| BusinessLogo
---------------------------------------------
| 1 | 23 | qwertyu.jpg
| 2 | 4 | asdfghjk.jpg
| 3 | 12 | 34567gfd.jpg
| 4 | 34 | drtyujhv.jpg
myads table
| MyAdsId | PersonalBisnaId| AdType
---------------------------------------------
| 1 | 2 | logo
| 2 | 2 | business card
| 3 | 3 | logo
| 4 | 2 | caricalture
I have used some already answered questions to solve my problem and i'm really getting totally confused to solve my issue
The above query should output the following
| PersonalBisnaId| account_id| BusinessLogo | AdsCount
-------------------------------------------------------
| 1 | 23 | qwertyu.jpg | 0
| 2 | 4 | asdfghjk.jpg | 3
| 3 | 12 | 34567gfd.jpg | 1
| 4 | 34 | drtyujhv.jpg | 0
This what i have
$query="SELECT
c.BusinessLogo,
c.PersonalBisnaId,
c.account_id,
c.BusinessName,
c.BusinessCategory,
c.BusinessSubCategory,
c.town,
c.estate,
c.street,
c.road,
c.building,
c.Address,
c.city,
c.PhoneNumber,
c.AltPhoneNumber,
c.website,
c.Email,
c.BusinessType
COUNT(MyAdsId) AS AdsCount
FROM personalbisna AS c
LEFT OUTER JOIN myads AS b
ON b.PersonalBisnaId= c.PersonalBisnaId
GROUP BY c.PersonalBisnaId LIMIT $itemfrom,$dataperpage";
Count aggregate is missing in your query.
Also if you want select all items from Personabisna table then instead of INNER JOIN you need to Left/Right Outer Join
SELECT c.personalbisnaid,
c.account_id,
c.BusinessLogo,
Count(AdType) AS AdsCount
FROM personalbisna AS c
LEFT OUTER JOIN myads AS b
ON b.personalbisnaid=c.personalbisnaid
GROUP BY c.personalbisnaid,
c.account_id,
c.BusinessLogo
I have 3 tables,
itemmaster
|--------|----------|
| id | name |
|--------|----------|
| 1 | Pizza |
|--------|----------|
| 2 | Burger |
|--------|----------|
| 3 | Pepsi |
---------------------
order
|--------|----------|
|orderid | date |
|--------|----------|
| 1 | 1-1-11 |
|--------|----------|
| 2 | 2-1-11 |
|--------|----------|
| 3 | 3-1-11 |
---------------------
orderdetails
|--------|-------------|---------|---------|
| id | orderid |itemid |quantity |
|--------|-------------|---------|---------|
| 1 | 1 | 1 | 10 |
|--------|-------------|---------|---------|
| 2 | 1 | 2 | 20 |
|--------|-------------|---------|---------|
| 3 | 2 | 1 | 10 |
-------------------------------------------
I want to join these 3 tables to get quantity of items of an order that placed on a particular date.
What I have tried is
$this->db->from('itemmaster');
$this->db->join('orderdetails', 'orderdetails.itemid= itemmaster.id','left');
$this->db->join('order', 'order.orderid= orderdetails.orderid');
$this->db->where('order.date',"1-1-11");
$query = $this->db->get();
I got Result as,
Pizza------ 10
Burger------10
What I want is,
Pizza-------10
Burger------20
Pepsi-------0
If changing the all the joins to left joins, you can always do it in two separate query and do a union between the two of them. One would get all the lines that actualy have a quantity and the other would get the rests and put them toguether.
It would look a bit like this. there might be some synthax error, but you'll have to rewrite it in php anyway:
p.s. to add up the quantities, you can use sum()
Select itemmaster.name, orderdetails.quantity from itemmaster
left join orderdetails on orderdetails.itemid = itemmaster.id
left join order on order.orderid = orderdetails.orderid
where order.date = '1-1-11'
group by itemmaster.name
Union
Select itemmaster.name, '0' as quantity From itemmaster
except (Select itemmaster.name, orderdetails.quantity from itemmaster
left join orderdetails on orderdetails.itemid = itemmaster.id
left join order on order.orderid = orderdetails.orderid
where order.date = '1-1-11'
group by itemmaster.name)
group by itemmaster.name
hope this helps ! good luck