MYSQL calculate amount from two table? - mysql

I have 2 mysql tables:
"Orders" table:
customer_id | money
3 120
5 80
3 45
3 70
6 20
"collecting" table:
customer_id | money
3 50
3 70
4 20
4 90
I want a result like:
"Total" table:
customer_id | Amount
3 115
4 110
5 80
6 20
"Total" table "customer_id" should be singular
Amount = (SUM(All customer orders.money) - SUM(All customer collecting.money))
"Money" can be NULL
"Orders" table can have customer_id and "Collecting" table may not have
Or
"Collecting" table can have customer_id and "Orders" table may not have
How can i write a single query for output "Total" table?

The following returns the result you expect.
SELECT
customer_id,
SUM(amount) as amount
FROM (
SELECT customer_id, SUM(money) as amount
FROM orders GROUP BY customer_id
UNION ALL
SELECT customer_id, SUM(money) * -1 as amount
FROM collecting GROUP BY customer_id
) as tb
GROUP BY customer_id;
customer_id = 4 returns -110, not 110, since it's only in the collecting table.
Example: http://www.sqlfiddle.com/#!2/3b922/5/0

The fastest way is to union your data with the money value being negative on the collecting table:
-- load test data
create table orders(customer_id int, money int);
insert into orders values
(3,120),
(5,80),
(3,45),
(3,70),
(6,20);
create table collecting(customer_id int,money int);
insert into collecting values
(3,50),
(3,70),
(4,20),
(4,90);
-- populate Total table
create table Total(customer_id int,Amount int);
insert into Total
select oc.customer_id,sum(oc.money) Amount
from (
select customer_id,coalesce(money,0) money from orders
union all
select customer_id,coalesce(-money,0) money from collecting
) oc
group by oc.customer_id;
-- return results
select * from Total;
SQL Fiddle: http://www.sqlfiddle.com/#!2/deebc

You need to do this by pre-aggregating the data. If I assume that the orders come first, you can use left outer join:
select o.customer_id, (o.money - coalesce(c.money)) as Amount
from (select o.customer_id, sum(o.money) as money
from orders o
group by o.customer_id
) o left outer join
(select c.customer_id, sum(c.money) as money
from collecting c
group by c.customer_id
) c
on o.customer_id = c.customer_id;

Something like this should work:
SELECT CASE
WHEN c.customer_id IS NULL
THEN o.customer_id
ELSE c.customer_id
END
,sum(o.MONEY) - sum(c.MONEY)
FROM orders o
OUTER JOIN collecting c ON c.customer_id = o.customer_id
GROUP BY 1

Try this:
SELECT customer_id, SUM(o_money - c_money) AS Amount
FROM (
SELECT customer_id, money AS o_money, 0 AS c_money FROM orders
UNION ALL
SELECT customer_id, 0 AS o_money, money AS c_money FROM collecting
) total
GROUP BY customer_id;
According to your description, you need a FULL [OUTER] JOIN, which is a combination of LEFT JOIN and RIGHT JOIN. Many databases don't support FULL JOIN, so you need to use UNION ALL to reach the same effect.

Related

How to calculate moving average DAYS of a date column

I have these columns and data
Sample data:
CREATE TABLE Orders(customerid int, orderdate datetime, orderqty int);
INSERT into Orders(customerid, orderdate, orderqty) VALUES
(1,'2020-11-25',100),(1,'2020-11-27',160),(2,'2020-12-05',3490),
(1,'2020-11-29',293),(2,'2020-12-07',293),(1,'2020-12-01',382);
sqlfiddle: http://sqlfiddle.com/#!9/d90aaf/1/0
From this data, I want to find out the date difference in last 3 rows by each customer.
an example output would be:
customerid, last 3 orders average days between orders,sum of orderqty for last 3 order
1, 2,835
2, 2,3783
I have tried datediff() but I can't achieve this result by that function.
Can't figure out better trick for latest 3 orders. This works, but probably with not good performance with large tables:
SELECT o3.customerid,DATEDIFF(Latest1,IFNULL(Latest3,Latest2))/IF(Latest3 IS NULL,1,2) AS avgDiff,SUM(oQ.orderqty) AS qty3orders FROM
(SELECT o2.*,MAX(ooo.orderdate) AS Latest3 FROM
(SELECT o1.*,MAX(oo.orderdate) AS Latest2 FROM
(SELECT customerid,MAX(orderdate) AS Latest1 FROM Orders GROUP BY customerid) o1
JOIN Orders oo ON o1.customerid=oo.customerid AND oo.orderdate<o1.Latest1 GROUP BY o1.customerid) o2
LEFT JOIN Orders ooo ON o2.customerid=ooo.customerid AND ooo.orderdate<o2.Latest2 GROUP BY o2.customerid) o3
JOIN orders oQ ON o3.customerid=oQ.customerid AND oQ.orderdate>=COALESCE(o3.Latest3,o3.Latest2,o3.Latest1) GROUP BY o3.customerid

Customer partitioning in sql query

I have a table with following format -
Customer_id Purchase_date
c1 2015-01-11
c2 2015-02-12
c3 2015-11-12
c1 2016-01-01
c2 2016-12-29
c4 2016-11-28
c4 2015-03-15
... ...
The table essentially contains customer_id with their purchase_date. The customer_id is repetitive based on the purchase made on purchase_date. The above is just a sample data and the table contains about 100,000 records.
Is there a way to partition the customer based on pre-defined category data
Category Partitioning
- Category-1: Customer who has not made purchase in last 10 weeks, but made a purchase before that
- Category-2: Customer who as not made a purchase in last 5 weeks, but made purchase before that
- Category-3: Customer who has made one or more purchase in last 4 weeks or it has been 8 weeks since the first purchase
- Category-4: Customer who has made only one purchase in the last 1 week
- Category-5: Customer who has made only one purchase
What I'm looking for is a query that tells customer and their category -
Customer_id Category
C1 Category-1
... ...
The query can adhere to - oracle, postgres, sqlserver
From your question it seems that a customer can fall in multiple categories. So lets find out the customers in each category and then take UNION of the results.
SELECT DISTINCT Customer_Id, 'CATEGORY-1' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 10
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-2' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) > 5
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-3' AS Category FROM mytable GROUP BY
Customer_Id HAVING DATEDIFF(ww,MAX(Purchase_date),GETDATE()) < 4 OR
DATEDIFF(ww,MIN(Purchase_date),GETDATE()) =8
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-4' AS Category FROM mytable WHERE
DATEDIFF(ww,Purchase_date,GETDATE())<=1 GROUP BY Customer_Id having
COUNT(*) =1
UNION
SELECT DISTINCT Customer_Id, 'CATEGORY-5' AS Category FROM mytable GROUP BY
Customer_Id HAVING COUNT(*) =1
ORDER BY Category
Hope this serves your purpose.
Thanks
you can use something like this
with myTab as (
SELECT Customer_id ,MIN(Purchase_date) AS Min_Purchase_date,MAX(Purchase_date) AS Max_Purchase_date
, SUM(CASE WHEN Purchase_date>= DATEADD(WEEk ,-1,GETDATE()) THEN 1 ELSE 0 END ) AS Count_LastWeek
, COUNT(*) AS Count_All
FROM Purchases_Table
GROUP BY Customer_id
)
SELECT Customer_id
, CASE WHEN Max_Purchase_date < DATEADD(WEEK,-10,GETDATE()) THEN 'Category-1'
WHEN Max_Purchase_date < DATEADD(WEEK,-5,GETDATE()) THEN 'Category-2'
WHEN Max_Purchase_date >= DATEADD(WEEK,-4,GETDATE())
OR DATEDIFF(WEEK, Min_Purchase_date,Max_Purchase_date) >= 8 THEN 'Category-3'
WHEN Count_LastWeek = 1 THEN 'Category-4'
WHEN Count_All = 1 THEN 'Category-5'
ELSE 'No Category'
END
FROM myTab

How do optimise sql query using join between multiple tables

i have two tables having following structure
Table A
itemId categoryId orderDate
==========================================
1 23 2016-11-08
1 23 2016-11-12
1 23 2016-11-16
Table B have the structure
categoryId stock price
==========================================
23 500 600
However mine desired output should be as like
Result C
price stock orderdate qty
600 500 2016-11-08 (first order date) 3 (3 time appearance in first table)
Here is what i have tried so far
select b.price,b.stock from B b, A a
where b.categoryId = (
select a.categoryId
from A
GROUP BY categoryId
HAVING COUNT(categoryId)>1
)
and (a.orderdate = (
select MIN(orderdate)
from A
where categoryId = b.categoryId)
)
i have following result
price stock orderdate
600 500 2016-11-08
i have no idea how do find qty as it is appeared 3 times in first table.
I think you want the records in table a grouped by item id and category id, so include these two in your group by statement. Then the other columns you have to aggregate using MIN, MAX, AVG, SUM, etc. I use MIN which will give you the smallest number in the group for that particular column, although it shouldn't matter in this case whether you use MIN or MAX or AVG - it's all the same. Then COUNT(*) will just count the number of recrods in the group.
Also, joins are generally preferred over listing tables with commas.
SELECT a.itemid, a.categoryid, MIN(b.price), MIN(b.stock), min(a.orderdate), count(*) as qty
FROM a
INNER JOIN b ON a.categoryid = b.categoryid
GROUP BY a.itemid, a.categoryid
You also need to select COUNT(*)
how about use following sql
select min(price), min(stock), min(orderDate), COUNT(categoryId)
from A,B where A.categoryId = B.categoryId
GROUP by A.categoryId
You could create views for your subqueries and give them meaningful names e.g. CategoriesUsedInMultipleOrders, MostRecentOrderByCategory. This would 'optimize' you query by abstracting away complexity and making it easier for the human reader to understand.
This is the Query with the appropriate join method see Result:
SELECT B.price, B.stock, MIN( A.orderDate ) AS orderdate, COUNT( * ) AS qty
FROM TableA A, TableB B
WHERE A.categoryId = B.CategoryId
GROUP BY A.categoryId, B.price, B.stock

Find the number (count) and price of new and repeat orders date wise

The schema of my orders table looks something like this:
Orders(Order_ID varchar, Order_NO int, User_ID varchar, Price float, Payment_Status int, Paid_At timestamp)
Consider Payment Status as 1 when payment is complete and zero when its not.
Expected Result:
Date New Orders Repeat Orders Total of New Total of Repeat
2015-09-01 4 9 500 1600
2015-09-02 3 5 311 222
I tried this query for new orders but it gives only orders from the customers who have placed only one order
SELECT date(paid_at) , COUNT(order_no) AS Total_Orders , SUM(price) AS daily_total FROM
(SELECT * FROM jobs AS D WHERE user_id IN
( SELECT user_id FROM(
(SELECT user_id,count(order_no) FROM Orders AS B WHERE user_id IN
(SELECT user_id FROM Orders AS A WHERE payment_status = '1'
) GROUP BY user_id HAVING COUNT(order_no) = 1
) AS C
)
)
)
AS E GROUP BY date(paid_at)
As you might have already observed I am a novice to SQL and a little help here will help me improve.

How to get rows with max date when grouping in MySQL?

I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;