Optimizing MySQL query that includes a repeated subquery - mysql

I have the following query that is working perfectly right now, I have been trying to optimize it since I am using the same subquery 4 times. It will be great to come up with a better/smarter solution. Thank you
Here is the query:
select
invoices.invoice_id
,invoices.invoice_amount
,(
select SUM(invoice_payment_amount) as total
FROM invoice_payments
where invoice_payment_invoice_id = invoices.invoice_id
) as payments
,round((invoices.invoice_amount-(
select SUM(invoice_payment_amount) as total
FROM invoice_payments
where invoice_payment_invoice_id = invoices.invoice_id
)),2) as balance
from invoices
where (
round((invoices.invoice_amount -
(select SUM(invoice_payment_amount) as total
FROM invoice_payments
where invoice_payment_invoice_id = invoices.invoice_id)
),2)
) > 0
or (
round((invoices.invoice_amount -
(select SUM(invoice_payment_amount) as total
FROM invoice_payments
where invoice_payment_invoice_id = invoices.invoice_id)
),2)
) IS NULL
order by balance
SQL Fiddle: http://sqlfiddle.com/#!9/aecea/1

Just use a subquery:
select i.invoice_id, i.invoice_amount, i.payments,
round((i.invoice_amount- i.payments), 2) as balance
from (select i.*,
(select sum(ip.invoice_payment_amount)
from invoice_payments ip
where ip.invoice_payment_invoice_id = i.invoice_id
) as payments
from invoices i
) i
where round((i.invoice_amount- i.payments), 2) > 0 or
round((i.invoice_amount- i.payments), 2) is null
order by balance;
For better performance, you want an index on invoice_payments(invoice_payment_invoice_id, invoice_payment_amount).

Related

How to Combine CASE queries to another JOIN query

I have following query:
SELECT x.id , x.amount , x.amount as paid_amount , SUM(y.bal) as total, x.reciept_no
FROM (SELECT *, paid bal
FROM challan_1 ) x
JOIN (SELECT *, amount bal
FROM challan_1 ) y
ON y.id <= x.id
GROUP BY x.id
HAVING total <= '500'
it's working quite fine. And output like
And then I made a new query that is as below
SELECT *, (CASE WHEN 500-sum(amount) >= 0
THEN '0'
ELSE 500-SUM(paid) END) as pending_amt
FROM challan_1
Output is
This query returns me a Pending Amount so I need to combine both queries so how can I combine both queries.
I need this pending amount in first query.
This is My SQL Fiddle
And I need Like This. Where User Have 500 Currency And Have 3 Payment So for that situation Output Should be like this.
Where 100 Is in pending amount and 200 from user value is debited.
I don't completely understand, but here is my take on this. 500 available. There are records where payments are made. Strange enough even beyond 500, so I assume these are would-be expenses/payments if there were more money available. I stop where payments exceed the 500.
SELECT
challan.*,
SUM(addup.amount) as total_amount,
sum(addup.paid) as total_paid,
sum(addup.amount) - sum(addup.paid) as total_pending,
sum(addup.amount) <= sum(addup.paid) as status
FROM challan_1 challan
JOIN challan_1 addup ON addup.id <= challan.id
GROUP BY challan.id
HAVING sum(addup.paid) <= 500
ORDER BY challan.id;
If you want to show further records, i.e. get rid of the HAVING clause, you'll need another formula for the pending amount, for the highest paid amount possible is 500:
SELECT
challan.*,
SUM(addup.amount) as total_amount,
sum(addup.paid) as total_paid,
sum(addup.amount) - least(500, sum(addup.paid)) as total_pending,
sum(addup.amount) <= least(500, sum(addup.paid)) as status
FROM challan_1 challan
JOIN challan_1 addup ON addup.id <= challan.id
GROUP BY challan.id
ORDER BY challan.id;
Add the subquery as another join.
SELECT x.id , x.amount , x.amount as paid_amount , SUM(y.bal) as total, x.reciept_no, p.pending_amt
FROM (SELECT *, paid bal
FROM challan_1 ) x
JOIN (SELECT *, amount bal
FROM challan_1 ) y
ON y.id <= x.id
CROSS JOIN (SELECT CASE WHEN SUM(amount) <= 500
THEN '0'
ELSE 500 - SUM(paid)
END AS pending_amt
FROM challan_1) AS p
GROUP BY x.id
HAVING total <= '500'

Better optimized SELECT SQL query for 50,000+ records

I have a query which works great for 1000 records or less but now I need to optimize it for 50,000+ records and when I run it on that it just stalls...
Here is my code:
SELECT
b1.account_num,b1.effective_date as ed1,b1.amount as am1,
b2.effective_date as ed2,b2.amount as am2
FROM bill b1
left join bill b2 on (b1.account_num=b2.account_num)
where b1.effective_date = (select max(effective_date) from bill where account_num = b1.account_num)
and (b2.effective_date = (select max(effective_date) from bill where account_num = b1.account_num and effective_date < (select max(effective_date) from bill where account_num = b1.account_num)) or b2.effective_date is null)
ORDER BY b1.effective_date DESC
My objective is to get the latest two effective dates and amounts from one table with many records.
Here is a working answer from your SQL-Fiddle baseline
First, the inner preQuery gets the max date per account. That is then joined to the bill table per account AND the effective date is less than the max already detected.
That is then joined to each respective bill for their amounts.
select
FB1.account_num,
FB1.effective_date as ed1,
FB1.amount as am1,
FB2.effective_date as ed2,
FB2.amount as am2
from
( select
pq1.account_num,
pq1.latestBill,
max( b2.effective_date ) as secondLastBill
from
( SELECT
b1.account_num,
max( b1.effective_date ) latestBill
from
bill b1
group by
b1.account_num ) pq1
LEFT JOIN bill b2
on pq1.account_num = b2.account_num
AND b2.effective_date < pq1.latestBill
group by
pq1.account_num ) Final
JOIN Bill FB1
on Final.Account_Num = FB1.Account_Num
AND Final.LatestBill = FB1.Effective_Date
LEFT JOIN Bill FB2
on Final.Account_Num = FB2.Account_Num
AND Final.secondLastBill = FB2.Effective_Date
ORDER BY
Final.latestBill DESC
In mysql , window analytic function like row_number is not there, so we can simulate the same using variables.
The good thing is, the table is scanned only once with this approach.
A row_number is assigned to each partition which is divided based on ( account number, effective date ) and only 2 rows are selected from each partition.
select account_num,
max(case when row_number =1 then effective_date end) as ed1,
max(case when row_number =1 then amount end) as am1,
max(case when row_number =2 then effective_date end) as ed2,
max(case when row_number =2 then amount end )as am2
from (
select account_num, effective_date, amount,
#num := if(#prevacct= account_num , #num + 1, 1) as row_number,
#prevacct := account_num as dummy
from bill, (select #num:=0, #prevacct := '' ) as var
order by account_num , effective_date desc
)T
where row_number <=2
group by account_num

Optimize SQL Server Query

I have to do a query to get the total cost of previous month and compared to current month to calculate the percentage difference.
this is the script:
create table #calc
(
InvoiceDate Date,
TotalCost decimal (12,2)
)
insert into #calc values ('2013-07-01', 9470.36)
insert into #calc values ('2013-08-01', 11393.81)
and this is the query:
select InvoiceDate,
TotalCost,
PrevTotalCost,
(CASE WHEN (PrevTotalCost = 0)
THEN 0
ELSE (((TotalCost - PrevTotalCost) / PrevTotalCost) * 100.0)
END) AS PercentageDifference
from (
select a.InvoiceDate, a.TotalCost,
isnull((select b.TotalCost
from #calc b
where InvoiceDate = (select MAX(InvoiceDate)
from #calc c
where c.InvoiceDate < a.InvoiceDate)), 0) as PrevTotalCost
from #calc a) subq
Is there a more efficient way to do it for cgetting the previous month?
Using a ranking function to put more burden on sorts than table scans seems the fastest when using no indexes. The query below processed 6575 records in under a second:
SELECT
Main.InvoiceDate,
Main.TotalCost,
PreviousTotalCost=Previous.TotalCost,
PercentageDifference=
CASE WHEN COALESCE(Previous.TotalCost,0) = 0 THEN 0
ELSE (((Main.TotalCost - Previous.TotalCost) / Previous.TotalCost) * 100.00)
END
FROM
(
SELECT
InvoiceDate,
TotalCost,
OrderInGroup=ROW_NUMBER() OVER (ORDER BY InvoiceDate DESC)
FROM
Test
)AS Main
LEFT OUTER JOIN
(
SELECT
InvoiceDate,
TotalCost,
OrderInGroup=ROW_NUMBER() OVER (ORDER BY InvoiceDate DESC)
FROM
Test
)AS Previous ON Previous.OrderInGroup=Main.OrderInGroup+1
Using nested looping as the case when getting the previous invoice cost in a select subquery proves the slowest - 6575 rows in 30 seconds.
SELECT
X.InvoiceDate,
X.TotalCost,
X.PreviousTotalCost,
PercentageDifference=
CASE WHEN COALESCE(X.PreviousTotalCost,0) = 0 THEN 0
ELSE (((X.TotalCost - X.PreviousTotalCost) / X.PreviousTotalCost) * 100.00)
END
FROM
(
SELECT
InvoiceDate,
TotalCost,
PreviousTotalCost=(SELECT TotalCost FROM Test WHERE InvoiceDate=(SELECT MAX(InvoiceDate) FROM Test WHERE InvoiceDate<Main.InvoiceDate))
FROM
Test AS Main
)AS X
Your query processed 6575 records in 20 seconds with the biggest cost coming from the nested loops for inner join
select InvoiceDate,
TotalCost,
PrevTotalCost,
(CASE WHEN (PrevTotalCost = 0)
THEN 0
ELSE (((TotalCost - PrevTotalCost) / PrevTotalCost) * 100.0)
END) AS PercentageDifference
from (
select a.InvoiceDate, a.TotalCost,
isnull((select b.TotalCost
from Test b
where InvoiceDate = (select MAX(InvoiceDate)
from #calc c
where c.InvoiceDate < a.InvoiceDate)), 0) as PrevTotalCost
from Test a) subq
Using indexes would be a big plus unless you are required to use temp tables.
Hope this helps :)
SELECT
`current`.`InvoiceDate`,
`current`.`TotalCost`,
`prev`.`TotalCost` AS `PrevTotalCost`,
(`current`.`TotalCost` - `prev`.`TotalCost`) AS `CostDifference`
FROM dates `current`
LEFT JOIN
dates `prev`
ON `prev`.`InvoiceDate` <= DATE_FORMAT(`current`.`InvoiceDate` - INTERVAL 1 MONTH, '%Y-%m-01');
Screenshot of the results I got: http://cl.ly/image/0b3z2x1f2H1n
I think this might be what you're looking for.
Edit: I wrote this query in MySQL, so it's possible you may need to alter a couple minor syntax things for your server.

MySQL SELECT Query - Subtract a SUM() value with the combined total of two other SUM() values

I have two SELECT statements that give the values "TotalSales" and "VendorPay_Com". I want to be able to subtract VendorPay_Com from TotalSales within the one MySQL statement to get the value "Outstanding_Funds" but I haven't found a reliable way to do so.
These are my two statements:
Query 1:
SELECT SUM(Price) AS TotalSales
FROM PROPERTY
WHERE Status = 'Sold';
Query 2:
SELECT SUM(AC.Amount) AS VendorPay_Comm
FROM (
SELECT Amount FROM lawyer_pays_vendor
UNION ALL
SELECT CommissionEarned AS `Amount` FROM COMMISSION AS C WHERE C.`status` = 'Paid') AS AC
Any help on this matter would be greatly appreciated :)
You can do it as follows :
select (select ...) - (select ...)
In your example, simply :
select
(
SELECT SUM(Price) AS TotalSales
FROM PROPERTY
WHERE Status = 'Sold'
)
-
(
SELECT SUM(AC.Amount) AS VendorPay_Comm
FROM (
SELECT Amount FROM lawyer_pays_vendor
UNION ALL
SELECT CommissionEarned AS `Amount` FROM COMMISSION AS C WHERE C.`status` = 'Paid') AS AC
) AS Outstanding_Funds
Try
SELECT TotalSales-VendorPay_Comm AS Outstanding_Funds
FROM
(SELECT SUM(Price) AS TotalSales
FROM PROPERTY
WHERE Status = 'Sold') t1,
(SELECT SUM(Amount) AS VendorPay_Comm
FROM (SELECT Amount FROM lawyer_pays_vendor
UNION ALL
SELECT CommissionEarned AS Amount
FROM COMMISSION
WHERE Status = 'Paid') t0) t2
Here is sqlfiddle

SQL query not returning expect result

I wrote the following query to return some statistics about purchases made in the X amount of time. But for some reason every "COUNT" column return the total number of rows. Did I organize the query incorrectly?
SELECT COUNT(*) as countTotal, SUM(`cost`) as cost, COUNT(`paymentType` = 'credit') as count_credit, COUNT(`paymentType` = 'cash') as count_cash
FROM `purchase` WHERE `date` >= '2011-5-4'
update
I just decided to use sub-queries. This is what I ended up with.
SELECT
COUNT(*) as countTotal,
SUM(`cost`) as cost,
(SELECT COUNT(*) FROM `purchase` WHERE `paymentType` = 'credit') as count_credit,
(SELECT COUNT(*) FROM `purchase` WHERE `paymentType` = 'cash') as count_cash
FROM `purchase` WHERE `date` >= '2011-5-4'
update2
Used ypercubes answer below.
count does return the number of rows for the domain or group queried. Looks like you need to group by PaymentType to achieve what you are looking for.
SELECT PaymentType, COUNT(*) as countTotal, SUM(`cost`) as cost,
FROM `purchase`
WHERE `date` >= '2011-5-4'
Group by PaymentType
here is a reference
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
It doesn't look correct but changing COUNT() to SUM() works fine:
SELECT COUNT(*) AS countTotal
, SUM(cost) AS cost
, SUM(paymentType = 'credit') AS count_credit --- SUM does counting here
, SUM(paymentType = 'cash') AS count_cash --- and here
FROM purchase
WHERE `date` >= '2011-05-04'
Explanation: True == 1 and False == 0 for MySQL.
You need a GROUP BY clause after your WHERE clause