MySQL - count multiple tables by day - mysql

I have various tables that track stuff being created within the system, sales, customer accounts, etc, and they all have created times on them. I can summarize any one of these on a per day basis with the following query:
select date(created_time), count(*) from customers group by date(created_time)
Which produces output like:
+--------------------+----------+
| date(created_time) | count(*) |
+--------------------+----------+
| 2012-10-12 | 15 |
| 2012-10-13 | 4 |
That gets the job done although it does skip over days when nothing happened.
However what I'd like to do is generate the same thing for multiple tables at once, producing something like:
+--------------------+--------------+------------------+
| date(created_time) | count(sales) | count(customers) |
+--------------------+--------------+------------------+
| 2012-10-12 | 15 | 1 |
| 2012-10-13 | 4 | 3 |
I could run the query separately for each table and join them by hand, but the skipping 0 days makes that join difficult.
Is there a way I can do this in a single mysql query?

Try this:
SELECT created_time, SUM(customers), SUM(sales)
FROM (SELECT DATE(created_time) created_time, COUNT(*) customers, 0 sales
FROM customers
GROUP BY created_time
UNION
SELECT DATE(created_time) created_time, 0 customers, COUNT(*) sales
FROM sales
GROUP BY created_time
) as A
GROUP BY created_time;

Related

MySQL GROUP_CONCAT with SUM() and multiple JOINs inside subquery

I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;

MySql Sum different types of expenses from 'expense' table based on value in 'expense type' group by employee

A more generic title for this post would be
MySql Sum different columns in same table based on value of another row, group by yet another row
I have a table of employee expenses:
id | employee_id | expense_cat_id | expense_amount |
1 | 11 | 1 | 100 |
2 | 11 | 1 | 200 |
3 | 12 | 1 | 120 |
4 | 12 | 1 | 140 |
5 | 11 | 2 | 5 |
6 | 12 | 2 | 8 |`
and I want to produce a report like this:
Employee Id | Expense Cat 1 Total Amount | Expense Cat 2 Total Amount
11 | 300 | 5
12 | 260 | 8
So initially I thought I could use 2 table aliases for the same table like this:
SELECT
employee_id,
sum(expense_cat_1.expense_amount) as expense_1_total,
sum(expense_cat_2.expense_amount) as expense_2_total
FROM
expenses as expense_cat_1 where expense_cat_1.expense_cat_id=1 ,
expenses as expense_cat_2 where expense_cat_2.expense_cat_id=2
group by employee_id
but this was not correct Sql Syntax, which makes sense to me.
So I thought I could do two joins on between employee table and the expenses table:
SELECT
employees.id as employee_id,
sum(expenses_cat_1.expense_amount) as expense_1_total,
sum(expenses_cat_2.expense_amount) as expense_2_total
FROM employees
join expenses as expenses_cat_1 on employees.id = expenses_cat_1.employee_id and expenses_cat_1.expense_cat_id=1
join expenses as expenses_cat_2 on employees.id = expenses_cat_2.employee_id and expenses_cat_2.expense_cat_id=2
group by employees.id
Which comes close, but is wrong:
employee_id | expense_1_total | expense_2_total
11 | 300 | 10
12 | 260 | 16
as the expense 2 total is doubled! I think this is because the join on shows up two rows for each of the two expenses with category 1, and sums them.
I also tried a sub-query approach:
SELECT (SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 1) AS sum1 ,
(SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 2) AS sum2,
employee_id
FROM expenses group by employee_id
but this has the same problem as the join approach - totals for cat 2 are doubled.
How do I make the second join only include the expense_2_total once ???
I have a personal dislike of sql case statements as they seem more of a procedural language construct (and sql is declarative), but am happy to consider their use in this case - but I put the challenge out there for sql experts to solve this elegantly.
You are looking for conditional aggregation:
SELECT employee_id,
sum(case when expense_cat_id = 1 then expense_amount else 0 end) as expense_1_total,
sum(case when expense_cat_id = 2 then expense_amount else 0 end) as expense_2_total
FROM expenses e
GROUP BY employee_id;

Is this select subquery avoidable?

I have two tables (Invoices and taxes) in mysql:
Invoices:
- id
- account_id
- issued_at
- total
- gross_amount
- country
Taxes:
- id
- invoice_id
- tax_name
- tax_rate
- taxable_amount
- tax_amount
I'm trying to retrive a report like this
rep_month | country | total_amount | tax_name | tax_rate(%) | taxable_amount | tax_amount
--------------------------------------------------------------------------------------
2017-01-01 | ES | 1000 | TAX1 | 21 | 700 | 147
2017-01-01 | ES | 1000 | TAX2 | -15 | 700 | 105
2016-12-01 | FR | 100 | TAX4 | 20 | 30 | 6
2016-12-01 | FR | 100 | B2B | 0 | 70 | 0
2017-01-01 | GB | 2500 | TAX3 | 20 | 1000 | 200
The idea behind this is that an invoice has a has_many relation with taxes. So an invoice can have or not taxes. The report should show the total amount collected (total_amount) for a given country (regardess if it includes taxes)
and indicate which part of that total amount is taxable (taxable_amount) for an specific tax.
My current approach is this one:
SELECT
DATE_FORMAT(invoices.issued_at, '%Y-%m-01') AS rep_month,
invoices.country AS country
( SELECT sum(docs.gross_amount)
FROM invoices AS docs
WHERE docs.country = invoices.country
AND DATE_FORMAT(docs.issue_date, '%Y-%m-01') = rep_month
) AS total_amount,
taxes.tax_name AS tax_name,
taxes.tax_rate AS tax_rate,
SUM(taxes.taxable_amount) AS taxable_amount,
SUM(taxes.tax_amount) AS tax_amount
FROM invoices
JOIN taxes ON invoices.id = taxes.document_id
AND documents.issue_date BETWEEN '2016-01-01' AND '2017-12-31'
GROUP BY account_id, rep_month, country, tax_name, tax_rate
ORDER BY country desc
Well, this works but for a real dataset (thousands of records) it's really slow as the select subquery for retrieving the total_amount is being run for each row of the report.
I cannot make a LEFT JOIN taxes with a direct SUM(gross_amount) as the GROUP BY groups by tax name and rate and I need to show the total collected per country regardless if the amount was taxed or not. Is there a faster alternative to this?
I do not know the exact use case of using this query but the issue is the way with which you're trying to structure the DB, you're trying to get the entire data in one go.
Ideally, you should run the query you have and store in a different table (summary table) and then query directly from the summary table whenever you want. And if you have a new entry in the Invoices table then you can use the query to run either on every entry or periodically update the summary table via a cronjob.

SUM after COUNT and Group By MYSQL

I want to ask about SQL in mysql. Im stack over 1 hour :(
I have sql :
SELECT TZL.IsMissed, COUNT(TZL.ChatID) as Amount FROM tblLog TZL group by TZL.IsMissed
And the result :
| IsMissed | Amount |
| 0 | 100 |
| 1 | 500 |
I want add one more column after Amount column, let say the name of new column is SumAmount. i want SumAmount value is SUM of the Amount Column.
| IsMissed | Amount | SumAmount |
| 0 | 100 | 600 |
| 1 | 500 | 600 |
I already try sql like below :
SELECT
tbl.*,SUM(tbl.Amount) as SumAmount
FROM
(
SELECT
TZL.IsMissed,
COUNT(TZL.ChatID) AS Amount
FROM
tblLog TZL
GROUP BY
TZL.IsMissed
) tbl
GROUP BY
tbl.IsMissed
WITH ROLLUP
But with ROLLUP the result is add a new one row, not column. Anyone can teach me for this ?
Thanks for answer
There are several ways to approach this. I would calculate the value in the from clause:
SELECT TZL.IsMissed, COUNT(TZL.ChatID) as Amount, tt.SumAmount
FROM tblLog TZL CROSS JOIN
(SELECT COUNT(*) as SumAmount FROM tblLog) tt
GROUP BY TZL.IsMissed, tt.SumAmount;

MySQL nested query

I have 2 tables, one containing products, one purchases. I'm trying to find the average of the last 3 purchase prices for each product. So in the example below, for the product 'beans' I would want to return the average of the last 3 purchase prices before the product time 1230854663, i.e. average of customer C,D,E (239)
Products
+-------+------------+
| Name | time |
+-------+------------+
| beans | 1230854764 |
+-------+------------+
Purchases
+----------+------------+-------+
| Customer | time | price |
+----------+------------+-------+
| B | 1230854661 | 207 |
| C | 1230854662 | 444 |
| D | 1230854663 | 66 |
| E | 1230854764 | 88 |
| A | 1230854660 | 155 |
+----------+------------+-------+
I've come up with a nested select query which nearly gets me there i.e. it works if I hard code the time:
SELECT products.name,(SELECT avg(temp.price) FROM (select purchases.price from purchases WHERE purchases.time < 1230854764 order by purchases.time desc limit 3) temp) as av_price
from products products
But if the query references product.time rather than a hard coded time such as below I get an error that column products.time does not exist.
SELECT products.name,(SELECT avg(temp.price) FROM (select purchases.price from purchases WHERE purchases.time < products.time order by purchases.time desc limit 3) temp) as av_price from products products
I'm not sure if I'm making a simple mistake with nested queries or I'm going about this in totally the wrong way and should be using joins or some other construct, either way I'm stuck. Any help would be greatfully received.
The only problem in your query is you haven't mentioned products table in your inner query.
SELECT products.name,(SELECT avg(temp.price)
FROM (select purchases.price from purchases,products
WHERE purchases.time < products.time order by purchases.time desc limit 3) temp) as
av_price from products products