Add two columns for sum of money by year - mysql

Below is my query that groups my customers together, and sums their total money spent over the last two years. Works like a charm. However I need a little more which goes over my sql knowledge.
Is there any way to select three more columns and populate it with the money they spent for each of those years (2010, 2011, 2012)?
SELECT SUM(price) AS money_spent_total, co.customer_id, cu.first_name, cu.last_name, cu.email_primary, cu.phone_primary, co.date_paid
FROM customer_order AS co
INNER JOIN customer AS cu ON (cu.customer_id = co.customer_id)
WHERE cu.customer_id != 32518 AND co.date_paid > "2010-1-1" AND co.date_paid < "2013-1-1"
GROUP BY co.customer_id

You need to GROUP BY YEAR(date_paid) in addition to customer_id. You should also not be selecting any columns which are not in your GROUP BY. In addition, you can use the BETWEEN operator instead of > and < for your date range.
SELECT Year(co.date_paid),
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id
To get the columns in your original SELECT, you would do something like (not tested):
SELECT a.date_year_paid,
a.customer_id,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT Year(co.date_paid) AS date_year_paid,
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
If you were doing the INNER JOIN just for customer info, then it can be dropped.
SELECT a.customer_id,
a.date_year_paid,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id, YEAR(date_paid) AS date_year_paid, SUM(price) AS money_spent_total
FROM customer_order
GROUP BY customer_id, YEAR(date_paid)) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Lastly, if you want to group the years into columns:
SELECT a.customer_id,
a.y2010 AS '2010_money_paid',
a.y2011 AS '2011_money_paid',
a.y2012 AS '2012_money_paid',
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id,
Sum(CASE
WHEN Year(date_paid) = 2010 THEN price
ELSE 0
end) AS 'y2010',
Sum(CASE
WHEN Year(date_paid) = 2011 THEN price
ELSE 0
end) AS 'y2011',
Sum(CASE
WHEN Year(date_paid) = 2012 THEN price
ELSE 0
end) AS 'y2012'
FROM customer_order
GROUP BY customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Result
| CUSTOMER_ID | 2010_MONEY_PAID | 2011_MONEY_PAID | 2012_MONEY_PAID | FIRST_NAME | LAST_NAME | EMAIL_PRIMARY | PHONE_PRIMARY |
------------------------------------------------------------------------------------------------------------------------------
| 1 | 9000 | 3000 | 2000 | Bob | Smith | bob#smith.com | 1112223333 |
| 2 | 4000 | 5000 | 1000 | Tom | Jones | tom#jones.com | 2223334444 |
See the demo

Related

Joining two tables produces no result despite common values

My fiddle is here
What I am trying to achieve?
I need to sum up income and sum up ad spend for each product in each month.
So the first query is summing ad cost for each product each month.
The second query is summing up income for each product during each month.
And the third query should merge this result and produce this table:
My first query looks like this:
SELECT ap.product, DATE_FORMAT(min(ai.date), '%m-%Y') as date, ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN (SELECT ad_id, product FROM ads_products anp) ap ON ai.ad_id = ap.ad_id
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
ORDER BY min(ai.date) asc;
My second query looks like this:
select pi.name, round(sum(pi.value)) as monthly_income, DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
order by min(pi.date) asc;
And now I want to join this queries, so I do something like this:
SELECT ap.product, DATE_FORMAT(min(ai.date), '%m-%Y') as date, ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN (SELECT ad_id, product FROM ads_products anp) ap ON ai.ad_id = ap.ad_id
join (
select pi.name, round(sum(pi.value)) as monthly_income, DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
) jpi on jpi.name = ap.product and jpi.date = ai.date
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
ORDER BY min(ai.date) asc;
But this joined query produces no rows. Why?
The final query should produce this table:
| product | date | montly_cost | monthly_income |
|---------|---------|-------------|----------------|
| prod1 | 04-2021 | 11 | 38 |
| prod2 | 04-2021 | 17 | 16 |

Get the middle value from query build on two select subqueries

I have the following query:
select
qry1.product, qry1.date, qry2.monthly_income, qry1.monthly_cost,
round(qry2.monthly_income / qry1.monthly_cost) as x_ratio
from
(select
ap.product,
DATE_FORMAT(min(ai.date), '%m-%Y') as date,
ROUND(SUM(ai.ad_spend)) as monthly_cost
from
ad_insight ai
join
ads_products ap on ai.ad_id = ap.ad_id
group by
ap.product, YEAR(ai.date), MONTH(ai.date)) as qry1
inner join
(select
pi.name,
round(sum(pi.value)) as monthly_income,
DATE_FORMAT(min(pi.date), "%m-%Y") as date
from
product_insight pi
where
pi.name in (select distinct anp.product from ads_products anp)
group by
pi.name, YEAR(pi.date), MONTH(pi.date)) as qry2 on qry1.product = qry2.name and qry1.date = qry2.date;
I want to find the middle value of x_ratio(median of x_ratio) in the upper query.
Up till now, I have done something like this:
select qry1.product, round(avg(qry2.monthly_income/qry1.monthly_cost)) as x_ratio, count(qry1.date) as occurrences
from (
SELECT ap.product,
DATE_FORMAT(min(ai.date), '%m-%Y') as date,
ROUND(SUM(ai.ad_spend)) as monthly_cost
FROM ad_insight ai
JOIN ads_products ap ON ai.ad_id = ap.ad_id
GROUP BY ap.product, YEAR(ai.date), MONTH(ai.date)
) as qry1
inner join (
select pi.name,
round(sum(pi.value)) as monthly_income,
DATE_FORMAT(min(pi.date), "%m-%Y") as date
from product_insight pi
where pi.name in (select distinct anp.product from ads_products anp)
group by pi.name, YEAR(pi.date), MONTH(pi.date)
) as qry2 on qry1.product=qry2.name and qry1.date=qry2.date
group by qry1.product
order by x_ratio asc, occurrences desc;
As you can see I have used avg function, but in my case is not helpful, because x_ratio has a very wide spectrum of values.
Here you have fiddle
I want to result table look like this:
+---------+----------------+-------------+
| product | middle_x_ratio | occurrences |
+---------+----------------+-------------+
| prod2 | 1 | 1 |
| prod1 | 4 | 5 |
+---------+----------------+-------------+

Mysql query select customers with no orders in each year

i have 2 table's one with customers and one with orders
SELECT customers.customer_name, orders.order_date
FROM customers
Left
Join orders on (customers.customer_id = orders.customer_id)
WHERE not orders.customer_id IN (SELECT customer_id from orders where Year(order_date) = Year(#Parameter1))
and not orders.order_date is null
this works but i want to do this for each year to get somthing like this as result
|Year | customer_id |
|2010 | 1 |
|2010 | 2 |
|2011 | 2 |
|2011 | 3 |
|2012 | 1 |
You want a list showing customers and years that are not present in the orders table. So get a list of all customers combined with all years and then subtract the customers and years that you find in the orders table:
select o.yr, c.customer_id
from customers c
cross join (select distinct year(order_date) as yr from orders) o
where (c.customer_id, o.yr) not in (select customer_id, year(order_date) from orders);
Scorpio is sort of right, you do have to use the year function:
SELECT Year(orders.order_date), customers.customer_id
FROM customers
LEFT JOIN orders
ON (customers.customer_id = orders.customer_id)
WHERE NOT orders.customer_id
IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
AND
NOT orders.order_date is NULL
You can use the year method in the SELECT section of your query
Update:
To display all the customers who didn't order in a year, you don't need the join. You can do it with a single subselect:
SELECT #Parameter1 AS Year, customer_id
FROM customers
WHERE customers.customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)

Mysql left join is not working as expected

3 tables.
table_customers - customer_id, name
table_orders - order_id, customer_id, order_datetime
table_wallet - customer_id, amount, type // type 1- credit, type 2- debit
Need to get all customers, their total balance, and their last order date and order id. If customer have not placed any return order date as 0000-00-00 and order id as 0.
This is my query.
SELECT
C.customer_id,
C.name,
COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
COALESCE( max( O.order_id ) , '0' ) AS last_order_id,
COALESCE( max( date( O.order_datetime ) ) , '0000-00-00' ) AS last_order_date
FROM
table_customers as C
LEFT JOIN
table_wallet as W
ON C.customer_id = W.customer_id
LEFT JOIN
table_orders AS O
ON W.customer_id = O.customer_id
group by C.customer_id
ORDER BY C.customer_id
Everything is coming correct except customer's value. From result it seems its getting added multiple times.
I have created the fiddle here. http://sqlfiddle.com/#!9/560f2/1
What is wrong in query? Can anyone help me on this?
Edit: Expected result
customer_id name value last_order_id last_order_date
1 abc 20 3 2016-06-22
2 def 112.55 0 0000-00-00
3 pqrs 0 4 2016-06-15
4 wxyz 0 0 0000-00-00
The issue is that the join between orders and wallet will produce as many rows as there as orders for each wallet, when you really just want one row per wallet from the order table (since you only use the max values). In your test case you get 3 rows for customer 1 which makes the sum 60 (3*20).
One way to solve this is to change to this:
SELECT
C.customer_id,
C.name,
COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
COALESCE( O.order_id , '0' ) AS last_order_id,
COALESCE( DATE( O.order_datetime ) , '0000-00-00' ) AS last_order_date
FROM table_customers AS C
LEFT JOIN table_wallet AS W ON C.customer_id = W.customer_id
LEFT JOIN (
SELECT
customer_id,
MAX(order_id) AS order_id,
MAX(order_datetime) AS order_datetime
FROM table_orders
GROUP BY customer_id
) AS O ON c.customer_id = O.customer_id
GROUP BY C.customer_id
ORDER BY C.customer_id
As you see the orders table is replaced by a derived table that gets you one row per customer.
Running the query above gets you the following result:
| customer_id | name | value | last_order_id | last_order_date |
|-------------|------|--------|---------------|-----------------|
| 1 | abc | 20 | 3 | 2016-06-22 |
| 2 | def | 112.55 | 0 | 0000-00-00 |
| 3 | pqrs | 0 | 4 | 2016-06-15 |
| 4 | wxyz | 0 | 0 | 0000-00-00 |
To further illustrate from the previous answers, if we simply remove your group by statement, you can easily see why you are double counting. The following code:
SELECT
C.*,
O.order_id, O.order_datetime,
W.amount, W.type
FROM
table_customers as C
LEFT JOIN
table_wallet as W
ON C.customer_id = W.customer_id
LEFT JOIN
table_orders AS O
ON W.customer_id = O.customer_id
Will yield the result:
customer_id name order_id order_datetime amount type
1 abc 1 April, 22 2016 23:53:09 20 1
1 abc 2 May, 22 2016 23:53:09 20 1
1 abc 3 June, 22 2016 23:53:09 20 1
2 def (null) (null) 100 1
2 def (null) (null) 12.55 1
3 pqrs (null) (null) (null) (null)
4 wxyz (null) (null) (null) (null)
Note the duplication of Customer ID 1 with amount 20.
This is the classic combinatorial explosion problem when you JOIN tables containing unrelated data.
You need to compute each customer's balance in a subquery. That subquery must yield either one row or zero rows per customer_id. It might look like this. (http://sqlfiddle.com/#!9/560f2/8/0)
SELECT customer_id,
SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
FROM table_wallet
GROUP BY customer_id
Similarly, you need to retrieve each customer's latest order in a subquery (http://sqlfiddle.com/#!9/560f2/10/0) . Again, it needs either one row or zero rows per customer_id.
SELECT customer_id,
MAX(order_id) AS order_id,
DATE(MAX(order_datetime)) AS order_date
FROM table_orders
GROUP BY customer_id
Then, you can LEFT JOIN those two subqueries as if they were tables, to your table_customers. The subqueries are tables; they're virtual tables. (http://sqlfiddle.com/#!9/560f2/12/0)
SELECT c.customer_id,
c.name,
w.value,
o.order_id,
o.order_date
FROM table_customers c
LEFT JOIN (
SELECT customer_id,
SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
FROM table_wallet
GROUP BY customer_id
) w ON c.customer_id = w.customer_id
LEFT JOIN (
SELECT customer_id,
MAX(order_id) AS order_id,
DATE(MAX(order_datetime)) AS order_date
FROM table_orders
GROUP BY customer_id
) o ON c.customer_id = o.customer_id
Your mistake was this: you joined two tables each with multiple rows for each customer id. For example, a particular customer might have had two orders and three wallet rows. Then, the join results in six rows representing all the possible combinations of wallet and order rows. That's called combinatorial explosion.
The solution I outlined makes sure there's only one row (or maybe no rows) to join for each customer_id, and so eliminates the combinatorial explosion.
Pro tip: Using subqueries like this makes it easy to test your query: you can test each subquery separately.

Complicated sum() with group and order by date

There are two tables:
* ORDER
- id
- pay_type
* ORDER_PRICE
- order_id
- dt
- price
Order price can be changed, for example:
order_id | price | dt
1 | 100.3 | 2013-10-25
1 | 105.7 | 2013-10-28
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
I can select price history for any date like that:
SELECT
o.`id`,
(SELECT op.`price` FROM `order_price` op
WHERE op.`order_id`=o.`id` AND op.`dt` <= '2013-10-26'
ORDER BY op.`dt` DESC LIMIT 1) order_price
FROM `order` o
It gives me right prices for given date
order_id | price | dt
1 | 100.3 | 2013-10-25
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
But i need the sum of the second column (no matter what order number, only one number - 405.7 in this case).
Is there a solution for such a situation? There can be thousands of orders, so i think it will be wrong to sum records outside the mysql. Maybe it's the wrong all the way from the start and i need other structure? Thank you for your time and help.
I suspect the core query should look more like this...
SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt;
...which can be rewritten this way, to give totals
SELECT o.order_id
, SUM(op.price) price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
GROUP
BY order_id,dt
WITH ROLLUP;
...or this way
SELECT SUM(price) total
FROM
( SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
) z;
...or just pull the total at the application level.