The sum amount from join tables is incorrect - mysql

I need to get the sum of the admin added into transactions. The transactions is splitted into multiple tables, and now i want to get a total from both tables separately. End up the results i'm getting is ridiculous high. I not sure is it my query multiplied the value somewhere.
User::select('user.name as name',
DB::raw('sum(CASE WHEN current.amount > 0 THEN current.amount END) as current_positive'),
DB::raw('sum(CASE WHEN current.amount < 0 THEN current.amount END) as current_negative'),
DB::raw('sum(CASE WHEN cash.amount > 0 THEN cash.amount END) as cash_positive'),
DB::raw('sum(CASE WHEN cash.amount < 0 THEN cash.amount END) as cash_negative')
)->leftjoin('current_transaction as current', 'current.created_by', '=', 'user.id')
->leftjoin('cash_transaction as cash', 'cash.created_by', '=', 'user.id')
->whereBetween('current.created_at', [$start_date->format('Y-m-d'), $end_date->format('Y-m-d')])
->whereBetween('cash.created_at', [$start_date->format('Y-m-d'), $end_date->format('Y-m-d')])
->where('user.type', 3)
->groupBy('user.name')
->get();
Update
I'm trying with the solution from M Khalid, and the following is the error message:
->mergeBindings($CUT)
Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 1 passed to Illuminate\Database\Query\Builder::mergeBindings() must be an instance of Illuminate\Database\Query\Builder
->mergeBindings($CUT->getBindings())
Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 1 passed to Illuminate\Database\Query\Builder::mergeBindings() must be an instance of Illuminate\Database\Query\Builder, array given
->addBinding($CUT)
ErrorException: Object of class Illuminate\Database\Eloquent\Builder could not be converted to string

You are getting wrong sum amount because of the left joins and each joined table may have more than one rows per user which makes the sum amount quite higher than the original one, to sort out this issue you need to calculation these sum amounts in indvidual sub clauses and then join these with main somewhat like
select users.name as name,cu.*,ca.*
from users
left join (
select created_by,
sum(CASE WHEN amount > 0 THEN amount END) as current_positive,
sum(CASE WHEN amount < 0 THEN amount END) as current_negative
from current_transaction
where created_at between :start_date and :end_date
group by created_by
) cu on users.id = cu.created_by
left join (
select created_by,
sum(CASE WHEN amount > 0 THEN amount END) as cash_positive,
sum(CASE WHEN amount < 0 THEN amount END) as cash_negative
from cash_transaction
where created_at between :start_date and :end_date
group by created_by
) ca on users.id = ca.created_by
where users.type = 3
To do above in laravel is quite complex like for each sub clause get the sql and query builder object and then use them in main query somewhat like below
// For current_transaction
$CUT = CurrentTransaction::query()
->select('created_by',
DB::raw('sum(CASE WHEN amount > 0 THEN amount END) as current_positive'),
DB::raw('sum(CASE WHEN amount < 0 THEN amount END) as current_negative')
)
->from('current_transaction')
->whereBetween('created_at', [$start_date->format('Y-m-d'), $end_date->format('Y-m-d')])
->groupBy('created_by');
$CUTSql = $CUT->toSql();
// For cash_transaction
$CAT = CashTransaction::query()
->select('created_by',
DB::raw('sum(CASE WHEN amount > 0 THEN amount END) as cash_positive'),
DB::raw('sum(CASE WHEN amount < 0 THEN amount END) as cash_negative')
)
->from('cash_transaction')
->whereBetween('created_at', [$start_date->format('Y-m-d'), $end_date->format('Y-m-d')])
->groupBy('created_by');
$CATSql = $CAT->toSql();
// Main query
User::select('user.name as name','cu.*','ca.*')
->leftjoin(DB::raw('(' . $CUTSql. ') AS cu'),function($join) use ($CUT) {
$join->on('user.id', '=', 'cu.created_by');
/* ->addBinding($CUT->getBindings());*/
})
->leftjoin(DB::raw('(' . $CATSql. ') AS ca'),function($join) use ($CAT) {
$join->on('user.id', '=', 'ca.created_by');
/* ->addBinding($CAT->getBindings()); */
})
->where('user.type', 3)
->mergeBindings($CUT) /* or try with ->mergeBindings($CUT->getBindings()) */
->mergeBindings($CAT) /* or try with ->mergeBindings($CAT->getBindings()) */
->get();

Related

SUM a column then SUBTRACT the SUM of another column and get values 0 instead of negative values

how can I get $sum 0 values instead of negative values? I've seen that people use < then .. else 0 but when I try, it display the wrong value :(
stock_movement_details.db:-
stock_movement.db
raw_material.db
As you can see, I have type (subtract/add). Let say, ID 10 for raw_material_id, quantity is 10.00, ID 11&12 subtract 9.48, logic is (10- 9.48 -9.48 = -8.96). However, instead of getting negative values, I want it to be 0.
Table that display quantity balance:-
$rawMaterials = RawMaterials::where(['status' => 'Active'])->get();
$rawMaterials = $rawMaterials->map(function ($f) {
$total = DB::select('select
( sum(case when detail.type="add" then detail.quantity else 0 end) - sum(case when detail.type="subtract" then detail.quantity else 0 end) ) as total
from stock_movement_details detail
left join stock_movements main on main.id = detail.stock_movement_id
where detail.raw_material_id = ?
', [$f->id]);
$sum = 0;
foreach ($total as $t) {
$sum += $t->total;
}
$rawMats = [
"id" => $f->id,
"total" => $sum,
];
return $rawMats;
});
You could add another CASE expression in your query, to check if total is negative, but this would mean that you should repeat the expression that calculates the difference of the 2 sums.
It's better to use your query as a subquery and check total in the outer query:
SELECT CASE WHEN total < 0 THEN 0 ELSE total END AS total
FROM (
SELECT SUM(CASE WHEN d.type = "add" THEN d.quantity ELSE 0 END) -
SUM(CASE WHEN d.type = "subtract" THEN d.quantity ELSE 0 END) AS total
FROM stock_movement_details d LEFT JOIN stock_movements m
ON m.id = d.stock_movement_id
WHERE d.raw_material_id = ?
) t
Also, I don't see the need for a LEFT join of stock_movement_details to stock_movements because you are not using any columns of stock_movements in your query.
You can simplify to:
SELECT CASE WHEN total < 0 THEN 0 ELSE total END AS total
FROM (
SELECT SUM(CASE WHEN type = "add" THEN quantity ELSE 0 END) -
SUM(CASE WHEN type = "subtract" THEN quantity ELSE 0 END) AS total
FROM stock_movement_details
WHERE raw_material_id = ?
) t
Or:
SELECT CASE WHEN total < 0 THEN 0 ELSE total END AS total
FROM (
SELECT SUM(CASE type WHEN "add" THEN 1 WHEN "subtract" THEN -1 ELSE 0 END * quantity) AS total
FROM stock_movement_details
WHERE raw_material_id = ?
) t

Diffrence between sum of two products > 0

I want to select the sum of T_No where Transactions are equal to R and subtract it by T_No where Transactions are equal to D and the answer of this should greater than zero for a CustomerID which would be a input (an int input declared in a stored procedure)
((Sum(T_No) where Transactions = R - Sum(T_No) where Transactions = D ) > 0) where CoustomerID = #input
Example : for ID = 1 it would be ((20+15) - 10) > 0
I Have tried so many things but either syntax is wrong, wrong value or it does not accept, and I am literally Stuck, this was my final attempt
SELECT
(select ( select Sum(T_No) where Transactions = R) - (select Sum(T_No) where Transactions = D) as C_T )
FROM CustomerTrans WHERE C_T > 0 ;
Conditional aggregation should help:
SELECT
SUM(CASE WHEN Transaction = 'R' THEN t_no ELSE 0 END) - SUM(CASE WHEN Transaction = 'D' THEN t_no ELSE 0 END)
FROM CustomerTrans
WHERE CoustomerID = #yourCustomerIdVariable
As you're writing a sproc you can assign the result of this to a variable and then decide what to do if the result is negative. (I would personally log an error for example, rather than just hide those results). If the result is null, then there were no transactions for that customer
ps; I used Transaction because that's what your screenshot showed, and I figured a screenshot is less likely to contain a typo than code with syntax errors. Adjust if required
you where kinda close, I would sum like you, only the syntax is a bit off, you can't have aggregate fields in Where, thats why you should use having, also case when syntax is not correct.
Select
CoustomerID,
Sum(case when Transactions = 'R' then T_No else 0 end) -
Sum(case when Transactions = 'D' then T_No else 0 end) as C_T
FROM CustomerTrans
group by CoustomerID
having (Sum(case when Transactions = 'R' then T_No else 0 end) -
Sum(case when Transactions = 'D' then T_No else 0 end))>0

Combining two resulted columns on order by when one is null

I have a inventory table where i have records which refers to inventory movement of products sold by a company. The movement have 'INCOMING', 'OUTGOING' params based on type of inventory movement. It also has one more column which says the type of 'INCOMING' or 'OUTGOING'... like incoming because of NEW STOCK arrival, outgoing because of PURCHASE by customer... etc...
Now am making a report where i want to list non sold products for a long while. So am making the following query...
SELECT p.id as pid, product_name, DATEDIFF(NOW(), MAX(case when movement_type='OUTGOING' and movement_type_category='PURCHASED' then movement_on end)) AS unsold_days_since_last_sale, DATEDIFF(NOW(), MIN(case when movement_type='INCOMING' and movement_type_category='NEW_STOCK' and quantity>0 then movement_on end)) AS unsold_days_since_first_inventory_in, MAX(case when movement_type='INCOMING' and movement_type_category='NEW_STOCK' and quantity>0 then movement_on end) AS last_inv_in from inventory_movement im left join products p on im.product = p.id GROUP BY product having last_inv_in > 0 ORDER BY unsold_days_since_last_sale desc limit 100
And i get the following output as shown in the image.
This output is nearly correct but with one issue. If a product was never sold even once in the past the column where i try to get days different between CURRENT DAY and LAST SOLD DAY will return null. In that case i need the DAYS difference between CURRENT DAY and FIRST INVENTORY IN of that product to be on the place so i can order that column descending and get the output. But i can get those data as only 2 different columns not as one column. Can someone help me to write a query to get it as combined column so i can sort that data to get result. Am attaching my inventory movement table snap also to show how the data look like...
I think IfNull function will resolve your issue.
Here's modified query.
SELECT p.id AS pid,
product_name,
Datediff(Now(), Ifnull(Max(CASE
WHEN movement_type = 'OUTGOING'
AND movement_type_category =
'PURCHASED' THEN
movement_on
end), Min(CASE
WHEN movement_type = 'INCOMING'
AND movement_type_category =
'NEW_STOCK'
AND quantity > 0 THEN
movement_on
end))) AS
unsold_days_since_last_sale,
Datediff(Now(), Min(CASE
WHEN movement_type = 'INCOMING'
AND movement_type_category = 'NEW_STOCK'
AND quantity > 0 THEN movement_on
end)) AS
unsold_days_since_first_inventory_in,
Max(CASE
WHEN movement_type = 'INCOMING'
AND movement_type_category = 'NEW_STOCK'
AND quantity > 0 THEN movement_on
end) AS last_inv_in
FROM inventory_movement im
LEFT JOIN products p
ON im.product = p.id
GROUP BY product
HAVING last_inv_in > 0
ORDER BY unsold_days_since_last_sale DESC
LIMIT 100

How do I calculate the difference of two alias for sorting

Considering the following code:
SELECT SUM(w.valor),
SUM(CASE WHEN w.tipo = '+' THEN w.valor ELSE 0 END) AS total_credit,
SUM(CASE WHEN w.tipo = '-' THEN w.valor ELSE 0 END) AS total_debit,
w.clientUNIQUE,
c.client as cclient
FROM wallet AS w
LEFT JOIN clients AS c ON w.clientUNIQUE = c.clientUNIQUE
WHERE w.status='V'
GROUP BY w.clientUNIQUE
ORDER BY total_credit-total_debit
I'm trying to calculate the difference of two aliased calculated values for sorting purposes, but I'm getting the following error:
Reference 'total_credit' not supported (reference to group function)
What am I doing wrong and how can I order results by using the difference value between the two aliases?
You can't refer to columns by their alias in the same select expression, so there are 2 options...
Repeat the expressions in the order by (yuk):
ORDER BY
SUM(CASE WHEN w.tipo = '+' THEN w.valor ELSE 0 END) AS total_credit -
SUM(CASE WHEN w.tipo = '-' THEN w.valor ELSE 0 END) AS total_debit
Or easier on the brain and easier to maintain (DRY), order via a sub query:
select * from (
<your query without the ORDER BY>
) q
ORDER BY total_credit - total_debit

Trying to COUNT the same table for different values

I have a table called flags from which I'm trying to extract two COUNTs.
I'd like one COUNT for the number of flags since the start of the year and a separate COUNT for this week's allocation.
The query I'm using is as follows:
SELECT
COUNT(f1.ID) AS `Total Flags`,
COUNT(f2.ID) AS `Weekly Flags`
FROM `frog_flags`.`flags` f1
LEFT JOIN `frog_flags`.`flags` f2
ON f1.`ID` = f2.`ID`
WHERE
f2.`Datetime` > '2013-07-08 00:00:00'
AND
( f1.`Staff_ID` = '12345' AND f2.`Staff_ID` = '12345')
AND
f1.`Datetime` > '2012-09-01 00:00:00'
Even though I have data in place, it's showing 0 for both the Total Flags and the Weekly Flags.
I suspect I've confused my WHERE clauses for trying to JOIN the same table twice.
Am I using my clauses incorrectly when trying to COUNT the same table for different values?
This is a cross-tab SQL query - it's a great design pattern once you get the hang of it:
SELECT
sum( case when `Datetime`> '2012-09-01 00:00:00' then 1 else 0 end) AS `Total Flags`,
sum( case when `Datetime`> '2013-07-08 00:00:00' then 1 else 0 end) AS `Weekly Flags`
FROM `frog_flags`.`flags` f1
WHERE f1.`Staff_ID` = '12345'
You use a condition to create basically boolean flags which get summed up - this allows for a number of predefined new columns instead of rows.
You could take it further and do it for all staff simultaneously:
SELECT
f1.`Staff_ID`,
sum( case when `Datetime`> '2012-09-01 00:00:00' then 1 else 0 end) AS `Total Flags`,
sum( case when `Datetime`> '2013-07-08 00:00:00' then 1 else 0 end) AS `Weekly Flags`
FROM `frog_flags`.`flags` f1
WHERE f1.`Staff_ID` = '12345'
GROUP BY f1.`Staff_ID`