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.
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'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 want to delete an entry in my PAGES table. Upon deletion of it, it will cascade to OBJECTS table. No worries in here, it's working if I delete the entry by using simple DELETE. However, I need to specify some conditions:
PAGES table
+--------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+--------------+------+-----+---------+----------------+
| page_id | int(11) | NO | PRI | NULL | auto_increment |
| users_id | int(11) | NO | MUL | NULL | |
| page_value | varchar(20) | NO | UNI | NULL | |
+--------------------------+--------------+------+-----+---------+----------------+
OBJECTS table
+----------------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+-------------+------+-----+---------+----------------+
| objects_id | int(11) | NO | PRI | NULL | auto_increment |
| page_id | int(11) | NO | MUL | NULL | |
| objects_name | varchar(50) | NO | | NULL | |
| objects_avail | varchar(20) | NO | | NULL | |
+----------------------------+-------------+------+-----+---------+----------------+
If objects_avail == "ALL", I must not include that entry in cascade delete. I came up with this SQL query but got an error:
$query = "
DELETE FROM pages AS p
INNER JOIN objects AS o ON p.page_id = o.page_id
WHERE p.page_id = ?
AND p.users_id = ?
AND p.page_value = ?
AND o.objects_avail != ?";
The error thrown:
["42000",1064,"You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax to
use near 'AS p INNER JOIN objects AS o ON p.page_id = o.page_id WHER'
at line 1"]
Example value for the PDO placeholders:
$params = array(81,5,"main page","ALL");
where all of this is valid and I'm sure this is not where the problem is.
I doubt or prettry sure I'm missing some in my query, any suggestions please?
For an inner join UPDATE or DELETE, you need to specify which of the tables you actually want to delete explicitly, or else the parser won't know what you mean. You can choose 1 or more tables to delete from. In your case, it makes sense to just delete p, the alias for pages.
DELETE p
FROM pages AS p
INNER JOIN objects AS o ON p.page_id = o.page_id
WHERE
p.page_id = ? AND
p.users_id = ? AND
p.page_value = ? AND
o.objects_avail != ?
The only line I changed was DELETE became DELETE p
I don't know how to write the SQL syntax of getting the last record (according to recent post and is not replied to).
My table
+-------------------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-----------------------+------+-----+---------+----------------+
| notification_id | mediumint(8) unsigned | NO | PRI | NULL | auto_increment |
| user_id | mediumint(8) unsigned | NO | | NULL | |
| notification_msg | text | NO | | NULL | |
| notification_date | int(11) unsigned | NO | | NULL | |
| private_message | tinyint(1) unsigned | NO | | 0 | |
| has_replied | tinyint(1) unsigned | NO | | 0 | |
| reply_id | mediumint(8) unsigned | NO | | 0 | |
+-------------------+-----------------------+------+-----+---------+----------------+
Basically for each threaded notification, it should get the last record of each notification record and check if has_replied is 0, if it is 0 then it should return it so PHP can read whether there is a notification that hasn't been replied to. So like, it should return like this (pseudo):
+--------------+-----+-----+
| username | 1 | 4 |
| username2 | 0 | 2 |
+--------------+-----+-----+
Where the second column represents if the last post has been replied to or not.
My current SQL syntax (works but does not get the last record and if it's replied to):
SELECT n.*,
m.user_id,
m.username
FROM notifications n
INNER JOIN members m ON n.user_id = m.user_id
WHERE private_message = 1
AND reply_id = 0
ORDER BY has_replied ASC,
notification_date DESC
Select m.user_id, m.username
, N...
From members As M
Join (
Select user_id, Max( notification_id ) As notification_id
From notifications
Group By user_id
) As UserLastNotification
On UserLastNotification.user_id = m.user_id
Join notifications As N
On N.notification_id = UserLastNotification.notification_id
Where N.private_message = 1
And N.reply_id = 0
Order By N.has_replied, N.notification_date Desc
Note that this will filter on each user's last notification being a private message and having a reply_id being zero.
A simple
LIMIT 1
at the end of the query should be sufficient to only return the last post.
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...