Perform calculating on each result of a MySQL search - php

To calculate a total amount for an order, I go to the articles table, and calculate the sum of the articles for this order.
Here is my code:
SELECT orders.*,
ROUND(SUM(`orders_article_updated_quantity` * (`orders_article_price` * (100 - orders_article_rate)/100)), 2) AS 'order_Total'
FROM orders JOIN orders_articles
ON orders.order_id = orders_articles.orders_article_id_order
WHERE oreder_id = '" . $order['order_id'] . "'
This code is working for an order, and I got a good result.
Now I want to do this for a list of orders, or all orders. So I deleted the "WHERE" from my request. And I get only one result, with the sum of all orders.
For each result (order), I want the sum of its own articles.

You need aggregation for this. Consider adding a group by clause to the query, like so:
SELECT
o.*,
ROUND(SUM(orders_article_updated_quantity * orders_article_price * (1 - orders_article_rate/100)), 2) AS order_Total
FROM orders o
INNER JOIN orders_articles oa ON ao.orders_article_id_order = o.order_id
GROUP BY o.order_id
Note that you should prefix each column in the query with the table they belong to - that's not the case for the column within the SUM(), which creates ambiguity.
Another option is a correlated subquery (this allows orders without any article):
SELECT
o.*,
(
SELECT ROUND(SUM(orders_article_updated_quantity * orders_article_price * (1 - orders_article_rate/100)), 2)
FROM orders_articles oa
WHERE ao.orders_article_id_order = o.order_id
) AS order_Total
FROM orders o

Related

Mysql (doctrine) is duplicating count results probably due to grouping by but can't figure it out

I'm using doctrine as an ORM layer but by putting a plain mysql query in my database tool i got the same results. So my problem:
I have an invoices table, invoice_items and invoice_payments table and what i would like to get as an result is all invoices that are not paid or at least not fully paid yet. I know that the query should be almost correctly because its giving the correct amount of items back... the only thing is that it is multiplying the amount by a number of probably joined rows.
So the query that i have for now:
select invoice.*, sum(item.amount * item.quantity) as totalDue,
sum(payment.amount) as totalPaid
from invoices as invoice
left join invoice_items as item on item.invoice_id = invoice.id
left join invoice_payments as payment on payment.invoice_id = invoice.id
and payment.status = 'successful'
where invoice.invoice_number is not null
and invoice.sent_at is not null
and invoice.due_date >= '2018-05-15'
group by invoice.id
having count(payment.id) = 0
or sum(payment.amount) < sum(item.amount * item.quantity)
order by invoice.issue_date desc, sum(payment.amount) desc;
As you can see i also have totalDue and totalPaid in my select (those are for reference only and should be removed if the query is correct).
What i saw is that the amount is multiplied by six (because it has 6 items in the payments table).
So maybe someone could help me pointing in the right direction that it doesn't do the multiplying on the totalDue. I was thinking its probably because the group by but without my query is failing.
By simply using a distinct in my query i fixed the problem.
select invoice.*, sum(distinct(item.amount * item.quantity)) as totalDue,
sum(payment.amount) as totalPaid
from invoices as invoice
left join invoice_items as item on item.invoice_id = invoice.id
left join invoice_payments as payment on payment.invoice_id = invoice.id
and payment.status = 'successful'
where invoice.invoice_number is not null
and invoice.sent_at is not null
and invoice.due_date >= '2018-05-15'
group by invoice.id
having count(payment.id) = 0
or sum(payment.amount) < sum(distinct(item.amount * item.quantity))
order by invoice.issue_date desc, sum(payment.amount) desc;
I would like to thank all the people who had taken the time to re-style my question ;-)

Sum columns on different tables and multiply by value of a column on another table

I need to compute employees' monthly salaries based on meetings attended, deductions and bonuses given;
Employees have different pay per meeting based on their job position.
The solution is:
salary = (Pay_per_minute * meetings_attended) + bonuses - deductions ;
I have four tables:
Jobs: Id, title, pay_per_meeting
Employees: Id, Name, job_id
Bonuses: Id, amount, employee_id, date
Deductions: Id, amount, employee_id, date
Meetings: Id, employee_id, date
SELECT
COUNT(meetings.employee_id) as meetings_attended,
COUNT(deductions.amount) as debt,
COUNT(bonuses.amount) bonus,
(SELECT jobs.pay_per_attendance from jobs where jobs.id = (select job_id from employees where id=meetings.employee_id)) as pay,
((meetings_attended * pay) + bonus - debt) as salary
FROM meetings
JOIN deductions ON deductions.employee_id = meetings.employee_id
JOIN bonuses ON bonuses.employee_id = meetings.employee_id
WHERE meetings.employee_id = 1
GROUP BY MONTH(meetings.date), MONTH(deductions.date), MONTH(bonuses.date)
The above query returns many incorrect values whenever i remove the salary line but gives error of unknown column pay, meetings_attended, debt and bonus, am sure something is wrong with the grouping but i can't just see it.
You can't refer to column aliases in the same select list as they're defined, you need to refer to the underlying column. And a subquery can't access an aggregate calculated in the main query. You need to repeat the aggregate expression, or move everything into a subquery and do the calculation with it in an outer query.
Also, all your COUNT() expressions are going to return the same thing, since they're just counting rows (I assume none of the values can be NULL). You probably want COUNT(DISTINCT <column>) to get different counts, and you need to use a column that's unique, so they should be the primary key column, e.g. COUNT(DISTINCT deductions.id).
Another problem is that when you try to sum and count values when you have multiple joins, you end up with a result that's too high, because rows get duplicated in the cross product of all the tables. See Join tables with SUM issue in MYSQL. The solution is to calculate the sums from each table in subqueries.
SELECT m.month, m.meetings_attended, d.debt, b.bonus,
m.meetings_attended * j.pay_per_meeting + b.amount - d.amount AS salary
FROM (
SELECT MONTH(date) AS month, COUNT(*) AS meetings_attended
FROM meetings
WHERE employee_id = 1
GROUP BY month) AS m
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS bonus, SUM(amount) AS amount
FROM bonuses
WHERE employee_id = 1
GROUP BY month) AS b ON m.month = b.month
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS debt, SUM(amount) AS amount
FROM deductions
WHERE employee_id = 1
GROUP BY month) AS d ON m.month = d.month
CROSS JOIN employees AS e
JOIN jobs AS j ON j.id = e.job_id
WHERE e.employee_id = 1

Join a query into another query with column computation

I have three tables named issue_details, nature_payments, and rci_records. Now I have this query which joins this three tables.
SELECT issue_details.issue_date AS Date,
issue_details.check_no AS Check_No,
payees.payee_name AS Name_payee,
nature_payments.nature_payment AS Nature_of_Payment,
issue_details.issue_amount AS Checks_issued,
issue_details.nca_balance AS Nca_balance
FROM
issue_details
INNER JOIN
nature_payments ON
issue_details.nature_id = nature_payments.nature_id
INNER JOIN
payees ON
issue_details.payee_id = payees.payee_id
ORDER BY Date Asc, Check_no ASC
On my column in Nca_balance, this is a computed differences of every issuances of check. But you may not know what really the process of how I got the difference but to make it simple, let's say that I have another query
that dynamically get also the difference of this nca_balance column. Here is the query:
SELECT r.*,
(#tot := #tot - issue_amount) as bank_balance
FROM (SELECT #tot := SUM(nca_amount) as nca_total FROM nca
WHERE account_type = 'DBP-TRUST' AND
year(issue_date) = year('2015-01-11') AND
month(issue_date) = month('2015-01-11')
)
vars CROSS JOIN issue_details r
WHERE r.account_type = 'DBP-TRUST' AND
r.issue_date = '2015-01-11'
ORDER BY r.issue_date, r.check_no
I know it you may not get my point but I just want to replace the first query of the line
issue_details.nca_balance AS Nca_balance
with my own computation on my second query.
Please help me combine those two query into a single query. Thanks

Executing a "weighted" SQL query

In my current setup, I have two tables: product and rating.
Product Table
product_id
rating
The product table contains a whole bunch additional of information, but for this question, I am focussed on those two fields only.
Rating Table
product_id
rating
user_id (who rated)
is_admin - bool on whether the user that rated was an admin
The reason we collect the admin ratings in the first place, is because we want to weigh admin ratings slightly higher (60%) in comparison to regular users (40%). The rating column in the product table is equal to the AVG of all the admin ratings. Ratings in general are between 1 and 5.
So for each product we have to consider four scenarios:
RATINGS BY TOTAL
USER ADMIN RATING
---- -----
no no = 0
yes no = AVG of user ratings (`ratings` table)
yes yes = 0.6 AVG of admin ratings (`product_table`) + 0.4 AVG of user ratings (`ratings` table)
no yes = AVG of admin ratings (`product_table`)
The SQL query which currently retrieves the datasets looks like this:
$sql = "SELECT p.product_id,
(COALESCE(p.rating,0)+COALESCE(j.sum,0)) / (COALESCE(p.rating/p.rating,0)
+ COALESCE(j.tot,0)) AS rating
FROM product p
LEFT JOIN
(SELECT SUM(rating) AS sum ,
COUNT(rating) AS tot,
product_id FROM rating
WHERE is_admin_rating=FALSE GROUP BY product_id) j
ON (p.product_id = j.product_id) LEFT JOIN product_description pd
ON (p.product_id = pd.product_id) LEFT JOIN product_to_store p2s
ON (p.product_id = p2s.product_id)";
This query then gets appended with a variety of different sort options (rating being the default), in addition to that we also use LIMIT to "paginate" the search results.
Is there a way in to incorporate the weighted ratings into the query? Or will I have to break it up into several queries?
Since this obviously looks like a web-based system, I would strongly suggest a slight denormalization and tacking on 5 columns to the product table for
UserRatings, UserCount, AdminRatings, AdminCount, FinalRating
When any entries are added or updated to the ratings table, you could apply a simple update trigger, something like
update Product p,
( select r.product_id,
sum( is_admin_rating=FALSE, 1, 0 ) as UserCount,
sum( is_admin_rating=FALSE, rating, 0 ) as UserRatings,
sum( is_admin_rating=TRUE, 1, 0 ) as AdminCount,
sum( is_admin_rating=TRUE, rating, 0 ) as AdminRatings
from Ratings r
where r.product_id = ProductIDThatCausedThisTrigger
group by r.product_id ) as PreSum
set p.UserCount = PreSum.UserCount,
p.UserRatings = PreSum.UserRatings,
p.AdminrCount = PreSum.AdminCount,
p.AdminRatings = PreSum.AdminRatings,
p.FinalRating = case when PreSum.UserCount = 0 and PreSum.AdminCount = 0
then 0
when PreSum.UserCount = 0
then PreSum.AdminRatings / PreSum.AdminCount
when PreSum.AdminCount = 0
then PreSum.UserRatings / PreSum.UserCount
else
( PreSum.UserRatings / PreSum.UserCount * .4 )
/ ( PreSum.AdminRatings / PreSum.AdminCount * .6 )
end
where p.product_id = PreSum.product_id
This way, you will never have to do a separate join to the ratings table and do aggregations which will just get slower as more data is accumulated. Then your query can just use the tables and not have to worry about coalesce, your count per each and their ratings will be there.
The case/when for the FinalRatings is basically doing it all wrapped up as the combination of the user counts and admin counts can be 0/0, +/0, 0/+ or +/+
So, if no count for either, the case/when sets rating to 0
if only the user count has a value, just get that average rating (userRatings / userCounts)
if only the admin count has a value, get admin avg rating (adminRatings / adminCounts)
if BOTH have counts, you are taking the respective averages * .4 and * .6 respectively. This would be the one factoring adjustment you might want to tweak.
Although the query itself looks somewhat monstrous and confusing, if you look at the "PreSum" query, you are only doing it for the 1 product that has just been rated and basis from the trigger. Then, a simple update based on the results of that joined by the single product ID.
Getting this to work might offer a better long-term solution for you.

MySQL Need to show 0 when no value in right hand table of join, with cumulative

I have one table with a list of number of sales per month against product code and another with a list of months that can extend before or after the months that had a sale in. I need to results to show 0 sales if there were no sales in the month and for the cumulative to add this up. I have tried using case and if and getting it to put 0 if sales.sales was null but this did not work and I still just had blanks.
create table summary as (SELECT
q1.productid As productid,
q1.date AS Month_View,
q1.sales AS Monthly_Units_Sold,
(#runtot_sales := #runtot_sales + q1.sales) AS Cumulative_Sales
FROM
(SELECT
sales.productid,
dates.date,
if(sales.date is null,0,sales.sales) as sales
from
dates
left join sales on dates.date = sales.date
where
sales.productid = '$input1'
group by dates.date
ORDER BY date) AS q1);
";
Try COALESCE() function to return the first non-NULL value of a list Also see demo here
CREATE TABLE summary AS
(SELECT
q1.productid AS productid,
q1.date AS Month_View,
q1.sales AS Monthly_Units_Sold,
(
#runtot_sales := #runtot_sales + q1.sales
) AS Cumulative_Sales
FROM
(SELECT
sales.productid,
dates.date,
COALESCE(sales.sales, 0) AS sales
FROM
dates
LEFT JOIN sales
ON dates.date = sales.date
WHERE sales.productid = '$input1'
GROUP BY dates.date
ORDER BY DATE) AS q1) ;
MySQL COALESCE() function
You are misusing GROUP BY and therefore getting indeterminate results. See this: http://dev.mysql.com/doc/refman/5.5/en/group-by-extensions.html
If you're aggregating your items by product and date you probably want something like this.
SELECT sales.productid,
dates.date,
SUM(sales.sales) as sales
FROM dates
LEFT JOIN sales ON dates.date = sales.date
WHERE sales.productid = '$input1'
GROUP BY sales.productid, dates.date
ORDER BY /* i'm not sure what you're trying to do with the running total */
Note that SUM(sales.sales) handles the NULL values from your LEFT JOIN correctly. If the date doesn't join a sales row then sales.sales will be NULL.
If you're trying to do a month-by-month summary you need more logic than you have. See this writeup: http://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/

Categories