my first table is meeting
id | title | body |
---------------------------
1 | mytit | my message |
---------------------------
2 | anoth | another mes |
---------------------------
3 | title | again a mess |
my second table is Meeting_status and it has a foreign key from the meeting table and another table called teachers
status_id | teacher_id | meeting_id
-------------------------------------------
1 | 28 | 2
-------------------------------------------
my query is :
SELECT * FROM meeting LEFT JOIN meeting_status ON meeting.id = meeting_status.meeting_id WHERE meeting_status.teacher_id <> 28
what I want as result is to show only the rows where teacher_id 28 doesn't exist like:
id | title | body | status_id | teacher_id | meeting_id
-----------------------------------------------------------------------
1 | mytit | my message | NULL | NULL | NULL
-----------------------------------------------------------------------
3 | title | again a mess | NULL | NULL | NULL
I changed my query like:
SELECT *
FROM meeting
LEFT JOIN meeting_status ON meeting.id = meeting_status.meeting_id where(teacher_id IS NULL
OR teacher_id <> 28)
and it gave me the output I was expecting.
I have the following tables:
Course
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| c_id | bigint(20) | NO | PRI | NULL | auto_increment |
| c_name | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Articles
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| a_id | bigint(20) | NO | PRI | NULL | auto_increment |
| a_name | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Course_Articles
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| ca_id | bigint(20) | NO | PRI | NULL | auto_increment |
| a_id | bigint(20) | NO | | NULL | |
| c_id | bigint(20) | NO | | NULL | |
| t_id | bigint(20) | NO | | NULL | |
| sort_order | int(11) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Term (or semester)
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| t_id | bigint(20) | NO | PRI | NULL | auto_increment |
| t_name | varchar(255) | NO | | NULL | |
| c_id | bigint(20) | NO | | NULL | |
| sort_order | int(11) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
I need to present the data from these tables as follows:
Course Name: Modern Gas Extraction
Articles:
Intro
Overview of gas extraction techniques
Term 1: Conventional Techniques
Coal Mine Methane
Another conventional technique
Another article not in a term
Another one
Term 2: Unconventional Techniques
Underground Coal Gasification
Coal Bed Methane
Hydraulic Fracturing
Conclusions
I know this data would benefit from perhaps nesting/tree. But it is what I have to work with. As you can see Articles can belong to a Term or be free standing.
I am a bit stumped as to how to efficiently query the above to output as per the example.
<?php
// UPDATED with UNION as suggested
try {
$stmt = $dbh->prepare(
"SELECT ca . * , a.a_name, t.t_name
FROM Course_Article AS ca
LEFT JOIN Article AS a ON a.a_id = ca.ca_id
LEFT OUTER JOIN Term AS t ON t.t_id = ca.t_id
WHERE ca.c_id = '2'
UNION SELECT te.t_name, te.t_id, te.c_id
FROM Term AS te
WHERE te.t_id = '2'");
$last_term_id = -1;
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if ($last_term_id != $row['t_id']) {
echo "<b>" . $row['t_name'] . "</b><br />";
$last_term_id = $row['t_id'];
}
echo $row['a_name'] . "<br />";
}
} catch(PDOException $e) {
echo 'Error : '. $e->getMessage();
exit();
}
?>
Thank you
So here is one idea how to do it with union: first part gets all the terms (also the empty ones) and articles under them, and second part gets all the additional articles that belong to the course but not to any term. Hopefully I didn't include too many logic/typo mistakes here.
select t.t_name, ca.*, a.a_name
from Term t
left outer join Course_Article ca ON ca.t_id = t.t_id
left outer join Article a ON a.a_id = ca.a_id
where t.c_id = '2'
UNION
select null as t_name, ca.*, a.a_name
from Course_Articles ca
left outer join Article a ON a.a_id = ca.a_id
where ca.c_id = '2' and ca.t_id is null
Additionally, if you would need to order the result a bit (dunno if the sorting order is generic between the course article and term), you can extend this a bit (add whatever columns you need to the final result into the main select):
select t_name, a_name, sort_column
FROM
(
select t.t_name, ca.*, a.a_name, t.sort_order as sort_column
from Term t
left outer join Course_Article ca ON ca.t_id = t.t_id
left outer join Article a ON a.a_id = ca.a_id
where t.c_id = '2'
UNION
select null as t_name, ca.*, a.a_name, ca.sort_order as sort_column
from Course_Articles ca
left outer join Article a ON a.a_id = ca.a_id
where ca.c_id = '2' and ca.t_id is null
) dt
order by sort_column ASC
I'm trying to create a book-catalogue. I have 3 basic tables - books, authors, books_authors;
books
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| book_id | int(11) | NO | PRI | NULL | auto_increment |
| book_title | varchar(250) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
authors
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| author_id | int(11) | NO | PRI | NULL | auto_increment |
| author_name | varchar(250) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
books_authors
+-----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| book_id | int(11) | NO | MUL | NULL | |
| author_id | int(11) | NO | MUL | NULL | |
+-----------+---------+------+-----+---------+-------+
I have a query that takes the book name and all authors for each book and displays the result:
$booksAndAuthors = mysqli_query($connection, 'SELECT * FROM books LEFT JOIN books_authors ON books.book_id=books_authors.book_id LEFT JOIN authors ON authors.author_id=books_authors.author_id');
It returns:
Book Name -> Author 1, Author 2
Book Name 2 -> Author 3, Author 2
And so on.
And I have another query that it's:
$booksAndAuthors = mysqli_query($connection, 'SELECT * FROM books_authors as ba
INNER JOIN books as b ON ba.book_id=b.book_id
INNER JOIN books_authors as booaut ON booaut.book_id=ba.book_id
INNER JOIN authors as a ON booaut.author_id=a.author_id
WHERE ba.author_id=' . $author_id);
When I click over an author (authors are links), the query returns all books of an author the opposite; The queries all work;
My Question is:
Could someone explain to me why I'm comparing a table with itself. Just explain for dummie like myself. I want to understand the action that is done by this query, with words or something else.
*If my question isn't properly asked! Edit me!
*Regards!
A book can have more than one author. The point of the self-join is to find the other authors for the book.
FROM books_authors as ba
...
INNER JOIN books_authors as booaut ON booaut.book_id=ba.book_id
...
WHERE ba.author_id=42
The join picks up any author who co-authored a book with author 42.
Another way to write the query:
FROM books_authors as ba
...
WHERE EXISTS
(
SELECT *
FROM books_authors ba2
WHERE ba2.book_id = ba.book_id
and ba2.author_id = 42
)
This says, select all rows where a matching book_authors entry exists for author 42.
It seems a self join ( joining the table with itself ) is unnecessary here since you are picking the same rows.
Usually self joins are performed to join two different rows in a table. For example, if you have a table with monthly account balances for example
acount_id |as_of_date | balance_amount
-----------|---------------------------
12213 |2014-01-01 | 10000
12213 |2014-02-01 | 20000
12213 |2014-03-01 | 25000
Let's say the table name is monthly_account_balances
Now you want to compute the difference between monthly balances
For instance, between February and January the difference is 20000 - 10000 = 10000
And between March and February the difference is 25000 - 20000 = 5000
And the output you need is
acount_id |as_of_date | balance_amount|difference
-----------|-----------|---------------|-------------
12213 |2014-01-01 | 10000 | null
12213 |2014-02-01 | 20000 | 10000
12213 |2014-03-01 | 25000 | 5000
Here you do a self join as follows:
select a.*, b.balance_amount - a.balance_amount as difference
from monthly_account_balances a
inner join monthly_account_balances b on a.account_id = b.account_id
and a.as_of_date + interval '1 month' = b.as_of_date
Notice the date condition. It's comparing two different records with same id but different as_of_date. Self join is useful in such situations.
However in your case you are just joining on id and I see no point in doing that unless I am missing something
I have the following MySQL Table Structure:
mysql> desc customers;
+---------------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+-------------+------+-----+---------+-------+
| hash | varchar(32) | NO | PRI | NULL | |
| date_joined | date | NO | | NULL | |
| agent_code | int(5) | NO | UNI | | |
+---------------------+-------------+------+-----+---------+-------+
mysql> desc persons;
+--------------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+-------------+------+-----+---------+-------+
| agent_code | int(5) | NO | UNI | | |
| team_id | int(2) | YES | | 0 | |
| hash | varchar(32) | NO | PRI | NULL | |
+--------------------+-------------+------+-----+---------+-------+
mysql> desc teams;
+--------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| leader | varchar(32) | NO | UNI | NULL | |
+--------+-------------+------+-----+---------+----------------+
And I'd wish to generate a report of sales by Team.
The SQL Query that I'm using is the following:
SELECT COUNT(`customers`.`agent_code) AS `customer_count`, `teams`.`name`
FROM `customers`
JOIN `persons` ON `customers`.`agent_code` = `persons`.`agent_code`
JOIN `teams` ON `persons`.`team_id` = `teams`.`id`
GROUP BY `teams`.`name`
And it shows the following information:
+----------------+--------+
| customer_count | name |
+----------------+--------+
| 3 | Team 1 |
+----------------+--------+
However I'd like to see the "customer_count" of all the teams in the database, even if the customer_count is null (or zero) for a given team. I have 15 teams in my database, so I'd like to see something like:
+----------------+--------+
| customer_count | name |
+----------------+--------+
| 3 | Team 1 |
| 0 | Team 2 |
| 0 | Team 3 |
| 0 | Team 4 |
+----------------+--------+
I have tried to execute some variants of the following Query, but I always get an error saying that the syntax of OUTER JOIN is incorrect, even though I've read the documentation, and it is correct.
SELECT COUNT( `customers`.`agent_code` ) AS `customer_count` , `teams`.`name`
FROM `customers`
LEFT JOIN `persons` ON `customers`.`agent_code` = `persons`.`agent_code`
LEFT OUTER JOIN `teams` ON `persons`.`team_id` = `teams`.`id`
GROUP BY `teams`.`name`
How can I alter my current query in order to display such result?
You have mistake get main table is person - I suggest your main table is team
SELECT COUNT(`customers`.`id`) AS `customer_count` , `teams`.`name`
FROM `teams`
JOIN `persons` ON `persons`.`team_id` = `teams`.`id`
LEFT JOIN `customers` ON `customers`.`agent_code` = `persons`.`agent_code`
GROUP BY `teams`.`name`
Update: if you do have empty teams, than you need to set left join on persons
SELECT COUNT(`customers`.`id`) AS `customer_count` , `teams`.`name`
FROM `teams`
LEFT JOIN `persons` ON `persons`.`team_id` = `teams`.`id`
LEFT JOIN `customers` ON `customers`.`agent_code` = `persons`.`agent_code`
GROUP BY `teams`.`name`
i was looking for a way to combine different mysql queries in a php file so this is my code :
$sql_query = "SELECT b.*,
u.username AS MY_Sender
FROM table_users u,
table_blogs b
WHERE b.reciever = '0'
AND
u.user_id = b.sender
UNION
SELECT b.*,
u2.username AS MY_Recipient
FROM table_users u2,
table_blogs b
WHERE b.reciever != '0'
AND
u2.user_id = b.reciever
";
this code works fine unless it cant fetch MY_Recipient
in the above code i need to fetch both sender of blog post and the receiver
is it wrong to use Union to do so ?!
I have made a guess at your table structure, and produced something similar. Right or wrong, it might at least help arrive at a suitable solution for you.
Two tables, users and blogs:
CREATE TABLE `users` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE `blogs` (
`id` int(11) NOT NULL auto_increment,
`sender` int(11) NOT NULL,
`receiver` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Add some users:
INSERT INTO `users` (username) VALUES
('Alice'), ('Bob'), ('Carol'), ('Eve');
Add blog entries for some users:
INSERT INTO `blogs` (sender, receiver) VALUES
(1,2), (2,1), (3,4), (4,3), (1,4), (4,1);
For each blog entry, list the sender and receiver:
SELECT
b.id,
b.sender AS sender_id,
b.receiver AS receiver_id,
us.username AS sender_name,
ur.username AS receiver_name
FROM blogs AS b
JOIN users AS us ON us.id = b.sender
JOIN users AS ur ON ur.id = b.receiver
ORDER BY b.id;
+----+-----------+-------------+-------------+---------------+
| id | sender_id | receiver_id | sender_name | receiver_name |
+----+-----------+-------------+-------------+---------------+
| 1 | 1 | 2 | Alice | Bob |
| 2 | 2 | 1 | Bob | Alice |
| 3 | 3 | 4 | Carol | Eve |
| 4 | 4 | 3 | Eve | Carol |
| 5 | 1 | 4 | Alice | Eve |
| 6 | 4 | 1 | Eve | Alice |
+----+-----------+-------------+-------------+---------------+
UPDATE 1
table_blogs should probably look like this:
CREATE TABLE IF NOT EXISTS `table_blogs` (
`bid` int(10) NOT NULL AUTO_INCREMENT,
`content` varchar(255) DEFAULT NULL,
`date` varchar(14) DEFAULT NULL,
`sender` int(10) NOT NULL,
`reciever` int(10) NOT NULL,
CONSTRAINT `fk_sender`
FOREIGN KEY (`sender` )
REFERENCES `table_users` (`id` )
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `fk_receiver`
FOREIGN KEY (`receiver` )
REFERENCES `table_users` (`id` )
ON DELETE CASCADE
ON UPDATE CASCADE,
PRIMARY KEY (`bid`)
) ENGINE=MyISAM AUTO_INCREMENT=1 ;
The CONSTRAINT clauses will prevent inserting values for users which don't exist, and will delete entries when users are deleted from the user table.
UPDATE 2
I think this is what you want, but as KM and bobince have stated in the comments, it violates foreign key constraints, which is not really a good idea. So, assuming no foreign key constraints, here's some additional inserts and a modified query:
INSERT INTO `blogs` (sender, receiver) VALUES
(1,0), (0,1), (4,0), (0,4), (2,0), (0,2);
SELECT
b.id,
b.sender AS sender_id,
b.receiver AS receiver_id,
IFNULL(us.username, ur.username) AS sender_name,
IFNULL(ur.username, us.username) AS receiver_name
FROM blogs AS b
LEFT JOIN users AS us ON us.id = b.sender
LEFT JOIN users AS ur ON ur.id = b.receiver
ORDER BY b.id;
+----+-----------+-------------+-------------+---------------+
| id | sender_id | receiver_id | sender_name | receiver_name |
+----+-----------+-------------+-------------+---------------+
| 1 | 1 | 2 | Alice | Bob |
| 2 | 2 | 1 | Bob | Alice |
| 3 | 3 | 4 | Carol | Eve |
| 4 | 4 | 3 | Eve | Carol |
| 5 | 1 | 4 | Alice | Eve |
| 6 | 4 | 1 | Eve | Alice |
| 7 | 1 | 0 | Alice | Alice |
| 8 | 0 | 1 | Alice | Alice |
| 9 | 4 | 0 | Eve | Eve |
| 10 | 0 | 4 | Eve | Eve |
| 11 | 2 | 0 | Bob | Bob |
| 12 | 0 | 2 | Bob | Bob |
+----+-----------+-------------+-------------+---------------+
The field name should be the same
Rename My_sender and My_Recipient to "User" and the union will work.
What are you trying to do? You say there are two queries there, but it looks like the same query to me, just one of them having a different table alias.
The only purpose I can see for the UNION is to put all the rows with a zero-receiver before those without. But you can do that more simply by using a computed ORDER BY:
SELECT b.*, u.username
FROM table_blogs AS b
JOIN table_users AS u ON u.user_id=b.sender
ORDER BY b.receiver<>0
if there are no negative receiver IDs, you could change that to ORDER BY b.receiver as 0 would always come first, which would then be possible to index if you needed to;
ANSI JOIN is generally considered more readable than the old-school method of implicit joins in the WHERE conditions;
<> is preferable to !=, which is a non-standard MySQL synonym;
check the spelling of receiver.
For a union to work, the two select statements should return identical columns. This is where the query is failing.
You can do this in a single query, but if you want to use unions, the problem is that both queries need to have the same column names:
select b.*, u.username AS username, "sender" as type ...
select b.*, u2.username AS username, "recipient" as type...