Optimize Query for MySQL - 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

Related

How to use 2 tables data in one query and make a new column based on the 2 tables data's with MySQL

I have a table contains People details, one for Currency details, one for Peoples Debts, and one for Paid Debts.
I want to make a query for showing Remaining Debts, like if someone debted 100$ and 100,000IQD then debted another 250$, after a while he paid back 150$ and 40,000IQD, I want the query to show that persons remaining debts in 2 rows, one say 200$ and the other say 60,000IQD, so the Remaining Debts money amount is sum of debt_amount - sum of paid_amount for a currency and also for the other.
I can do the summerize for both currencies for each person so it shows the 2 debts of (100$ and 250$) as one 350$, but what causes the problem is that I don't know how to achieve that calculation of getting the remaining amount based on debts and paid ones with MySQL.
Getting all debts query:
SELECT
people.id, people.name, SUM(debt_people.amount) AS debt, debt_people.cur_id AS currency_id
FROM
debt_people
INNER JOIN
people ON people.id = debt_people.people_id
GROUP BY debt_people.people_id , debt_people.cur_id;
Getting all paid depts query:
SELECT
people.id,
people.name,
SUM(payment_people.amount) AS Payment,
payment_people.cur_id AS currency_id
FROM
payment_people
INNER JOIN
people ON people.id = payment_people.people_id
GROUP BY payment_people.people_id , payment_people.cur_id;
What I want to achieve is explained in the image below:
this shows 2 tables that contain Debt and Paid Debts, I want to have a query that takes out the amount of paid money for a person based on the currency id from the debts he did before also based on the currency id
I think you want to use union all before joining:
SELECT p.id, p.name, dp.cur_id,
SUM(dp.debt) AS debt, SUM(dp.payment) as payment,
SUM(dp.debt) - SUM(dp.payment) as remaining
FROM people p JOIN
((SELECT d.people_id, d.amount as debt, 0 as payment, d.cur_id
FROM dept_people d
) UNION ALL
(SELECT p.people_id, 0, p.amount as payment, p.cur_id
FROM payment_people d
)
) dp
ON p.id = dp.people_id
GROUP BY p.id, p.name, dp.cur_id;
I would pre-aggregate the debt and payment tables (to one row per person-currency combination) and then join the results...
SELECT
people.id,
people.name,
debt.currency_id,
debt.amount AS debts,
payment.amount AS payments,
debt.amount - COALESCE(payment.amount, 0) AS remaining
FROM
people
INNER JOIN
(
SELECT people_id, cur_id, SUM(amount) AS amount
FROM debt_people
GROUP BY people_id, cur_id
)
AS debt
ON debt.people_id = people.id
LEFT JOIN
(
SELECT people_id, cur_id, SUM(amount) AS amount
FROM payment_people
GROUP BY people_id, cur_id
)
AS payment
ON payment.people_id = debt.people_id
AND payment.cur_id = debt.cur_id
NOTE: This assumes a payment can always be associated to a debt (same person, same currency)

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.

Wrong use of inner join function / group function?

I have the following problem with my query:
I have two tables:
Customer
Subscriber
linked together by customer.id=subscriber.customer_id
in the subscriber table, I have records with id_customer=0 (these are email records, that do not have a full customer account)
Now i want to show how many customers I have per day, and how many subscribers with id_customer, and how many subscribers WITH id_customer=0 (emailonlies i call them)
Somehow, i cannot manage to get those emailonlies.
Perhaps it has something to do with not using the right join type.
When i use left join, i get the right amount of customers, but not the right amount of emailonlies. When I use inner join i get the wrong amount of customers. Am i using the group function correctly? i think it has something to do with that.
THIS IS MY QUERY:
` SELECT DATE(c.date_register),
COUNT(DISTINCT c.id) AS newcustomers,
COUNT(DISTINCT s.customer_id) AS newsubscribedcustomers,
COUNT(DISTINCT s.subscriber_id AND s.customer_id=0) AS emailonlies
FROM customer c
LEFT JOIN subscriber s ON s.customer_id=c.id
GROUP BY DATE(c.date_register)
ORDER BY DATE(c.date_register) DESC
LIMIT 10
;`
I'm not entirely sure, but I think in DISTINCT s.subscriber_id AND s.customer_id=0, it runs the AND before the DISTINCT, so the DISTINCT only ever sees true and false.
Why don't you just take
COUNT(DISTINCT s.subscriber_id) - (COUNT(DISTINCT s.customer_id) - 1)?
(The -1 is there because DISTINCT s.customer_id will count 0.)
Got it, only risk is that i get no email onlies if there are no customers on this day, becuase of the left join. But this one works:
SELECT customers.regdatum,customers.customersqty,subscribers.emailonlies
FROM (
(SELECT DATE(c.date_register) AS regdatum,COUNT(DISTINCT c.id) AS customersqty
FROM customer c
GROUP BY DATE(c.date_register)
) AS customers
LEFT JOIN
(SELECT DATE(s.added) AS voegdatum,COUNT(DISTINCT s.subscriber_id) AS emailonlies
FROM subscriber s
WHERE s.customer_id=0
GROUP BY DATE(s.added)
) AS subscribers
ON customers.regdatum=subscribers.voegdatum
)
ORDER BY customers.regdatum DESC
;

MySQL JOIN with multiple tables and SUMS

I am trying to create a query that will take information out of four tables for a billing system that I am creating. I have the following tables:
Table Invoice
InvoiceID (PK)
ClientID
Date
Status
...
Table Client
ClientID (PK)
ClientName
...
Table InvoiceItem
ItemID (PK)
InvoiceID
Amount
...
Table Payments
PaymentID (PK)
InvoiceID
Amount
...
I need to create a query where I can access information from the Invoice table along with the client name, and the sum of all invoice items and payments associated with the invoice.
I have tried the following:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN (Client, InvoiceItem, Payment) ON
(Client.ClientID=Invoice.ClientID AND
InvoiceItem.InvoiceID=Invoice.InvoiceID AND
Payment.InvoiceID=Invoice.InvoiceID)
And while this kind-of works, it is multiplying the SUM() by the number of records used to get the sum (i.e. if there are two payments - 800,400 - It gives me (800+400)*2 -- 2400). I am guessing that there is something with how I am using the join, and I have honestly never had to use join for more than one table, and I would always use GROUP BY, but I can't seem to get that to work correctly.
To make matters worse, I have been lost to the world of vb.net/MSSQL client-side programming for the past several years, so my MySQL is rather rough.
Your problem is that you can't aggregate over two independent tables at once in a single query. However you can do it using subqueries.
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
INNER JOIN (
SELECT InvoiceID, SUM(Amount) AS SumOfAmount
FROM InvoiceItem
GROUP BY InvoiceID
) InvoiceItemSum ON InvoiceItemSum.InvoiceID = Invoice.InvoiceID
INNER JOIN (
SELECT InvoiceID, SUM(PaymentAmount) AS SumOfPaymentAmount
FROM Payment
GROUP BY InvoiceID
) PaymentSum ON PaymentSum.InvoiceID = Invoice.InvoiceID
Here try this one
SELECT a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName,
SUM(c.Amount),
SUM(d.PaymentAmount)
FROM Invoice a
INNER JOIN Client b
on a.ClientID = b.ClientID
INNER JOIN InvoiceItem c
ON c.InvoiceID = a.InvoiceID
INNER JOIN JOIN Payment d
ON d.InvoiceID = a.InvoiceID
GROUP BY a.InvoiceID,
a.`Date`,
a.Terms,
a.DateDue,
a.Status,
b.ClinicName
can you elaborate more on this?
it is multiplying the SUM() by the number of records used to get the
sum (i.e. if there are two payments - 800,400 - It gives me
(800+400)*2 -- 2400)
Try this:
SELECT
Invoice.InvoiceID,
Invoice.`Date`,
Invoice.Terms,
Invoice.DateDue,
Invoice.Status,
Client.ClinicName,
SUM(InvoiceItem.Amount),
SUM(Payment.PaymentAmount)
FROM Invoice
JOIN Client ON Client.ClientID=Invoice.ClientID
JOIN InvoiceItem ON InvoiceItem.InvoiceID=Invoice.InvoiceID
JOIN Payment ON Payment.InvoiceID=Invoice.InvoiceID
group by 1,2,3,4,5,6;
I did two things to your query:
Created separated joins for each of the child tables
Added a group by, without which the sum won't work correctly (fyi, in all other databases, omitting the group by would actually result in a syntax error)
you can also achive it by "CROSS APPLY"
SELECT Invoice.InvoiceID, Invoice.`Date`, Invoice.Terms, Invoice.DateDue, Invoice.Status, Client.ClinicName, InvoiceItemSum.SumOfAmount, PaymentSum.SumOfPaymentAmount
FROM Invoice
INNER JOIN Client ON Client.ClientID = Invoice.ClientID
CROSS APPLY ( SELECT ISNULL(SUM(Amount),0) AS SumOfAmount
FROM InvoiceItem
WHERE InvoiceID = Invoice.InvoiceID
) InvoiceItemSum
CROSS APPLY ( SELECT ISNULL(SUM(PaymentAmount),0) AS SumOfPaymentAmount
FROM Payment
WHERE InvoiceID = Invoice.InvoiceID
) PaymentSum

get the average time for time from subscription until payment

I have two tables. The first is subscribers. Subscribers are also appointed to a category. The second table is payments that the subscribers made. I want to know what the average time is between the time of subscription and the FIRST payment of a subscriber (the can make multiple).
Here is a piece of SQL, but it doesn't do what I want just yet - although I have the feeling I'm close ;)
SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, MIN(payments.timestamp)))
FROM subs
JOIN payments ON (payments.user_id = subs.user_id)
GROUP BY category
Now I get "Invalid use of group function" - because of the MIN function, so that ain't right. What do I have to do now? Thanks in advance!
SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, p.timestamp))
FROM subs
JOIN ( SELECT user_id
, min(timestamp) timestamp
FROM payments
GROUP BY user_id
) p
ON p.user_id = subs.user_id
GROUP BY category
If you needed to update another table with the results of this query, you could do something like this (not tested, so there may be syntax errors but hopefully you get the idea). I assume that another_table has category and avg_hrs_spent columns.
UPDATE another_table
SET avg_hrs_spent =
(
SELECT a.avg_hrs_spent FROM
(
(SELECT category,
AVG(TIMESTAMPDIFF(HOUR, subs.timestamp, p.timestamp)) avg_hrs_spent
FROM subs
JOIN ( SELECT user_id
, min(timestamp) timestamp
FROM payments
GROUP BY user_id
) p
ON p.user_id = subs.user_id
GROUP BY category) a
)
WHERE a.category = another_table.category
)