MySQL multiple sums, using numbers from multiple tables - mysql

Let's say I have two tables, one storing accounts, one storing transactions :
id acct_name opening_bal
1 checking 1029.99
2 savings 2002.19
...
And
id date amount from_acct to_acct
...
99 2018-01-21 12.15 1 2
100 2018-01-21 9.99 4 1
101 2018-01-23 10.01 5 2
...
For example, row 99 in the transactions table is saying that 12.15 was transfered from checking to savings on 21 Jan 2018.
I would like to write a query that returns all accounts together with their balances on a given date (like today or 12 Oct 2018, etc), something like this :
acct balance
checking 1599.21
savings 2221.99
...
How would I write such a query?
Edit: Here's a solution, which is close enough to what I want (it just has an additional id column). You can replace CURDATE() with an arbitrary date to get the corresponding table of balances on that date.
SELECT id, acct_name, opening_bal+amt-amt2 as balance FROM accounts
INNER JOIN
(SELECT to_acct, sum(amount) AS amt
FROM transactions
WHERE date <= CURDATE()
GROUP BY to_acct) as T2
ON accounts.id=T2.to_acct
INNER JOIN
(SELECT from_acct, sum(amount) AS amt2
FROM transactions
WHERE date <= CURDATE()
GROUP BY from_acct) as T3
ON T2.to_acct = T3.from_acct
;

Something like this. If you'd provided more data, answer could be more precise. Table1 is your first table, Table2 is the second.
select acct_name as acct,
opening_bal - t1.put_money + t2.get_money as balance
from Table1
left join (select from_acct, ifnull(sum(amount),0) as put_money from Table2 group by from_acct) t1
on t1.from_acct = Table1.id
left join (select to_acct, ifnull(sum(amount),0) as get_money from Table2 group by to_acct) t2
on t2.to_acct = Table1.id;

Related

Access Join without Double Counting

I have two tables. One with amounts in quarterly increments, the other with amounts in annual increments. I want to join the two and sum FEES and COUNTS without double counting.
Table 1
CLM_NUM Year Quarter FEES
1234 2016 1 100
1234 2016 2 100
1234 2016 3 100
1234 2016 4 100
Table 2
CLM_NUM Year COUNT
1234 2016 10
Desired Result:
CLM_NUM Year FEES COUNT
1234 2016 400 10
However, my query causes the counts to be counted four times resulting in 40, not 10.
How do I resolve this?
SELECT Table1.CLM_NUM, Table1.YEAR, Sum(Table1.FEES) AS SumOfFEES,
Sum(Table2.COUNT) AS SumOfCOUNT
FROM Table1 INNER JOIN Table2 ON (Table1.CLM_NUM = Table2.CLM_NUM) AND (Table1.YEAR = Table2.YEAR)
GROUP BY Table1.CLM_NUM, Table1.YEAR;
You can use a sub-query. The SUM in the sub-query means it would work even if table 2 wasn't yearly (i.e. there were more rows per CLM_NUM/Year key).
SELECT
t1.clm_num,
t1.year,
sum(t1.fees),
(SELECT
sum(t2.count)
FROM
Table2 t2
WHERE t1.CLM_NUM = t2.CLM_NUM
AND t1.Year = t2.Year
GROUP BY
t2.CLM_NUM, t2.Year
) AS SumOfCount
FROM
Table1 t1
GROUP BY
t1.CLM_NUM, t1.Year
if access doesnt have max, use first
http://sqlfiddle.com/#!18/7ec1d/1
select t1.clm_num, t1.year,sum(t1.fees),max(t2.count)
from Table1 t1 inner join
Table2 t2 on t2.clm_num = t1.clm_num and (t1.YEAR = t2.YEAR)
GROUP BY t1.CLM_NUM, t1.YEAR

How can I join two unrelated mysql table and use group by date

I have 2 tables voucher and ledger the fields are
Ledger:
id total_sale cancel_amount date
1 3000 0 2018-01-20
2 3000 0 2018-01-29
3 5000 0 2018-01-30
4 10000 500 2018-01-30
5 2000 100 2018-01-31
6 2000 0 2018-01-31
voucher:
id expenditure date
1 500 2018-01-20
2 800 2018-01-30
3 1000 2018-01-30
4 200 2018-01-31
5 300 2018-01-31
I want a result like[ date between 2018-01-29 to 2018-01-31]
date total_sale total_expenditure
2018-01-29 3000 0
2018-01-30 15000 1800
2018-01-31 4000 500
Please someone help
For such requirements, always prefer a dimension table. Then you'll never get confused on joining two dissimilar tables witch no keys in common.
Tables:
create schema test;
create table date_dim(date date);
insert into date_dim values ('2018-01-20'),
('2018-01-21'),
('2018-01-22'),
('2018-01-23'),
('2018-01-24'),
('2018-01-25'),
('2018-01-26'),
('2018-01-27'),
('2018-01-28'),
('2018-01-29'),
('2018-01-30'),
('2018-01-31');
create table ledger(id int,total_sale int, cancel_amount int, date date);
insert into ledger values
(1,3000,0,'2018-01-20'),
(2,3000,0,'2018-01-29'),
(3,5000,0,'2018-01-30'),
(4,10000,500,'2018-01-30'),
(5,2000,100,'2018-01-31'),
(6,2000,0,'2018-01-31');
create table voucher(id int, expenditure int, date date);
insert into voucher values
(1,500,'2018-01-20'),
(2,800,'2018-01-30'),
(3,1000,'2018-01-30'),
(4,200,'2018-01-31'),
(5,300,'2018-01-31');
SQL Script (for solution):
select l.date,total_sale,
total_expenditure
from
(select d.date date,sum(total_sale)total_sale
from ledger l right join date_dim d on l.date=d.date
where d.date between '2018-01-29' and '2018-01-31'
group by 1)l
join
(select d.date date,sum(expenditure)total_expenditure
from voucher v right join date_dim d on v.date=d.date
where d.date between '2018-01-29' and '2018-01-31'
group by 1)v
on l.date=v.date
group by 1,2,3;
Resultset:
date total_sale total_expenditure
2018-01-29 3000 (null)
2018-01-30 15000 1800
2018-01-31 4000 500
Check the solution at SQL Fiddle
You're hoping to present three different aggregates as columns of your result set.
distinct dates
net sales (total less canceled) (I guess that's what you want.)
expenditures.
You need to create three subqueries and join them.
The dates: (http://sqlfiddle.com/#!9/666dcb/1/0)
SELECT DISTINCT DATE(date) date FROM Ledger
UNION
SELECT DISTINCT DATE(date) date FROM Voucher
The net sales: (http://sqlfiddle.com/#!9/666dcb/2/0)
SELECT DATE(date) date,
SUM(total_sale) - SUM(cancel_amount) total_sale
FROM Ledger
GROUP BY DATE(date)
The expenditures: (http://sqlfiddle.com/#!9/666dcb/3/0)
SELECT DATE(date) date,
SUM(expenditure) total_expenditures
FROM Voucher
GROUP BY DATE(date)
Then you need to join them together on date. (http://sqlfiddle.com/#!9/666dcb/5/0)
SELECT d.date, s.total_sale, e.total_expenditures
FROM (
SELECT DISTINCT DATE(date) date FROM Ledger
UNION
SELECT DISTINCT DATE(date) date FROM Voucher
) d
LEFT JOIN (
SELECT DATE(date) date,
SUM(total_sale) - SUM(cancel_amount) total_sale
FROM Ledger
GROUP BY DATE(date)
) s ON d.date = s.date
LEFT JOIN (
SELECT DATE(date) date,
SUM(expenditure) total_expenditures
FROM Voucher
GROUP BY DATE(date)
) e ON d.date = e.date
WHERE d.date >= somedate AND d.date <= anotherdate
ORDER BY d.date
Why do the first subquery--the one with the dates? It makes sure you get a row in your final result set for dates that don't have any sales or any expenditures.
Why do separate subqueries? Because you want to end up joining three virtual tables--three subqueries--that have either zero or one row per date. If you join tables with more than one row per date, you'll get combinatorial explosion of sales and expenditures, which will exaggerate your sums.
Why use DATE(date)? Because that will allow the date columns in your detail tables to contain date/time values in case you want that.
select l.dates,l.total_sale,
v.total_expenditure
from
(select l.dates dates,sum(total_sale)total_sale
from ledger l
where l.dates between '2018-01-29' and '2018-01-31'
group by l.dates)l
left join
(select v.dates,sum(expenditure)total_expenditure
from voucher v
where v.dates between '2018-01-29' and '2018-01-31'
group by v.dates)v
on l.dates=v.dates;

MYSQL fill group by "gaps"

I´m trying to fill the gaps after using group by using an aux table, can you help?
aux table to deal with days with no orders
date quantity
2014-01-01 0
2014-01-02 0
2014-01-03 0
2014-01-04 0
2014-01-05 0
2014-01-06 0
2014-01-07 0
group by result from "orders" table
date quantity
2014-01-01 7
2014-01-02 1
2014-01-04 2
2014-01-05 3
desired result joining "orders" table with "aux table"
date quantity
2014-01-01 7
2014-01-02 1
2014-01-03 0
2014-01-04 2
2014-01-05 3
2014-01-06 0
2014-01-07 0
Without knowing how you create your group by result table, what you're looking for in an outer join, perhaps with coalesce. Something like this:
select distinct a.date, coalesce(b.quantity,0) quantity
from aux a
left join yourgroupbyresults b on a.date = b.date
Please note, you may or may not need distinct -- depends on your data.
Edit, given your comments, this should work:
select a.date, count(b.date_sent)
from aux a
left join orders b on a.date = date_format(b.date_sent, '%Y-%m-%d')
group by a.date
SQL Fiddle Demo
Using your results it would be something like:
SELECT a.date
,COALESCE(b.quantity,0) as quantity
FROM auxtable a
LEFT JOIN groupbyresult b
ON a.date = b.date
You can also do your grouping in the same query as the left join:
SELECT a.date
,COALESCE(COUNT(b.somefield),0) as quantity
FROM auxtable a
LEFT JOIN table1 b
ON a.date = b.date
GROUP BY a.date
One familiar approach to solving a problem like this is to use a row source that has the distinct list of dates you want to return, and then do an outer join to the table that has gaps. That way, you get all the dates back, and you can substitute a zero for the "missing" quantity values.
For example:
SELECT d.date
, IFNULL(SUM(s.quantity),0) AS quantity
FROM distinct_list_of_dates d
LEFT
JOIN information_source s
ON s.date = d.date
GROUP BY d.date
It's not clear why a GROUP BY would be eliminating some date values. We might conjecture that you are using a MySQL extension to ANSI-standard GROUP BY semantics, and that is eliminating rows. Or, you may have a WHERE clause that is excluding rows. But we're just guessing.
FOLLOW UP based on further information revealed by OP in comments...
In the query above, replace distinct_list_of_dates with aux, and replace information_source with orders, and adjusting the join predicate to account for datetime comparison to date
SELECT d.date
, IFNULL(SUM(s.quantity),0) AS quantity
FROM aux d
LEFT
JOIN orders s
ON s.date >= d.date
AND s.date < d.date + INTERVAL 1 DAY
GROUP BY d.date

selecting max record in mysql

I have a table of multiple transactions. I am trying to get the row of the last transaction.
I am using the following:
select n.AccountNumber, max(PostDate), f.TransAmt
from mp_cycle n, fintrans f
where n.AccountNumber = f.AccountNumber
and TransCode >= '50'
and TransCode <= '59'
group by n.AccountNumber
This is returning the last date for a particular account, but the TransAmt is not for the same record.
ie:
Acct # Date Amt
1 1/1 10.00
1 1/2 11.00
1 1/3 12.00
2 1/2 20.00
2 1/3 21.00
2 1/4 22.00
My select will return the last date for each account, so 1/3 for act # 1 and 1/4 for act # 2, but the Amt field is not the amt that goes with that record.
Any assistance will be greatly appreciated.
There are many ways to solve this problem, one is by joining extra subquery which separate gets the latest PostDate for every AccountNumber. The result of the subquery will then be joined on the other table provided that it should match on two columns: AccountNumber and PostDate.
SELECT a.*, b.*
FROM mp_cycle a
INNER JOIN fintrans b
ON a.AccountNumber = b.AccountNumber
INNER JOIN
(
SELECT AccountNumber, MAX(PostDate) max_date
FROM fintrans
GROUP BY AccountNumber
) c ON b.AccountNumber = c.AccountNumber AND
b.PostDate = c.max_date
-- WHERE ..your conditions here..

create a conditional select

I have a table that keeps record of targets assigned to different employees for different products for each month and it has a status field which keeps record of whether assigned target had been approved or not.
status - 1>>Pending, 2>>Approved
Eg:
pdt_id month emp_id status
1 04 1 2
2 04 2 2
3 04 3 1
1 05 1 2
2 05 2 2
3 05 3 2
Now I want to generate a report which shows the only the month for which there are no pending approvals. i.e from the above data the report should only show '05' because its the only month in which all the request have been approved
if i provide condition select month where status='2' it will fetch both 04 and 05 but i want to fetch only 05 ...
Plea
SELECT month
FROM myTable
WHERE month NOT IN (
SELECT month
FROM myTable
WHERE status = 1
)
LEFT JOIN the table onto itself to find out matches and eliminate them.
SELECT
t1.`month`
FROM
your_table AS t1
LEFT JOIN your_table AS t2
ON t1.`month` = t2.`month`
AND t2.`status` = 1
WHERE t2.month IS NULL
GROUP BY t1.month
There might be more elegant ways of doing this, but it gets the job done.
Months with Approved statuses only:
SELECT DISTINCT month
FROM myTable a
WHERE NOT EXISTS
( SELECT *
FROM myTable b
WHERE a.month = b.month
AND b.status <> 2
)
Months without any Pending:
SELECT DISTINCT month
FROM myTable a
WHERE NOT EXISTS
( SELECT *
FROM myTable b
WHERE a.month = b.month
AND b.status = 1
)
There are usually 3 ways to do this kind of problem, (using NOT EXISTS, using NOT IN and using LEFT JOIN with NULL check). You already have answers for the other 2 ways.
In this special case, there's another (4th) way. If you never plan to add more statuses than the 1 and 2, this will also work:
SELECT month
FROM myTable
GROUP BY month
HAVING MIN(status) = 2
Just a final comment/question. Do you only store month in the table, and not year? Because if you also have a year field, the query will not show correct results, once you have data from more than one year in the table.
You can just select months that don't have state Pending:
select month from table_name as tb1 where 0 = (select count(*) from table_name as tb2 where tb2.state = '1' AND tb1.month = tb2.month)
I dont understand why dont you do?
... WHERE month = "05" AND status = 2;
and if you have another concept which is "approved" why not add it as a column and include it in the query as well?
Sometimes fixing a thing means redesigning your tables.