Using a function in a where-clause - mysql

I have a MySQL-Database where I run certain Stored Procedures.
In one of them, I want to sum up the amount a certain user has to pay, the amount he already payed and return all the users, where the amount to be payed isn't equal the amount paid. I came up with the following (simplyfied) query:
SELECT userId, SUM(costs) as sum_costs,
(SELECT SUM(payed)
FROM payments p WHERE ta.userId=p.userId) as sum_payed
FROM ta
GROUP BY ta.userId
ORDER BY ta.userId;
This gives me the sum of the costs and the payment for each user. But I do not know, how I can select only the users, where costs and payment are not equal.
WHERE sum_costs != sum_payed
doesn't work, because mysql doesn't know the column 'sum_costs' in the WHERE-clause.
Any idea, how to get the selection to work?

SELECT * FROM
(
SELECT userId, SUM(costs) as sum_costs,
(SELECT SUM(payed)
FROM payments p WHERE ta.userId=p.userId) as sum_payed
FROM ta
GROUP BY ta.userId
)a
WHERE a.sum_costs <> a.sum_payed
--ORDER BY a.userId;
Update: actually no need for ORDER BY UserId since it will be ordered implicitly by GROUP BY

SELECT * from
(select userId, SUM(costs) as sum_costs
FROM ta
GROUP BY ta.userId) t1
left join
(SELECT userId,SUM(payed) as sum_payed FROM payments p group by userId) t2
on t1.UserId=t2.UserId
where t1.sum_costs<>t2.sum_payed
ORDER BY t1.userId;

WHERE SUM(costs) <> (SELECT SUM(payed) FROM payments p WHERE ta.userId=p.userId)

Related

SQL Query for getting maximum value from a column Joining from Another Table

This is a slight variant of the question I asked here
SQL Query for getting maximum value from a column
I have a Person Table and an Activity Table with the following data
-- PERSON-----
------ACTIVITY------------
I have got this data in the database about users spending time on a particular activity.
I intend to get the data when every user has spent the maximum number of hours.
My Query is
Select p.Id as 'PersonId',
p.Name as 'Name',
act.HoursSpent as 'Hours Spent',
act.Date as 'Date'
From Person p
Left JOIN (Select MAX(HoursSpent), Date from Activity
Group By HoursSpent, Date) act
on act.personId = p.Id
but it is giving me all the rows for Person and not with the Maximum Numbers of Hours Spent.
This should be my result.
You have several issues with your query:
The subquery to get hours is aggregated by date, not person.
You don't have a way to bring in other columns from activity.
You can take this approach -- joins and group by, but it requires two joins:
select p.*, a.* -- the columns you want
from Person p left join
activity a
on a.personId = p.id left join
(select personid, max(HoursSpent) as max_hoursspent
from activity a
group by personid
) ma
on ma.personId = a.personId and
ma.max_hoursspent = a.hoursspent;
Note that this can return duplicates for a given person -- if there are ties for the maximum.
This is written more colloquially using row_number():
select p.*, a.* -- the columns you want
from Person p left join
(select a.*,
row_number() over (partition by a.personid order by a.hoursspent desc) as seqnum
from activity a
) a
on a.personId = p.id and a.seqnum = 1
ma.max_hoursspent = a.hoursspent;

Mysql get sum of two tables columns grouped

I have 3 tables:
I would like to select the difference of the total gain and total spent per user. So my hypothetical table could be:
I tried this:
SELECT g.total - s.total AS quantity, id FROM
(SELECT SUM(quantity) AS total FROM gain GROUP BY user) AS g,
(SELECT SUM(quantity) AS total FROM spent GROUP BY user) AS s, users
But it doesn't work...
You need to use the users table as base table, to be able to consider all the users, and then LEFT JOIN to the sub queries computing the total spent and total gain. This is because some user may not have any entry in either gain or spent table(s). Also, Coalesce() function handles the NULL (in case of no matching row)
SELECT
u.id AS user,
COALESCE(tot_gain, 0) - COALESCE(tot_spent, 0) AS balance
FROM users AS u
LEFT JOIN (SELECT user, SUM(quantity) as tot_spent
FROM spent
GROUP BY user) AS s ON s.user = u.id
LEFT JOIN (SELECT user, SUM(quantity) as tot_gain
FROM gain
GROUP BY user) AS g ON g.user = u.id
Madhur's solution is fine. An alternative is union all and group by:
select user, sum(gain) as gain, sum(spent) as spent
from ((select user, quantity as gain, 0 as spent
from gain
) union all
(select user, 0, quantity as spent
from spent
)
) u
group by user;
You can join to user if you want users that are not in either table or you need additional columns. However, that join may not be necessary.

Average days between first and second purchase

can someone assist with the following. I need to figure out what the average day difference is between the first and second purchase of a customer:
I have the below: can someone assist with with my query please.
SELECT src.id, AVG(DATEDIFF(dest.purchasing, src.registrations)) AS avgdays FROM
(SELECT accounts.id, accounts.`created_date` AS registrations FROM accounts
WHERE YEAR(accounts.`created_date`) =2018 AND MONTH(accounts.`created_date`) =4) src
INNER JOIN
(SELECT account_id, MIN(created_date) AS purchasing
FROM ordering
WHERE STATUS = 'Fulfilled'
GROUP BY account_id
) dest
ON
dest.account_id = src.id;
The below query will give you the average difference between 2nd purchase if customer registration date is also his first order purchase date.
select account_id,customer_created,
avg(DATEDIFF(order_created, customer_created)) from
(select a.account_id,b.created_at as order_created, a.created_at
customer_created from
(select * from accounts where YEAR(created_at) =2018 AND
MONTH(created_at) =4) a
INNER JOIN ordering b on a.account_id=b.account_id where
b.status="Delivered" ) a
where customer_created <> order_created GROUP BY a.account_id ;

Converting Multiple subqueries with GROUP BY to JOIN

I'm working on a simple ordering system in MySQL and I came across this snag that I'm hoping some SQL genius can help me out with.
I have a table for Orders, Payments (with a foreign key reference to the Order table), and OrderItems (also, with a foreign key reference to the Order table) and what I would like to do is get the total outstanding balance (Total and Paid) for the Order with a single query. My initial thought was to do something simple like this:
SELECT Order.*, SUM(OrderItem.Amount) AS Total, SUM(Payment.Amount) AS Paid
FROM Order
JOIN OrderItem ON OrderItem.OrderId = Order.OrderId
JOIN Payment ON Payment.OrderId = Order.OrderId
GROUP BY Order.OrderId
However, if there are multiple Payments or multiple OrderItems, it messes up Total or Paid, respectively (eg. One OrderItem record with an amount of 100 along with two Payment Records will produce a Total of 200).
In order to overcome this, I can use some subqueries in the following way:
SELECT Order.OrderId, OrderItemGrouped.Total, PaymentGrouped.Paid
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId
JOIN (
SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped ON PaymentGrouped.OrderId = Order.OrderId
As you can imagine (and as an EXPLAIN on this query will show), this is not exactly an optimal query so, I'm wondering, is there any way to convert these two subqueries with GROUP BY statements into JOINs?
The following is likely to be faster with the right indexes:
select o.OrderId,
(select sum(oi.Amount)
from OrderItem oi
where oi.OrderId = o.OrderId
) as Total,
(select sum(p.Amount)
from Payment p
where oi.OrderId = o.OrderId
) as Paid
from Order o;
The right indexes are OrderItem(OrderId, Amount) and Payment(OrderId, Amount).
I don't like writing aggregation queries this way, but it can sometimes help performance in MySQL.
Some answers have already suggested using a correlated subquery, but have not really offered an explanation as to why. MySQL does not materialise correlated subqueries, but it will materialise a derived table. That is to say with a simplified version of your query as it is now:
SELECT Order.OrderId, OrderItemGrouped.Total
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId;
At the start of execution MySQL will put the results of your subquery into a temporary table, and hash this table on OrderId for faster lookups, whereas if you run:
SELECT Order.OrderId,
( SELECT SUM(OrderItem.Amount)
FROM OrderItem
WHERE OrderItem.OrderId = OrderId
) AS Total
FROM Order;
The subquery will be executed once for each row in Order. If you add something like WHERE Order.OrderId = 1, it is obviously not efficient to aggregate the entire OrderItem table, hash the result to only lookup one value, but if you are returning all orders then the inital cost of creating the hash table will make up for itself it not having to execute the subquery for every row in the Order table.
If you are selecting a lot of rows and feel the materialisation will be of benefit, you can simplifiy your JOIN query as follows:
SELECT Order.OrderId, SUM(OrderItem.Amount) AS Total, PaymentGrouped.Paid
FROM Order
INNER JOIN OrderItem
ON OrderItem.OrderID = Order.OrderID
INNER JOIN
( SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped
ON PaymentGrouped.OrderId = Order.OrderId;
GROUP BY Order.OrderId, PaymentGrouped.Paid;
Then you only have one derived table.
What about something like this:
SELECT Order.OrderId, (
SELECT SUM(OrderItem.Amount)
FROM OrderItem as OrderItemGrouped
where
OrderItemGrouped.OrderId = Order.OrderId
), AS Total,
(
SELECT SUM(Payment.Amount)
FROM Payment as PaymentGrouped
where
PaymentGrouped.OrderId = Order.OrderId
) as Paid
FROM Order
PS: You win again #Gordon xD
Select o.orderid, i.total, s.paid
From orders o
Left join (select orderid, sum(amount)
From orderitem) i
On i.orderid = o.orderid
Ieft join (select orderid, sum(amount)
From payments) s
On s.orderid = o.orderid

Optimize Query for MySQL

I want to run a query that generates a revenue report for campaigns. There are 2 tables
members and payments.
members (id, campaign_code)
payments (id, member_id, amount)
I want to create a table that groups by campaign_code, ie in this format
campaign_code, member_count, total_revenue
I am putting all campaigns into an array and running this,
SELECT sum( amount ) AS amt
FROM (members
INNER JOIN payments
ON payments.member_id = members.id
)
WHERE campaign_code = 'XX'
and it is taking a LOT of time. Anyway to optimize this or do it in a single query?
As you said that you need aggregation for all campaign code, try this
SELECT m.campaign_code , count(p.member_id) AS member_count,
SUM( amount ) AS total_revenue
FROM members m, payments p
WHERE p.member_id = m.id
GROUP BY campaign_code;
Make sure to read on mysql group by function
payments (id, member_id, amount)
I want to create a table that groups by campaign_code, ie in this format
campaign_code, member_count, total_revenue
I am putting all campaigns into an array and running this,
select
m.Campaign_Code,
count( distinct p.member_id) as Members,
count(*) as PaymentEntries,
sum( p.amount ) as TotalRevenue
from
members m
join Payments p
on m.id = p.member_id
where
m.campaign_code = 'XX'
If you want all campaigns, just remove the WHERE clause. You mentioned in comments that the tables DO have indexes, but I would ensure that members table has index on campaign_code, and payments has index on member_id.
This query will give a count of distinct members who've contributed, total number of contributions (in case one or more members contributed multiple times), and the totals of all the contributions.
use
where campaing_code in ('XX','YY','ZZ','AA','BB')
and have an index on campaing_code