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
Related
i have two tables
Cursos
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| curso | varchar(255) | NO | | NULL | |
+-------+------------------+------+-----+---------+----------------+
Trienios
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| data_trienio | varchar(255) | NO | | NULL | |
| curso_id | int(11) | NO | | NULL | |
| oe_id | int(11) | NO | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
those tables are connected through a relationship (as seen through curso_id), and i want to make a query where i retrieve the curso records and the number of trienio records related to each one of them
so i've done this query in laravel
$curso = Curso::select([
'cursos.curso',
\DB::raw('count(trienios.curso_id) as count')
])->join('trienios', 'trienios.curso_id', '=', 'cursos.id')
->groupBy('trienios.curso_id');
which translates to this
select `cursos`.`curso`,
count(trienios.curso_id) as count
from `cursos`
inner join `trienios`
on `trienios`.`curso_id` = `cursos`.`id`
group by `trienios`.`curso_id`
and it gets me the number of cursos with trienios related to them. HOWEVER, it only gives those who have a number of trienios related to them. the other ones who dont have trienios related to them are not queried, and i want to query them. so, how do i solve this issue ?
Use leftJoin(), select cursos.id, and group by cursos.id:
$curso = Curso::select([
'cursos.id',
DB::raw('count(trienios.curso_id) as count')
])->leftJoin('trienios', 'trienios.curso_id', '=', 'cursos.id')
->groupBy('cursos.id');
This is the query I want to run:
SELECT c.id,
COUNT(t.curso_id) AS count
FROM cursos c
LEFT JOIN trienios t
ON t.curso_id = c.id
GROUP BY c.id
The LEFT JOIN should prevent records from cursos from being dropped even if they have no matching counterparts in trienios. As for the error you were seeing, you were selecting cursos.curso, which is not an aggregate and which does not appear in your GROUP BY clause. When MySQL is in only_full_group_by this is not allowed, hence I changed the select clause to cursos.id.
i've three tables
comments table
---------------------------------------------------------------------------
id | commented_by | comment | commented_by
---------------------------------------------------------------------------
1 | asdf156df5s4fd | student comment | student
2 | klsdfjklsdfno | student comment | student
3 | jafkadfjlkdnvwa | teacher comment | teacher
4 | adsjflandsfinndfs | student comment | student
5 | dsf5614dsf8wef4 | teacher comment | teacher
---------------------------------------------------------------------------
teacher table
id | teacher_id | teacher_name | teaches
---------------------------------------------------------------------------
1 | jafkadfjlkdnvwa | abc cde | maths
2 | dsf5614dsf8wef4 | egc kjl | science
.
.
.
---------------------------------------------------------------------------
student table
id | student_id | student_name |
---------------------------------------------------------------------------
1 | asdf156df5s4fd | student_1 |
2 | klsdfjklsdfno | student_2 |
3 | adsjflandsfinndfs | student_3 |
.
.
---------------------------------------------------------------------------
now, what we wish is mysql join which could return something like this
id | comment_id | comment_by | name | teaches
---------------------------------------------------------------------------
1 | asdf156df5s4fd | student | student_1 | null
2 | klsdfjklsdfno | student | student_2 | null
3 | jafkadfjlkdnvwa | teacher | abc cde | maths
4 | adsjflandsfinndfs | student | student_3 | null
5 | dsf5614dsf8wef4 | teacher | egc kjl | science
---------------------------------------------------------------------------
unable to find the exact query with which we can achieve this with codeigniter active records
You need to join student and teacher table:
select id, comment_id as comment_by , commented_by,
( case when commented_by = 'teacher' THEN teacher_name else student_name ) AS name, teaches
from comments c left join teacher t ON t.teacher_id = c.commented_by left
join student s ON s.student_id = c.commented_by
It's real dirty table scheme but this is the query you want:
SELECT
c.id,
commented_by as comment_id,
commented_by,
IF(commented_by = 'student', student_name, teacher_name) as name,
teaches
FROM comments c
LEFT JOIN teacher t
ON t.teacher_id = c.commented_by
AND c.commented_by = 'teacher'
LEFT JOIN student s
ON s.student_id = c.commented_by
AND c.commented_by = 'student'
A better table scheme:
CREATE TABLE comments (
comment_id INT NOT NULL AUTO_INCREMENT,
student_id INT,
teacher_id INT,
PRIMARY KEY (comment_id)
);
CREATE TABLE teachers (
teacher_id INT NOT NULL AUTO_INCREMENT,
teacher_name VARCHAR(200) NOT NULL,
teacher_subject VARCHAR(50),
PRIMARY KEY (teacher_id)
);
CREATE TABLE students (
student_id INT NOT NULL AUTO_INCREMENT,
student_name VARCHAR(200) NOT NULL,
PRIMARY KEY (student_id)
);
SELECT
comments.*,
IF(student_id IS NOT NULL, student_name, teacher_name) as name,
teacher_subject
FROM comments
LEFT JOIN teachers USING(teacher_id)
LEFT JOIN students USING(student_id)
My db structure is as follows: categories table
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| parent_id | int(11) | NO | | NULL | |
| title | varchar(260) | NO | | NULL | |
| slug | varchar(260) | NO | | NULL | |
| custom | int(11) | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
category_to_content table
+-------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| category_id | int(11) | NO | | NULL | |
| content_id | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+----------------+
And content table:
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| heading | varchar(200) | NO | | NULL | |
| subheading | text | NO | | NULL | |
| content_text | longtext | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
As you can see, typical adjacency list model in categories table, category_to_content is here because some categories share same content...
There are 4 levels currently, and 'level 0' categories are actually big/main categories (infobases in this case), and i want to limit search JUST to (sub)categories/content which belongs to: current level 0 category (parent_id of level 0 categories is, well.. 0).
I'm using Codeigniter, query builder class (not mandatory, i can use plain sql query, too, if needed), and returned query looks like this:
SELECT `heading`, `slug`
FROM `content`
JOIN `category_to_content` ON `category_to_content`.`content_id` = `content`.`id`
JOIN `categories` ON `categories`.`id`=`category_to_content`.`category_id`
WHERE `categories`.`id` IN('1,2,3') // current level 0 category //subcategories id's, 12,3, just as example...
AND (
`content`.`heading` LIKE '%pod%'
OR `content`.`subheading` LIKE '%pod%'
)
LIMIT 10
However, this doesn't return desired results in all cases. If i remove parentheses from AND portion of query, i got results, BUT from all main categories. With parentheses, no results, in most cases...
E.g. when search term is 'about' i got proper results, when i try with 'podiatry' - no results.
Sample data:
Just to clarify - IN portion works fine, i got proper level 0 subcategories ids on every page.
If you can't see my error (probably something trivial for MySQL gurus), i can send sample data (export from PhPMyAdmin).
EDIT1: Example: When i type 'pod' i would like to get slug from categories table -> 'podiatry', and heading from 'content' table - 'What is podiatry'.
Interesting, when i type 'about' i get desired results (from desired level 0 SUBcategory). DB export - ASAP, so you can test by your self...
EDIT2: link to sample data: http://pastebin.com/YHziZH8f
EDIT3: in this case, i made 3 main/level0 categories (podiatry - cat id=1, infobase2, cat id=208, test3, cat id=213). They all (almost) have some subcategories, and content related to them. If we are on 'podiatry' page - current level 0 category subcategories should be searched. I want to limit search just to these subcategories (current level 0 subcategories). So, if i am on 'podiatry' page, i need 'about' content just for podiatry category (no 'about' from/for the rest of categories).
EDIT4:
Example of successful query:
SELECT `heading`, `slug`
FROM `content`
JOIN `category_to_content` ON `category_to_content`.`content_id` = `content`.`id`
JOIN `categories` ON `categories`.`id`=`category_to_content`.`category_id`
WHERE `categories`.`id` IN('209,210,211,212')
AND (
`content`.`heading` LIKE '%about%'
OR `content`.`subheading` LIKE '%about%'
)
LIMIT 10
I got: heading 'About Infobase2', and 'about-infobase2' slug.
Second search term:
SELECT `heading`, `slug`
FROM `content`
JOIN `category_to_content` ON `category_to_content`.`content_id` = `content`.`id`
JOIN `categories` ON `categories`.`id`=`category_to_content`.`category_id`
WHERE `categories`.`id` IN('209,210,211,212')
AND (
`content`.`heading` LIKE '%categ%'
OR `content`.`subheading` LIKE '%categ%'
)
LIMIT 10
No results. If you test sample data you will see that there is Category 2 (id=210) in categories table, referenced as: category_id 210, content id 48, in category_to_content table, and Category 2 heading in content table (id=48)... What i'm doing wrong... I can't get that data. :(
Try this: first I join all tables needed
SELECT content.heading, categories.slug
FROM content
INNER JOIN category_to_content ctc ON ctc.content_id = content.id
INNER JOIN categories ON categories.id = ctc.category_id
Then we need to keep only "level 1 categories" (direct subcategories of level 0 categories): I used a subquery instead of listing needed ids. It's more dynamic that way: if you add new categories, no need to update the query.
WHERE categories.parent_id IN (SELECT id FROM categories WHERE parent_id = 0)
Finally, search by keyword.
AND (content.heading LIKE '%pod%' OR content.subheading LIKE '%pod%')
(I'm not sure wether you want to add OR categories.slug LIKE '%pod%' in that part of your query or not.)
And you can add LIMIT 10 at the end if needed.
A word about what was wrong in your query, it's this line:
WHERE `categories`.`id` IN('1,2,3')
it should have been IN(1,2,3) or IN('1','2','3') (see the quotes)
"level 1" categories are not only 1, 2 and 3
So I store all transactions in a transaction table with the following struture:
+----------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+----------------+
| debit_amount | decimal(10,2) | YES | | 0.00 | |
| credit_amount | decimal(10,2) | YES | | 0.00 | |
| flag | int(11) | YES | | NULL | |
| date | datetime | YES | | NULL | |
| id | int(11) | NO | PRI | NULL | auto_increment |
+----------------+---------------+------+-----+---------+----------------+
Then I store the total amount of credits the user has in the "credits" row in the user table.
I am trying to figure out whether there is a mismatch in the total(debit amount + credit amount) for each user as stored in the transactions table to the number of credits stored in the user table.
basically for each user
transactions.debit_amount + transactions.credit amount MUST EQUAL user.credits
but the does not equal operator in the mysql query does not work (especially when the transactions.total is null i.e. there is no row in the transactions table for that user):
SELECT s.id AS uid, s.total, s.credits
FROM (
SELECT (sum(t.credit_amount) + sum(t.debit_amount)) AS total, t.userid, u.credits, u.id
FROM transactions AS t
RIGHT JOIN users AS u ON t.userid = u.id
GROUP BY u.id
) AS s
WHERE s.total != s.credits
Try:
select u.id, u.credits, t.total
from users u
left join (
select userid, sum(coalesce(credit_amount,0)) + sum(coalesce(debit_amount, 0)) as total
from transactions
group by userid
) t on u.id = t.userid
where coalesce(t.total, 0) <> coalesce(u.credits, 0)
You can't compare NULL to a non-null value in MySQL (or at least, if you do, the result is always NULL).
If you can get away with it, use an INNER JOIN to only get users who have made a transaction. If not, use COALESCE to provide a default value of 0 when there are no transaction rows, as per MichaĆ's answer.
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...