Partial sum and subtraction (minus) in UNION Query - mysql

I have two similar tables (one for bills, the other payments) right now I show to users a union mixing data from both..
Table Bills
CustomerId Amount
1 100
2 100
1 100
2 100
Table Payments
CustomerId Amount
1 100
2 100
1 100
Right now my users can see the following informtation
From Customer 1
Type CustomerId Amount
B 1 100
P 1 -100
B 1 100
P 1 -100
TOTAL 0
From Customer 2
Type CustomerId Amount
B 1 100
P 1 -100
B 1 100
Total 100
Everything works fine using UNION Statement
Right now I need to show Partial Balance on each record so users can keep in mind balanca while they are looking at records..
Like this... (DESIRED)
From Customer 1
Type CustomerId Amount Partial
B 1 100 100
P 1 -100 0
B 1 100 100
P 1 -100 0
I've alredy try using Variables like #Partial := #Partial + Amount but it seems that it first sum the first part (bills) and then the rest (payments)... like this...
From Customer 1
Type CustomerId Amount Partial
B 1 100 100
P 1 -100 200
B 1 100 200
P 1 -100 100
it seems that first sum everything from bills and then start subtraction... anyone knows how to solve it?
****** // update // ********
here original query ...
(SELECT 'Bill' as cType , b.type, b.tal , 'Customer', b.number , b.date , b.subtot, b.tax, IF(b.type='CA' or b.type='CB' or b.type='CC' or b.type='CX',b.total*-1,b.total) as total FROM bills b WHERE b.idcustomer='000140') UNION ALL
(SELECT 'Payment' as cType, 'CO' , '1' , '' , c.idcash , c.date , 0 ,0 , -c.amount FROM cash c WHERE c.idcustomer='000140' and ( c.type='CO' or c.type='DM') ) order by date asc;
this brings something like this
Bill FX 1 Customer 9 2011-02-25 0.00 0.00 100.00
Payment CO 1 37 2011-03-04 0.00 0.00 -100.00
Bill FX 1 Customer 616 2011-03-23 0.00 0.00 100.00
Payment CO 1 751 2011-04-12 0.00 0.00 -100.00
Bill FX 1 Customer 1267 2011-04-27 0.00 0.00 100.00
Payment CO 1 1157 2011-05-10 0.00 0.00 -100.00
Bill FX 1 Customer 1974 2011-05-26 0.00 0.00 100.00
Payment CO 1 1654 2011-06-08 0.00 0.00 -100.00
then When I try to sum patiars...using the following code
set #running_total=0;
(SELECT 'Bill' as cType , b.type, b.tal , 'Customer', b.number , b.date , b.subtot, b.tax, IF(b.type='CA' or b.type='CB' or b.type='CC' or b.type='CX',b.total*-1,b.total) as total, ( #running_total := #running_total + total) AS RunningTotal FROM bills b WHERE b.idcustomer='000140') UNION ALL
(SELECT 'Payment' as cType, 'CO' , '1' , '' , c.idcash , c.date , 0 ,0 , -c.amount, ( #running_total := #running_total-c.amount) AS RunningTotal FROM cash c WHERE c.idcustomer='000140' and ( c.type='CO' or c.type='DM') ) order by date asc;
results...
Bill FX 1 Customer 9 2011-02-25 0.00 0.00 100.00 100.00
Payment CO 1 37 2011-03-04 0.00 0.00 -100.00 1905.00
Bill FX 1 Customer 616 2011-03-23 0.00 0.00 100.00 200.00
Payment CO 1 751 2011-04-12 0.00 0.00 -100.00 1805.00
Bill FX 1 Customer 1267 2011-04-27 0.00 0.00 100.00 300.00
Payment CO 1 1157 2011-05-10 0.00 0.00 -100.00 1705.00
As you Can See seems to sum first all from bills and then start substraccion from payments...

The following is the only way I can think of doing this in MySQL, assuming that you have a time stamp for ordering the records. If you have even a moderate amount of data, this may prove to be too inefficient:
select t.*,
((select sum(amount)
from bills b
where b.customerId = t.customerId and
b.datetime <= t.datetime
) -
(select sum(amount)
from payments p
where p.customerid = t.customerid and
p.datetime <= t.datetime
)
) as balance
from ((select 'B' as type, customerid, amount, datetime from bills b) union all
(select 'P', customerid, amount, datetime from payments p)
) t

Related

MySQL Join AND SUM on Both Tables

I'm trying to join two tables and sum the results from both tables columns but I can't figure out. I'm doing it in Joomla.
invoices table:
id
owner
amount
1
123
300.00
2
123
125.00
3
144
200.00
4
166
155.00
expenses table:
id
owner
amount
1
123
10.00
2
123
50.00
3
144
50.00
results:
owner
invoices
expenses
123
425.00
60.00
144
200.00
50.00
166
155.00
0.00
This should give you the expect results. I do LEFT JOIN for results from both tables.
SELECT l.`owner`, invoices, IFNULL(expenses,0) FROM
(
SELECT
`owner`, SUM(amount) AS invoices
FROM
invoices
GROUP BY
`owner`
) AS l LEFT JOIN (
SELECT
`owner`, SUM(amount) AS expenses
FROM
expenses
GROUP BY
`owner`
) AS r ON l.`owner` = r.`owner`

Running total in two tables

Consider two tables: invoices and payments. The invoices table contains records of invoices raised, and the payments table contains records of payments received.
invoices
id
date
cname
amount
1
2021-12-12
cname1
10000
2
2021-12-13
cname2
5000
3
2022-01-15
cname1
7000
4
2022-01-16
cname2
1000
payments
id
date
cname
amount
1
2022-01-05
cname1
5000
2
2022-01-07
cname2
5000
3
2022-02-05
cname1
10000
4
2022-02-06
cname2
1000
CALCULATE RUNNING BALANCE
Q) Extend the SQL query to do invoice / payment matching as follows (as of 28/2/2022)
matching
date
document_id
cname
amount
due
2021-12-12 00:00:00
1
cname1
10000
10000
2022-01-05 00:00:00
1
cname1
-5000
5000
2022-01-15 00:00:00
3
cname1
7000
12000
2022-02-05 00:00:00
3
cname1
-10000
2000
2021-12-13 00:00:00
2
cname2
5000
5000
2022-01-07 00:00:00
2
cname2
-5000
0
2022-01-16 00:00:00
4
cname2
1000
1000
2022-02-06 00:00:00
4
cname2
-1000
0
You can union both tables considering the second one with negative amount, and then a simple running total will produce the result you want. For example:
select
date,
id as document_id,
cname,
amount,
sum(amount) over(partition by id order by date) as due
from (
select * from invoices
union all select id, date cname, -amount from payments
) x
order by cname, date
SELECT `date`,
documentId,
cname,amount,
due FROM (SELECT `date`,
documentId,
cname,
amount,
(CASE WHEN #running_customer='' THEN #running_balance:=amount
WHEN #running_customer=cname THEN #running_balance:=#running_balance+amount ELSE #running_balance:=amount END) due,
#running_customer:=cname
FROM (SELECT `date`, id AS documentId,cname, amount FROM `invoices`i
UNION ALL
SELECT `date`, id AS documentId,cname, amount*-1 AS actionType FROM `payments` p) final
JOIN (SELECT #running_customer:='') rc
JOIN (SELECT #running_balance:=0) rb
ORDER BY cname, `date`) finalResult
You need to be using assignment operator for these kind of problems.

How to get average starting third previous record to be included to current row as column?

I have some data like below:
id
date
code
price
892
2022-02-04
B
472
891
2022-02-03
B
58
890
2022-02-02
B
467
868
2022-01-28
B
50
821
2022-01-23
B
45
780
2022-01-20
B
55
550
2022-01-14
B
79
245
2022-01-12
B
841
112
2022-01-11
B
128
91
2022-01-07
B
174
74
2022-01-04
B
64
I want to get the average price of three records, starting from previous third row to be included to current row in one SQL query, so I'm expecting like below:
id
date
code
price
avg3th
892
2022-02-04
B
472
average of ( 467+50+45)
891
2022-02-03
B
58
average of ( 50+45+55)
890
2022-02-02
B
467
average of ( 50+45+79)
868
2022-01-28
B
50
average of ( 45+79+841)
821
2022-01-23
B
45
average of ( 79+841+128)
780
2022-01-20
B
55
average of ( 841+128+174)
550
2022-01-14
B
79
...
245
2022-01-12
B
841
...
112
2022-01-11
B
128
...
91
2022-01-07
B
174
...
74
2022-01-04
B
64
...
What I have tried, using below query:
SELECT t.id, t.date, t.code, t.price,
format(
CASE WHEN
ROW_NUMBER() OVER (ORDER BY t.date) >=5 THEN
AVG ( t.price ) OVER (ORDER BY t.date
ROWS BETWEEN 4 PRECEDING AND 2 FOLLOWING)
ELSE NULL
END,0) AS average_3th
FROM test t ORDER BY t.id DESC
However, above the query still includes the first element as average (it should begin at third element), so the below result still wrong:
Any idea?
Using the row number idea average in a sub query
WITH CTE AS
(SELECT T.*,ROW_NUMBER() OVER (PARTITION BY CODE ORDER BY ID DESC) RN FROM T ),
CTE1 AS
(SELECT T.*,ROW_NUMBER() OVER (PARTITION BY CODE ORDER BY ID DESC) RN FROM T )
SELECT CTE.*,
(SELECT AVG(CTE1.PRICE) FROM CTE1 WHERE CTE1.CODE = CTE.CODE AND CTE1.RN BETWEEN CTE.RN + 2 AND CTE.RN + 4),
(SELECT GROUP_CONCAT(CTE1.ID) FROM CTE1 WHERE CTE1.CODE = CTE.CODE AND CTE1.RN BETWEEN CTE.RN + 2 AND CTE.RN + 4)
FROM CTE
ORDER BY CTE.ID DESC
;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=437342579b3165f31292e288e3165bed

Conditional Window Functions

I have a sales table that looks like this:
store_id cust_id txn_id txn_date amt industry
200 1 1 20180101 21.01 1000
200 2 2 20200102 20.01 1000
200 2 3 20200103 19 1000
200 3 4 20180103 19 1000
200 4 5 20200103 21.01 1000
300 2 6 20200104 1.39 2000
300 1 7 20200105 12.24 2000
300 1 8 20200105 25.02 2000
400 2 9 20180106 103.1 1000
400 2 10 20200107 21.3 1000
Here's the code to generate this sample table:
CREATE TABLE sales(
store_id INT,
cust_id INT,
txn_id INT,
txn_date bigint,
amt float,
industry INT);
INSERT INTO sales VALUES(200,1,1,20180101,21.01,1000);
INSERT INTO sales VALUES(200,2,2,20200102,20.01,1000);
INSERT INTO sales VALUES(200,2,3,20200103,19.00,1000);
INSERT INTO sales VALUES(200,3,4,20180103,19.00,1000);
INSERT INTO sales VALUES(200,4,5,20200103,21.01,1000);
INSERT INTO sales VALUES(300,2,6,20200104,1.39,2000);
INSERT INTO sales VALUES(300,1,7,20200105,12.24,2000);
INSERT INTO sales VALUES(300,1,8,20200105,25.02,2000);
INSERT INTO sales VALUES(400,2,9,20180106,103.1,1000);
INSERT INTO sales VALUES(400,2,10,20200107,21.3,1000);
What I would like to do is create a new table, results that answers the question: what percentage of my VIP customers have, since January 3rd 2020, shopped i) at my store only; ii) at my store and at other stores in the same industry; iii) at only other stores in the same industry? Define a VIP customer to be someone who has shopped at a given store at least once since 2019.
Here's the target output table:
store industry pct_my_store_only pct_both pct_other_stores_only
200 1000 0.5 0.5 0.0
300 2000 0.5 0.5 0.0
400 1000 0.0 1.0 0.0
I'm trying to use window functions to accomplish this. Here's what I have so far:
CREATE TABLE results as
SELECT s.store_id, s.industry,
COUNT(DISTINCT (CASE WHEN s.txn_date>=20200103 THEN s.cust_id END)) * 1.0 / sum(count(DISTINCT (CASE WHEN s.txn_date>=20200103 THEN s.cust_id END))) OVER (PARTITION BY s.industry) AS pct_my_store_only
...AS pct_both
...AS pct_other_stores_only
FROM sales s
WHERE sales.txn_date>=20190101
GROUP BY s.store_id, s.industry;
The above does not seem to be correct; how can I correct this?
Join the distinct store_ids and industries to the concatenated distinct store_ids and industries for each customer and then use window function avg() with the function find_in_set() to determine if a customer how many customer have shopped or not from each store:
with
stores as (
select distinct store_id, industry
from sales
where txn_date >= 20190103
),
customers as (
select cust_id,
group_concat(distinct store_id) stores,
group_concat(distinct industry) industries
from sales
where txn_date >= 20190103
group by cust_id
),
cte as (
select *,
avg(concat(s.store_id) = concat(c.stores)) over (partition by s.store_id, s.industry) pct_my_store_only,
avg(find_in_set(s.store_id, c.stores) = 0) over (partition by s.industry) pct_other_stores_only
from stores s inner join customers c
on find_in_set(s.industry, c.industries) and find_in_set(s.store_id, c.stores)
)
select distinct store_id, industry,
pct_my_store_only,
1 - pct_my_store_only - pct_other_stores_only pct_both,
pct_other_stores_only
from cte
order by store_id, industry
See the demo.
Results:
> store_id | industry | pct_my_store_only | pct_both | pct_other_stores_only
> -------: | -------: | ----------------: | -------: | --------------------:
> 200 | 1000 | 0.5000 | 0.5000 | 0.0000
> 300 | 2000 | 0.5000 | 0.5000 | 0.0000
> 400 | 1000 | 0.0000 | 1.0000 | 0.0000

mysql group number used more than once and add total cost

I need help , I have date ,number ,duration and cost
i want to see a result where a number has been dialed more than once and total cost added together
date number duration cost
17/11/2014 10:32:19 am 0800 00:00:00 0.00
17/11/2014 11:06:47 am 071991 00:02:42 3.54
13/11/2014 02:47:40 pm 060302874 00:00:00 0.00
6/11/2014 11:53:28 am 0100601555 00:00:47 0.50
24/11/2014 12:06:27 pm 0113151407 00:00:46 0.50
19/11/2014 08:37:34 am 0113941905 00:00:47 0.50
24/11/2014 02:48:43 pm 0113941905 00:00:29 0.50
19/11/2014 08:39:16 am 0113949182 00:01:03 0.50
24/11/2014 02:41:57 pm 0113949182 00:00:36 0.50
24/11/2014 12:08:09 pm 0113949182 00:00:43 0.50
24/11/2014 02:50:29 pm 0114922660 00:03:26 1.72
A simple group by number should do it
select number,
sum(cost) total_cost,
count(*) times_dialed,
min(date) earliest_date,
max(date) latest_date
from mytable
group by number
having count(*) > 1
SELECT number
, SUM(cost) total
FROM my_table
GROUP
BY number
HAVING COUNT(*) > 1;
+------------+-------+
| number | total |
+------------+-------+
| 0113941905 | 1.00 |
| 0113949182 | 1.50 |
+------------+-------+
Here you go:
SELECT
number, sum(cost)
FROM myTable as a,
(SELECT
number, count(*) as dials
FROM myTable
GROUP BY 1) as b
WHERE a.number = b.number
and b.dials > 1
GROUP BY 1