Complicated sum() with group and order by date - mysql

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.

Related

Get last update time in a status history table for a given status

I've this table
order_status_history, I insert a row everytime an order is updated, and let's say a table orders with only the column id:
id
order_id
order_status_id
updated_at
1
121
ready
2021-12-30 10:00:00
2
121
shipped
2021-12-30 10:01:00
3
121
ready
2021-12-30 10:02:00
4
121
shipped
2021-12-30 10:03:00
5
121
delivered
2021-12-30 10:04:00
I need to get the latest updated_at value for all the orders (DISTINCT) that have been shipped as last status, or delivered if for any reason there is no shipped status .
I wrote this query, but I get the last updated_at value of the delivered status. In the example case I need the updated_at as 10:03, but I get the 10:04 one.
SELECT o.id,
osh.max_updated_at AS updatedAt
FROM orders o
JOIN ( SELECT order_id,
Max(updatedat) AS max_updated_at
FROM order_status_history
WHERE order_status_id IN('shipped','delivered')
GROUP BY order_id
) AS osh ON osh.order_id = o.id
AND ( SELECT order_status_id
FROM order_status_history
WHERE order_id = o.id
ORDER BY updatedat DESC
LIMIT 1
) IN('shipped','delivered')
Try this one with subquery
SELECT id, (
SELECT h.updated_at
FROM order_status_history h
WHERE h.order_id = o.id
AND h.order_status_id IN ('shipped','delivered')
AND NOT EXISTS (
SELECT 1
FROM order_status_history k
WHERE k.order_id = h.order_id
AND k.updated_at > h.updated_at
AND k.order_status_id NOT IN ('shipped','delivered')
)
ORDER BY CASE WHEN order_status_id = 'shipped' THEN 1 ELSE 2 END ASC, updated_at DESC
LIMIT 1
) AS updatedAt
FROM orders o
Subquery works if you select a single column
This is another option for MySQL8+
WITH ordered AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY order_id ORDER BY CASE WHEN order_status_id = 'shipped' THEN 1 ELSE 2 END ASC, updated_at DESC) rn
FROM order_status_history
WHERE order_status_id IN ('shipped','delivered')
)
SELECT o.id, h.*
FROM orders o
LEFT JOIN ordered h ON o.id = h.order_id
AND h.rn = 1 AND NOT EXISTS (
SELECT 1
FROM order_status_history k
WHERE k.order_id = h.order_id
AND k.updated_at > h.updated_at
AND k.order_status_id NOT IN ('shipped','delivered')
)

group by error : this is incompatible with sql_mode=only_full_group_by

i have two tables order and order_detail
order
id | total | date
------------------------
1 3500 2018-02-10
2 1000 2018-02-18
order_detail
id | order_id | item_id | quantity
-------------------------------------------------
1 1 4 20
2 1 6 10
3 2 3 50
i am trying to achive
orderCount | itemCount | totalAmount
----------------------------------------
2 80 4500
i have written this query for extracting last 30day summary:
select COUNT(*) as orderCount
, (select SUM(od.quantity)
from order_detail od
where od.order_id = o.id
) as itemCount
, SUM(o.total) as totalSum
from order o
WHERE DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= o.date
this query works fine in local mysql database
but in production server it is generating following error:
#1140 - In aggregated query without GROUP BY, expression #2 of SELECT list contains nonaggregated column 'databaseb.o.id'; this is incompatible with sql_mode=only_full_group_by
The 2nd column:
(select SUM(od.quantity)
from order_detail od
where od.order_id = o.id
) as itemCount
Is not an aggregated value; it's the result of a subquery (which happens to produce aggregated value, but that is irrelevant).
You must add:
group by 2
To your query to conform with sql_mode=only_full_group_by.
Obviously your local database does not have sql_mode=only_full_group_by set.
I suspect you just want a join:
select COUNT(*) as orderCount
, SUM(od.quantity) as itemCount
, SUM(o.total) as totalSum
from order o
left join order_detail od on od.order_id = o.id
WHERE DATE_SUB(CURDATE(),INTERVAL 30 DAY) <= o.date
To achieve your desire output you can simple use below query
select sum(od.quantity) as itemCount,
sum(o.total) as totalAmount,
count(distinct(o.id)) as orderCount
from order_detail od
left join `order` o
on od.id=o.id ;

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.

MySQL - SUM/COUNT Different Columns From Different Tables

I have a peculiar problem. Given three tables.
product
- id
- title
product_rating
- product_id
- rating
product_view
- product_id
I want to grab products with a SUM of their ratings (which will either be a +1 or -1 value) and their total product count.
SELECT
p.`id`, p.`title`,
SUM( pr.`rating` ) AS rating,
COUNT( pv.`product_id` ) AS views
FROM
`product` AS p
LEFT JOIN `product_rating` AS pr ON ( pr.`product_id` = p.`id` )
LEFT JOIN `product_view` AS pv ON ( pv.`product_id` = p.`id` )
GROUP BY
p.`id`
ORDER BY rating DESC
In the table I have 1 rating, 9 views. The query, however, is returning 9 rating, 9 views. I understand why it's happening (it's summing the rating for each product_view), but I don't know how to get around it.
Any suggestions would be greatly appreciated.
Sample data:
product
------------
id | title
1 | Globber
product_rating
-------------------
product_id | rating
1 | 1
product_view
------------
product_id
1
1
1
1
1
1
1
1
1
Try
SELECT p.id, p.title, r.rating, v.views
FROM product p LEFT JOIN
(
SELECT product_id, SUM(rating) rating
FROM product_rating
GROUP BY product_id
) r ON p.id = r.product_id LEFT JOIN
(
SELECT product_id, COUNT(*) views
FROM product_view
GROUP BY product_id
) v ON p.id = v.product_id
ORDER BY r.rating DESC
Sample output:
| ID | TITLE | RATING | VIEWS |
---------------------------------
| 1 | Globber | 1 | 9 |
Here is SQLFiddle demo
How to do that?
SELECT tbl.pid,tbl.ptitle, SUM(tbl.rating) as Rate, COUNT (tbl.views) as ViewList FROM (SELECT
p.`id` as pid, p.`title` as ptitle,
pr.`rating` AS rating,
pv.`product_id` AS views
FROM
`product` AS p
LEFT JOIN `product_rating` AS pr ON ( pr.`product_id` = p.`id` )
LEFT JOIN `product_view` AS pv ON ( pv.`product_id` = p.`id` ) ) as tbl
GROUP BY
tbl.`pid`
ORDER BY tbl.Rate DESC