MySQL Query for Subtraction of 2 fields with decimal(13,2) - mysql

How do you subtract (2)two different fields with the same data type of decimal(13,2) ?
I have table for users
attribtues:
id, name, password, email, deleted, created_at, updated
and
table for top_up_history
attributes:
id, userId, paymentId, paymentDesc, amount, deleted, created_at, updated_at
and
table for transaction_details
id, userId, merchId, transId, amount, refCode, procId, deleted, created_at, updated_at
and
I have view for user_balance
attributes:
userId, total_topup, total_balance
Here's my current query:
SELECT a.id as userId, SUM(b.amount) as total_topup,
SUM(b.amount) - SUM(c.amount) as total_balance
FROM `users` AS a LEFT JOIN `top_up_history` AS b
ON a.id = b.userId LEFT JOIN`transaction_details` as c
ON a.id = c.userId GROUP BY a.id
Now the current output of this user_balance is this:
But the problem is
the data from transaction_details is:
the data from top_up_history
the data from users
There's something wrong with my computation.
The output should be:
userId total_topup total_balance
1 NULL NULL
2 NULL NULL
3 15000 14,725
9 10150 9,875

you should manage the null value
SELECT a.id as userId
, SUM(ifnull(b.amount,0)) as total_topup
, SUM(ifnull(b.amount,0)) - SUM(ifnull(c.amount,0)) as total_balance
FROM `users` AS a
LEFT JOIN `top_up_history` AS b ON a.id = b.userId
LEFT JOIN`transaction_details` as c ON a.id = c.userId
GROUP BY a.id

Just take a look at resulting table without GROUP BY function.
LEFT JOIN top_up_history produces two rows for each unique user,
It caused by repeated userId values (9 and 3) in top_up_history table. At this moment (before joining transaction_details) you already have 6 rows. Now sql joins transaction_details and duplicates its amount column value for each matched row.
Then, finally you group, and sum duplicated values. Substruction itself works ok.
The easiest way to deal with that kind of problem is to do subquery like so:
SELECT a.id as userId, SUM(b.amount) as total_topup,
SUM(b.amount) -
(select sum(tr.amount) from transaction_details_ as tr where tr.users_id = a.id) as total_balance
FROM users_ AS a
LEFT JOIN top_up_history_ AS b ON a.id = b.users_id
LEFT JOIN transaction_details_ as c ON a.id = c.users_id
GROUP BY a.id

Related

Getting max record on varchar field

I have this query
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN
services s ON a.id = s.account_id
INNER JOIN
facilities f ON f.id = a.facility_id
INNER JOIN
account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id) latest_note ON latest_note.account_id = a.id
WHERE
a.facility_id = 56
My problem comes from
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id)
Content is a varchar field and I am needed to get the most recent record. I now understand that MAX will not work on a varchar field the way that I want it. I am not sure how to be able to get the corresponding content with the MAX id and group that by account id on in this join.
What would be the best way to do this?
My notes table looks like this...
id account_id content created
1 1 This is a test 2011-03-16 02:06:40
2 1 More test 2012-03-16 02:06:40
Here are two choices. If your content is not very long and don't have funky characters, you can use the substring_index()/group_concat() trick:
(SELECT account_id,
SUBSTRING_INDEX(GROUP_CONCAT(content ORDER BY created desc SEPARATOR '|'
), 1, '|') as content
FROM notes
GROUP BY account_id
) latest_note
ON latest_note.account_id = a.id
Given the names of the columns and tables, that is likely not to work. Then you need an additional join or a correlated subquery in the from clause. I think that might be easiest in this case:
select . . .,
(select n.content
from notes n
where n.account_id = a.id
order by created desc
limit 1
) as latest_note
from . . .
The advantage to this method is that it only gets the notes for the rows you need. And, you don't need a left join to keep all the rows. For performance, you want an index on notes(account_id, created).
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT account_id, MAX(created) mxcreated
FROM notes GROUP BY account_id) latest_note ON latest_note.account_id = a.id and
latest_note.mxcreated = --datetime column from any of the other tables being used
WHERE a.facility_id = 56
You have to join on the max(created) which would give the latest content.
Or you can change the query to
SELECT account_id, content, MAX(created) mxcreated
FROM notes GROUP BY account_id
as mysql allows you even if you don't include all non-aggregated columns in group by clause. However, unless you join on the max date you wouldn't get the correct results.
The last created record is the one for which does not exist a newer one. Hence:
SELECT
s.account_number,
a.id AS "ASPIRION ID",
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS "STATUS",
astat.definition,
latest_note.content AS "LAST NOTE",
a.insurance_company
FROM accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(
SELECT account_id, content
FROM notes
WHERE NOT EXISTS
(
SELECT *
FROM notes newer
WHERE newer.account_id = notes.account_id
AND newer.created > notes.created
)
) latest_note ON latest_note.account_id = a.id
WHERE a.facility_id = 56;

Mysql query involving multiple tables and an OR statement

Sorry in advance for an ambiguous title, but I can't think of a good one.
I have 2 tables:
bookings(booking_id, customer_id)
charges(customer_id, booking_id, amount)
where in charges table, either booking_id or customer_id must be entered. Not both.
I'm trying to get amount that are associated with a customer_id
Because the customer_id's are null sometimes in charges table, I have to use booking_id to acquire customer_id through bookings table.
So, I had a query like this:
SELECT c.amount
FROM charges as c, bookings as b
WHERE (c.customer_id = 1234) OR
(c.customer_id = null AND c.booking_id = b.booking_id AND b.customer_id = 1234)
However, it seems to create an infinite loop.
Any suggestions?
Thanks,
The problem is that you are doing a cross join (cartesian product), based on the structure of your where clause.
A better approach is to use left outer join (and proper join syntax). So, join to the bookings table, if it exists. Then use or in the where clause to look for a match on the customer_id in either place:
SELECT c.amount
FROM charges as c left outer join
bookings as b
on c.booking_id = b.booking_id
WHERE (c.customer_id = 1234) OR (b.customer_id = 1234)
Why you don't use a JOIN ? like
SELECT c.amout
FROM charges AS c
LEFT JOIN bookings AS b ON c.bookin_id = b.booking_id
WHERE c.customer_id = 1234 OR b.customer_id = 1234
SELECT amount, customer_id from charges where customer_id = 1234
UNION
select amount, b.customer_id from charges c, bookings b where b.booking_id = c.booking_id and c.customer_id is null and b.customer_id = 1234

MySQL: SELECT if non of group by members is equal to x

I have troubles getting proper data.
I have table structure like:
id INT(11) AI
order_id INT(11)
status varchar(45)
This table log status changes for orders.
So order_id's will have few statuses.
Now I need to select rows and group them by order_id, where order never had status (not even one status with given order_id) != 'example'
We don't show orders, where one of members had status = example
Sample data
1 12 ready
1 12 example
2 13 ready
2 13 sent
So I don't want order 12 to show at all, because one of it members have "example" status
I've tried grouping results, but it's not enough.
you can do it by simple join query :
select a.order_id
from ordrstatus as a left outer join (select orderid , count(*) as status from orderstatus where status = 'example' group by orderid) as b on a.orderid = b.orderid
where b.status = 0 or b.status is NUll
Join query always run faster then IN query . by using Join in query it will run only one time .
You can try like this...it will return all order id which never had status -example
Select
Order_id,
from TableName A where Not Exists(
Select id from TableName B where
status='example' and
a.Order_id=b.Order_id
)
group by Order_id
Not quite sure if you want the records for order which have had a status of example, or ones which have never had a status of example
To get a list of orders (with the status grouped up) which have had a status of example:-
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a
INNER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b
ON a.order_id = b.order_id
GROUP BY order_id
To get those which have NEVER had a status of exmaple
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a
LEFT OUTER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b
ON a.order_id = b.order_id
WHERE b.order_id IS NULL
GROUP BY order_id
EDIT
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a -- Statuses
LEFT OUTER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b -- Get any order id which has had a status of example (as a LEFT JOIN)
ON a.order_id = b.order_id
INNER JOIN
(
SELECT order_id, MAX(id) AS Latestid
FROM SomeTable
GROUP BY order_id
) c -- Get the latest status for each order (ie, max id)
ON a.order_id = c.order_id
LEFT OUTER JOIN
(
SELECT order_id, id
FROM SomeTable
WHERE status = 'example2'
) d -- Get the id of the order status of example2
ON a.order_id = d.order_id AND c.Latestid = d.id -- join on the same order id and that the record id matches the latest record id
WHERE b.order_id IS NULL -- reject those where a match was found on example for any status
AND d.order_id IS NULL -- reject those where a match was found on example2 for the latest status
GROUP BY order_id
try this
SELECT Order_ID FROM tbl_Orders
WHERE Status NOT IN ('example')
GROUP BY Order_ID
SELECT DISTINCT x.order_id
FROM order_status x
LEFT
JOIN order_status y
ON y.order_id = x.order_id
AND y.status = 'example'
WHERE y.id IS NULL;

MySQL Query not displaying correctly

I am having to set up a query that retrieves the last comment made on a customer, if no one has commented on them for more than 4 weeks. I can make it work using the query below, but for some reason the comment column won't display the latest record. Instead it displays the oldest, however the date shows the newest. It may just be because I'm a noob at SQL, but what exactly am I doing wrong here?
SELECT DISTINCT
customerid, id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments
WHERE customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
GROUP BY customerid
ORDER BY maxdate
The first "WHERE" clause is just ensuring that it shows only customers from a specific area, and that they are "past due enabled". The second makes sure that the customer has not been commented on within the last 27 days. It's grouped by customerid, because that is the number that is associated with each individual customer. When I get the results, everything is right except for the comment column...any ideas?
Join much better to nested query so you use the join instead of nested query
Join increase your speed
this query resolve your problem.
SELECT DISTINCT
customerid,id, customername, user, MAX(date) AS 'maxdate', comment
FROM comments inner join customers on comments.customerid = customers.id
WHERE comments.pastdue='1' AND comments.hubarea='1' AND DATEDIFF(NOW(), comments.date) <= 27
GROUP BY customerid
ORDER BY maxdate
I think this might probably do what you are trying to achieve. If you can execute it and maybe report back if it does or not, i can probably tweak it if needed. Logically, it ' should' work - IF i have understood ur problem correctly :)
SELECT X.customerid, X.maxdate, co.id, c.customername, co.user, co.comment
FROM
(SELECT customerid, MAX(date) AS 'maxdate'
FROM comments cm
INNER JOIN customers cu ON cu.id = cm.customerid
WHERE cu.pastdue='1'
AND cu.hubarea='1'
AND DATEDIFF(NOW(), cm.date) <= 27)
GROUP BY customerid) X
INNER JOIN comments co ON X.customerid = co.customerid and X.maxdate = co.date
INNER JOIN customer c ON X.customerid = c.id
ORDER BY X.maxdate
You need to have subquery for each case.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN
(
SELECT DISTINCT ID
FROM customers
WHERE pastdue = 1 AND hubarea = 1
) c ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL
The first join gets the latest record for each customer.
The second join shows only customers from a specific area, and that they are "past due enabled".
The third join, which uses LEFT JOIN, select all customers that has not been commented on within the last 27 days. In this case,only records without on the list are selected because of the condition d.customerID IS NULL.
But tomake your query shorter, if the customers table has already unique records for customer, then you don't need to have subquery on it.Directly join the table and put the condition on the WHERE clause.
SELECT a.*
FROM comments a
INNER JOIN
(
SELECT customerID, max(`date`) maxDate
FROM comments
GROUP BY customerID
) b ON a.customerID = b.customerID AND
a.`date` = b.maxDate
INNER JOIN customers c
ON c.ID = a.customerID
LEFT JOIN
(
SELECT DISTINCT customerid
FROM comments
WHERE DATEDIFF(NOW(), date) <= 27
) d ON a.customerID = d.customerID
WHERE d.customerID IS NULL AND
c.pastdue = 1 AND
c.hubarea = 1
Two of your table columns are not contained in either an aggregate function or the GROUP BY clause. for example suppose that you have two data rows with the same customer id and same date, but with different comment data. how SQL should aggregate these two rows? :( it will generate an error...
try this
select customerid, id, customername, user,date, comment from(
select customerid, id, customername, user,date, comment,
#rank := IF(#current_customer = id, #rank+ 1, 1),
#current_customer := id
from comments
where customerid IN
(SELECT DISTINCT id FROM customers WHERE pastdue='1' AND hubarea='1')
AND customerid NOT IN
(SELECT DISTINCT customerid FROM comments WHERE DATEDIFF(NOW(), date) <= 27)
order by customerid, maxdate desc
) where rank <= 1

MySQL syntax: bigger than value, but NOT IN (...)

Tables:
bookings: id, user_id, object_id, date1, ...
booking_status: id, book_id, status
status is int, range from 1 to 9 (request, confirmed, paid, cancelled by user and that sort of stuff), so I need all bookings, where status is at least 4 (which means paid) but no value bigger than 4 (which would mean cancelled etc).
Till now the SELECT looks about like this (I left out some fields (...) to shorten it):
SELECT b.date1, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id)
WHERE ((b.object_id=$object_id) AND (bs.status NOT IN (5,6,7,8,9)));"
...but it still selects those bookings that have booking status bigger than 4 as well. Any ideas how I need to change the query??
Thank you very much in advance!
UPDATE: thank you all again, I am amazed with how many great ideas you have come up with! There is really many ways to do it and I learned a lot from you, so thank you again! I will try all your suggestions and see for the performance, for now I mixed your solutions to this query, which works for now but I need to test it further:
SELECT b.date, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id AND bs.status<=4)
WHERE (b.object_id=$object_id) HAVING MAX(bs.status)=4
it does not return multiple rows, but returns the rows with 4, excludes the rows with more than 4 and has no subqueries...
EDIT 2: I edited the query again... with HAVING MAX(bs.status)=4 it then works...
EDIT 3: sorry, after testing different cases I have to admit I was much too fast by saying it works...
If I'm understanding correctly, what you need is this:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON b.user_id = u.id
WHERE b.object_id = ...
AND EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status = 4
)
AND NOT EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status > 4
)
;
The problem with your current query is that it filters out all joined rows where bs.status > 4, but it doesn't filter out joined rows that have the same bookings.id as a joined row where bs.status > 4.
inspired by ruakh's solution without the correlated subqueries:
select ...
from bookings b
join (select book_id from booking_status group by book_id having max(status) = 4
) bs on b.id = bs.book_id
left join user u on b.user_id = u.id
Another way
SELECT id,
user_id,
object_id,
date1
FROM bookings b
INNER JOIN (SELECT book_id
FROM booking_status
GROUP BY book_id
HAVING MAX(CASE
WHEN status >= 4 THEN status
END) = 4) bs
ON bs.book_id = b.id
(or version without subselect in response to comments)
SELECT b.id,
b.user_id,
b.object_id,
b.date1
FROM bookings b
INNER JOIN booking_status bs
ON bs.book_id = b.id
GROUP BY b.id /* Other RDBMSs would require all columns listed*/
HAVING MAX(CASE
WHEN bs.status >= 4 THEN bs.status
END) = 4
Here's a method without subqueries:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON u.id = b.user_id
JOIN booking_status bs1
ON bs1.book_id = b.id AND bs1.status = 4
LEFT JOIN booking_status bs2
ON bs2.book_id = b.id AND bs2.status > 4
WHERE b.object_id = $object_id
AND b2.book_id IS NULL
This query eliminates any row where there is a status for the same id that is greater than 4.