How to create a MySQL Query with accumulation on a created field? - mysql

I have two tables, Invoice and Receiving, and I am using MySQL. I want to generate a balance sheet from these two tables. The result should accumulate a balance over time based on the I_Total column from Invoice and the CR_Amount column from Receiving as shown in the figures below. I have tried many queries with different joins but I am not getting the desired output.
How can I achieve the desired balance sheet with a query or function in MySQL?

If we ignore the balance column for a moment, and assuming that the date column is DATE datatype, then getting the result shown can be achieved with something like this:
SELECT t.date
, t.debit
, t.credit
FROM ( SELECT i.i_date AS date
, i.i_total AS debit
, 0 AS credit
, 'i' AS i_or_r
, i.i_id AS id
FROM invoice i
UNION ALL
SELECT r.r_date AS date
, 0 AS debit
, r.r_total AS credit
, 'r' AS i_or_r
, r.r_id AS id
FROM receiving r
) t
ORDER
BY t.date
, t.i_or_r
, t.id
To get the balance, we could do that processing on the client side, as the rows are retrieved.
NOTE: MySQL 8.0 introduces window functions, which have been available in other RDBMS such as SQL Server and Oracle (calls them "Analytic Functions").
Without window functions, to get it done in the SQL is going to be ugly.
We could make use of unsupported usage of user-defined variables. Using this approach, we would basically emulate the processing that we would do on the client side, fetching through the result that query (processing each row in order) to add/subtract from a "running balance" in a user-defined variable.
The "ugly" part about this is that it relies on behavior that is not guaranteed. The MySQL Reference Manual includes warning about it.)
Or, to get the result using pure SQL, we could use a couple of complicated looking correlated subqueries to sum up the debit and credit amounts up to the current row, and do that for each row.
It looks like we are applying debits and credits towards the balance in a similar order that a bank does, applying all of the debits and credits in date order. And on each date, we apply the debits first, and then the credits.
From the sample data and expected result, it's not clear if debits are applied ordered by amount in ascending order, or by id in ascending order.
With the sample data, we get the same balance result either way. Assuming that i_id is unique in invoice, and cr_id is unique in receiving, we can get the balance by applying credits and debits using id order as a discriminator when we're at the current date.
(If we need to apply credits on the same date in ascending amount order, the subqueries would be a little more complicated, to take into account the possibility that two credits on the same date could be for the same amount.)
SELECT t.date
, t.debit
, t.credit
, ( SELECT SUM(bi.i_total)
FROM invoice bi
WHERE bi.i_date <= t.date
AND ( bi.i_date < t.date
OR ( t.i_or_r = 'i' AND bi.i_id <= t.id )
)
)
- ( SELECT SUM(br.cr_amount)
FROM receiving br
WHERE br.cr_date <= t.date
AND ( br.cr_date < t.date
OR ( t.i_or_r = 'r' AND br.cr_id <= t.id )
OR t.i_or_r = 'i'
)
) AS balance
FROM ( SELECT i.i_date AS date
, i.i_total AS debit
, 0 AS credit
, 'i' AS i_or_r
, i.i_id AS id
FROM invoice i
UNION ALL
SELECT r.cr_date AS date
, 0 AS debit
, r.cr_amount AS credit
, 'r' AS i_or_r
, r.cr_id AS id
FROM receiving r
) t
ORDER
BY t.date
, t.i_or_r
, t.id

Try This
SELECT *, SUM(Debit) OVER(ORDER BY dt,debit ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)-SUM(Credit) OVER(ORDER BY dt,debit ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) Balance
FROM (
SELECT ID,Dt,Amount AS Debit,0 AS Credit FROM Debit
UNION ALL
SELECT ID,Dt,0 AS Debit,Amount AS Credit FROM Credit
)X
ORDER BY DT
DEMO

Related

I Need help in writing a multi-level ms access subquery

I am having hard time to wrap my mind around a multi-level query/sub-query. In short, I a table called REGISTER with 3 fields in it: TransactionId (PK), TransDate (DateTime), Amount (Currency). I need a single query to retrieve the TransactionId where TransDate is the Max and the Amount is the Min qualifying records. For example, if the max of qualifying record returns 5 records with today's date (any future dates is excluded), I would like to know the TransactionId of the lowest Amount within the 5 records returned.
I amble to accomplish this task with two separate query but I am sure it cab be done with a single one.
Query 1 (qryFlag):
SELECT REGISTER.TransDate, REGISTER.*
FROM REGISTER
WHERE (((REGISTER.TransDate)=(
SELECT Max(t2.Transdate) from REGISTER t2
where Transdate <= Date())));
Query 2:
SELECT REGISTER.TransactionId
FROM qryFlag INNER JOIN REGISTER ON qryFlag.TransactionId = REGISTER.TransactionId
WHERE (((qryFlag.Amount)=(SELECT Min(t2.Amount) from qryFlag t2)));
Try:
SELECT TOP 1 VTID.TransactionID
FROM (
SELECT TransactionID, Amount
FROM Register
WHERE TransDate = (SELECT Max(R.TransDate) FROM Register as R WHERE R.TransDate <= Date())
) as VTID
ORDER BY VTID.Amount
HTH
Dale
You need to GROUP BY TransDate to get the min amount and then join to the table:
SELECT r.*
FROM REGISTER AS r INNER JOIN (
SELECT TransDate, MIN(Amount) AS MinAmount
FROM REGISTER
WHERE TransDate = (SELECT Max(Transdate) FROM REGISTER WHERE Transdate <= Date())
GROUP BY TransDate
) AS g on g.TransDate = r.TransDate AND g.MinAmount = r.Amount

Calculating Running totals across rows and grouping by ID

I want to compute running row totals across a table, however the totals must start over for new IDs
https://imgur.com/a/YgQmYQA
My code:
set #csum := 0;
select ID, name, marks, (#rt := #rt + marks) as Running_total from students order by ID;
The output returns the totals however doesn't break or start over for new IDs
Bro try this... It is tested on MSSQL..
select ID, name, marks,
marks + isnull(SUM(marks) OVER ( PARTITION BY ID ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) ,0) as Running_total
from students
You need to partition your running total by ID. A running total always needs an order of some column, by ordering on which you want to calculate the running total. Assuming running total under each ID is based on ORDER of marks,
Approach 1: It can be written in a simple query if your DBMS supports Analytical Functions
SELECT ID
,name
,marks
,Running_total = SUM(marks) OVER (PARTITION BY ID ORDER BY marks ASC)
FROM students
Approach 2: You can make use of OUTER APPLY if your database version / DBMS itself does not support Analytical Functions
SELECT S.ID
,S.name
,S.marks
,Running_total = OA.runningtotalmarks
FROM students S
OUTER APPLY (
SELECT runningtotalmarks = SUM(SI.marks)
FROM students SI
WHERE SI.ID = S.ID
AND SI.marks <= S.marks
) OA;
Note:- The above queries have been tested MS SQL Server.

SQL get one time customers by email field

I have a database with over 100,000 records. I'm trying to get all customers who ordered only once searching by customer's email field (OrderEmail).
The SQL query is running for 10 minutes and then times out.
If I use short date ranges, I can get results but it still takes over 3 minutes.
How can I optimize the syntax to get it work?
SELECT
tblOrders.OrderID,
tblOrders.OrderName,
tblOrders.OrderEmail,
tblOrders.OrderPhone,
tblOrders.OrderCountry,
tblOrders.OrderDate
FROM
tblOrders
LEFT JOIN tblOrders AS orders_join ON orders_join.OrderEmail = tblOrders.OrderEmail
AND NOT orders_join.OrderID = tblOrders.OrderID
WHERE
orders_join.OrderID IS NULL
AND (tblOrders.OrderDate BETWEEN '2015-01-01' AND '2017-03-01')
AND tblOrders.OrderDelivered = - 1
ORDER BY
tblOrders.OrderID ASC;
I would expect the below to work - but I can't test it as you don't provide sample data. Well, I added a temporary table definition that could be used for the query ....
But , if you could actually change the data model to use an INTEGER id for the entity who placed the order (instead of a VARCHAR() email address), you would get considerably faster.
CREATE TEMPORARY TABLE IF NOT EXISTS
tblorders(orderid,ordername,orderemail,orderphone,ordercountry,orderdate) AS (
SELECT 1,'ORD01','adent#hog.com' ,'9-991' ,'UK', DATE '2017-01-01'
UNION ALL SELECT 2,'ORD02','tricia#hog.com','9-992' ,'UK', DATE '2017-01-02'
UNION ALL SELECT 3,'ORD03','ford#hog.com' ,'9-993' ,'UK', DATE '2017-01-03'
UNION ALL SELECT 4,'ORD04','zaphod#hog.com','9-9943','UK', DATE '2017-01-04'
UNION ALL SELECT 5,'ORD05','marvin#hog.com','9-9942','UK', DATE '2017-01-05'
UNION ALL SELECT 6,'ORD06','ford#hog.com' ,'9-993' ,'UK', DATE '2017-01-06'
UNION ALL SELECT 7,'ORD07','tricia#hog.com','9-992' ,'UK', DATE '2017-01-07'
UNION ALL SELECT 8,'ORD08','benji#hog.com' ,'9-995' ,'UK', DATE '2017-01-08'
UNION ALL SELECT 9,'ORD09','benji#hog.com' ,'9-995' ,'UK', DATE '2017-01-09'
UNION ALL SELECT 10,'ORD10','ford#hog.com' ,'9-993' ,'UK', DATE '2017-01-10'
)
;
SELECT
tblOrders.OrderID
, tblOrders.OrderName
, tblOrders.OrderEmail
, tblOrders.OrderPhone
, tblOrders.OrderCountry
, tblOrders.OrderDate
FROM tblOrders
JOIN (
SELECT
OrderEmail
FROM tblOrders
GROUP BY
OrderEmail
HAVING COUNT(*) = 1
) singleOrders
ON singleOrders.OrderEmail = tblOrders.OrderEmail
ORDER BY OrderID
;
OrderID|OrderName|OrderEmail |OrderPhone|OrderCountry|OrderDate
1|ORD01 |adent#hog.com |9-991 |UK |2017-01-01
4|ORD04 |zaphod#hog.com|9-9943 |UK |2017-01-04
5|ORD05 |marvin#hog.com|9-9942 |UK |2017-01-05
As you can see, it returns Mr. Dent, Zaphod and Marvin, who all occur only once in the example data.
Another approach that might work is that you group by email address and get only those with one entry. It may behave unpredictably if you want to get customers with multiple orders but it should be fine for this particular case:
SELECT
tblOrders.OrderID,
tblOrders.OrderName,
tblOrders.OrderEmail,
tblOrders.OrderPhone,
tblOrders.OrderCountry,
tblOrders.OrderDate,
count(tblOrders.OrderID) as OrderCount
FROM
tblOrders
WHERE
tblOrders.OrderDate BETWEEN '2015-01-01' AND '2017-03-01'
AND tblOrders.OrderDelivered = - 1
GROUP BY
tblOrders.OrderEmail
HAVING
OrderCount = 1
ORDER BY
tblOrders.OrderID ASC;
Also, I suspect that if you're seeing so long query times with just 100k records, you probably don't have an index on the OrderEmail column - I suggest setting that up and that might help with your original queries as well.
This does not work in Oracle, or SQL Server but it does work in MySQL and SQLite. So, while the code is not portable between different RDBMS, it works for this particular case.

Displaying data with respect to specific date?

I am trying to make a reporting system where I need to display report
for each date.
These is my table schema for selected_items
This is stock_list
I am using php in the back-end and java in the front end to display
the data. I tried a couple of queries to get the desired output but so
far I am not able to get it.These are some of the queries i used.
SELECT
COALESCE(stock_list.date, selected_items.date) AS date,
SUM( stock_list.qty ) AS StockSum,
SUM( stock_list.weight ) AS Stockweight,
COUNT( selected_items.barcode ) AS BilledItems,
SUM( selected_items.weight ) AS Billedweight
FROM stock_list join selected_items
ON stock_list.date = selected_items.date
GROUP BY COALESCE(stock_list.date, selected_items.date)
ORDER BY COALESCE(stock_list.date, selected_items.date);
This gives me the first five columns but the output gives me wrong values.
Then I also tried Union.
SELECT SUM( qty ) AS StockSum, SUM( weight ) AS Stockweight
FROM `stock_list`
WHERE DATE LIKE '08-Jan-2016'
UNION SELECT COUNT( barcode ) AS BilledItems, SUM( weight ) AS Billedweight
FROM `selected_items`
WHERE DATE LIKE '08-Jan-2016'
UNION SELECT SUM( qty ) AS TotalStock, SUM( weight ) AS TotalWeight
FROM `stock_list`;
Here I get the correct values for four columns but the problem is the >result is displayed in two columns when I would like it to be in 4 columns.
Can anyone guide me please I have figured the java part of it but I am not good at php and mysql.
Thank you
Unfortunately, SQL Fiddle crashed while I was trying to execute this query
SELECT sl.date AS date, B.qtySum AS StockSum, B.weightSum AS Stockweight,
C.barcodeCount AS BilledItems, C.weightSum AS Billedweight
FROM stock_list sl
JOIN (SELECT SUM(qty) as qtySum, SUM(weight) as weightSum
FROM STOCK_LIST GROUP BY date) AS B
ON B.date = sl.date
JOIN (SELECT SUM (weight) AS weightSum, COUNT(barcode) AS barcodeCount
FROM SELECTED_ITEMS GROUP BY date) AS C
ON C.date = sl.date;
As it was tried here. The problem with joins is that the rows will be joined multiple times and thus, the sum goes awry. For example, you have four rows that are joined from the second table and so the sum is four times higher as it should. With subqueries you can avoid this problem as you count and sum up variables before joining them and therefore, the numbers should fit. Alas, I couldn't run the query so I'm not 100% sure it works, but it should be the right approach.

Aggregating data to get a running total month on month

I have a table which holds the below data
This issue im having is that i need a running total for each month, I've managed to create this is an excel sheet pretty easily but when i try anything in SQL the data result varies.
The image below shows the sum of each paid amount by month, then a total of each one added onto it. I've edited excel to show the formula and the result of the formula. Also have the result i get from SQL 2008 when using (example only)
***UPDATE - The result set im trying to achieve that is in the excel document is for example month 117 + Month 118 gives Month118 TotalToDate, then month 118 + 119 gives Months 119 Total to Date.
Not sure how else to explain this?
( select sum(paid) from #tmp005 t2 where t2.[monthid] <=
t5.[monthid] ) as paid
Really feel that this is less complicated than what I think!
As I understand this you are trying to get a running total month by month, the below CTE should do what you want.
--create table #temp (M_ID Int, Paid Float)
--Insert Into #temp VALUES (116, '50.00'), (117, '50.00'),(117, '5.00'),(117, '20.00'),(117, '10.00'),(117, '75.40'),(118, '125.00'),(118, '200.00'),(118, '5.00')
;WITH y AS
(
SELECT M_ID, paid, rn = ROW_NUMBER() OVER (ORDER BY M_ID)
FROM #temp
), x AS
(
SELECT M_ID, rn, paid, rt = paid
FROM y
WHERE rn = 1
UNION ALL
SELECT y.M_ID, y.rn, y.paid, x.rt + y.paid
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT M_ID, MAX(rt) as RunningTotal
FROM x
Group By M_ID
OPTION (MAXRECURSION 10000);
It is based on the first 3 M_ID of your sample data, just change around the #temp to your specific table, I didn't know whether you had another unique identifier in the table which is why I had to use the ROW_NUMBER()but this should order it correctly based on the M_ID field.
I guess that you are storing the month in a separated table and using M_ID to reference it. So, to get the sum of each month do this:
SELECT [M_ID]
,sum([Paid])
FROM #tmp005
GROUP BY [M_ID]
I think I'd use a correlated sub query:-
select r.m_id,
(
select sum(csq.paid)
from #tmp005 csq
where csq.m_id<=r.m_id
)
from (
select distinct m_id
from #tmp005
) r
Hopefully you can figure out how to apply it to your circumstance/schema.