HAVING clause and performance - mysql

I've got a performance problem with a SQL (MySql) query. Basically I have a table similar to this:
ID PRICE ID OBJECT
-----------------------------
1 500.00 1 1
2 300.00 1 1
3 400.00 1 1
4 100.00 1 1
5 100.00 1 1
6 100.00 2 3
And I need to get the maximum amount of lines given an amount.
For example, given the amount 1000.00 the query must returns these ids (order by price asc) and the total price.
ID PRICE TOTAL_PRICE
---------------------------------
4 100 100.00
5 100 200.00
2 300 500.00
3 400 900.00
Atm I'm using a query similar to the below one:
set #total=0;
select a.id, a.price , #total:=#total + a.price as total_price , a.id_user
from shares a
where a.`id_user` != 0 and a.id_object = 1
having #total < 1000.00 order by a.price asc;
It works fine but it's not efficient. It takes around 1.5 seconds to extract the data (the table has around 1M lines).
The problem is related to the Having clause.
Do you have any suggestions?
Is there a way to perform this type of query without using the clause Having ?

I believe this (cumulative sum) is what you are looking for:
set #total = 0;
SELECT ID,
Price,
Total_Price
FROM (
SELECT a.*
,(#total := #total + price) AS total_price
FROM shares a
WHERE a.id_user != 0
AND a.id_object = 1
ORDER BY a.price
) q1
WHERE q1.total_price < 1000;
SQL Fiddle Demo

Related

Get most recent ROW with a GROUP BY on multiple columns

I'm trying to get the most resent result for every amount linked to a duration.
This is my table :
SELECT * FROM financement;
id amount duration total
8 200 5 result8
7 200 4 result7
6 100 5 result6
5 100 4 result5
4 200 5 result4
3 200 4 result3
2 100 5 result2
1 100 4 result1
There is is a total for every amount linked to a duration.
My problematic is that, they can be multiple row for the same amout x duration and I only want the most recent.
For exemple in my case :
id amount duration total
5 100 4 result5
1 100 4 result1
I tried to use a GROUP BY like this:
SELECT * FROM `financement` GROUP BY amout, duration ORDER BY `id` DESC
But with this method, even with the ORDER BY id DESC, I still get the most ancien ROW.
I also tried to SELECT max() in a sub query like this, but the query is extremely long and it times out.
SELECT * financement where id in (select max(id) from financement group by amount, duration);
How can I get this output ?
id amount duration total
8 200 5 result8
7 200 4 result7
6 100 5 result6
5 100 4 result5
This should work:
SELECT * FROM `financement`
WHERE id IN (
SELECT max(id) as id FROM `financement` GROUP BY duration, amount
)
ORDER BY `id` DESC
I think you want filtering. You can use a scalar subquery:
select f.*
from financement f
where f.id = (select max(f2.id)
from financement f2
where f2.amount = f.amount and f2.duration = f.duration
);
Try this query:
SELECT * FROM financement ORDER BY id DESC
output of this query

configure query to bring rows which have more than 1 entries

How to get those entries which have more than 1 records?
If it doesn't make sense... let me explain:
From the below table I want to access the sum of the commission of all rows where type is joining and "they have more than 1 entry with same downmem_id".
I have this query but it doesn't consider more entries scenario...
$search = "SELECT sum(commission) as income FROM `$database`.`$memcom` where type='joining'";
Here's the table:
id mem_id commission downmem_id type time
2 1 3250 2 joining 2019-09-22 13:24:40
3 45 500 2 egbvegr new time
4 32 20 2 vnsjkdv other time
5 23 2222 2 vfdvfvf some other time
6 43 42 3 joining time
7 32 353 5 joining time
8 54 35 5 vsdvsdd time
Here's the expected result: it should be the sum of the id no 2, 7 only
ie. 3250+353=whatever.
It shouldn't include id no 6 because it has only 1 row with the same downmem_id.
Please help me to make this query.
Another approach is two levels of aggregation:
select sum(t.commission) income
from (select sum(case when type = 'joining' then commission end) as commission
from t
group by downmem_id
having count(*) > 1
) t;
The main advantage to this approach is that this more readily supports more complex conditions on the other members of each group -- such as at most one "joining" record or both "joining" records and no more than two "vnsjkdv" records.
Use EXISTS:
select sum(t.commission) income
from tablename t
where t.type = 'joining'
and exists (
select 1 from tablename
where id <> t.id and downmem_id = t.downmem_id
)
See the demo.
Results:
| income |
| ----- |
| 3603 |
You can use subquery that will find all downmem_id having more than one occurrence in the table.
SELECT Sum(commission) AS income
FROM tablename
WHERE type = 'joining'
AND downmem_id IN (SELECT downmem_id
FROM tablename t
GROUP BY downmem_id
HAVING Count(id) > 1);
DEMO

SQL - Groups rows that share a value in different columns

I have a table that has the following fields
SalesOrderID RelatedOrderID Amount
1 0 5.00
2 1 1.00
3 0 3.00
4 0 20.00
5 4 10.00
I'm looking to write a query that will return the sales order total made up of the original total and the RelatedOrderID total.
SalesOrderID NewAmount
1 6.00
4 30.00
Hope that makes sense...please ask any questions. I'm aware it's a confusing situation!
Thanks,
Mike
You could use union all and then aggregate::
select SalesOrderID, sum(amount) as amount
from ((select SalesOrderID, amount from t) union all
(select RelatedSalesOrderID, amount from t where RelatedSalesOrderID > 0)
) tt
group by SalesOrderID;
For that effect you will have to use something like this
SELECT
least(o.SalesOrderId,r.SalesOrderId) AS SalesOrderId,
o.Amount + r.Amount AS Amount
FROM
orders o
JOIN orders r ON o.SalesOrderId = r.RelatedOrderID;
Thanks both, I've actually worked it out since posting this:
SELECT so1.SalesOrderID,
cast(SUM(so1.Amount+so2.Amount) as money) as NewAmount
FROM SalesOrder so1
JOIN SalesOrder so2
ON so1.SalesOrderID = so2.RelatedOrderID
GROUP BY so1.sorder_code

mysql: two table join with sum

I'm attempting to join two tables and also get a SUM and flailing badly. I need to get the total commission amounts for each affiliate where affiliate.approved=1 AND order.status=3.
//affiliate table
affiliate_id | firstname | lastname | approved |
1 joe shmoe 1
2 frank dimag 0
3 bob roosky 1
here's the order table
//order
affiliate_id | order_status_id | commission
1 3 0.20
1 0 0.30
2 3 0.10
3 3 0.25
1 3 0.25
2 3 0.15
2 0 0.20
and here's what I'd like the query to return:
affiliate_id | commission
1 0.45
3 0.25
Here is my attempt that doesn't work. It outputs just one line.
SELECT order.affiliate_id, SUM(order.commission) AS total, affiliate.firstname, affiliate.lastname FROM `order`, `affiliate` WHERE order.order_status_id=3 AND affiliate.approved=1 AND order.affiliate_id = affiliate.affiliate_id ORDER BY total;
thanks for any help.
You've missed GROUP BY, try this:
SELECT
`order`.affiliate_id,
SUM(`order`.commission) AS total,
affiliate.firstname,
affiliate.lastname
FROM `order`
JOIN `affiliate`
ON `order`.order_status_id = 3 AND affiliate.approved = 1 AND `order`.affiliate_id = affiliate.affiliate_id
GROUP BY `order`.affiliate_id
ORDER BY total;
Demo Here
You can try this Query for your solution :-
SELECT order.affiliate_id, SUM(order.commission) AS total,affiliate.firstname,
affiliate.lastname
FROM `order`, `affiliate`
WHERE order.order_status_id=3
AND affiliate.approved=1
AND order.affiliate_id = affiliate.affiliate_id
GROUP BY order.affiliate_id
ORDER BY total;
Here is the solution:
select affiliate.affiliate_id,sum(`order`.commission) as total from affiliate left join `order` on affiliate.affiliate_id=`order`.affiliate_id
where affiliate.approved=1 and `order`.order_status_id=3 group by affiliate.affiliate_id
In addition,"order" is a key word of SQL , I recommend you not to use it as a table/column name.
First: Remove the implicit join syntax. It's confusing.
Second: You needed to group by affiliate_id. Using aggregate function without group by collapses your result set into a single row.
Here's the query using INNER JOIN:
SELECT
`order`.affiliate_id,
SUM(`order`.commission) AS total,
affiliate.firstname,
affiliate.lastname
FROM `order`
INNER JOIN`affiliate` ON `order`.affiliate_id = affiliate.affiliate_id
WHERE `order`.order_status_id = 3
AND affiliate.approved = 1
GROUP BY affiliate.affiliate_id
ORDER BY total;
WORKING DEMO
Caution: You have picked one of the reserved words of MySQL as table name (order). Be aware to enclose it with (`)backtick always .
Just a gentle reminder

How to make a fake column with an autoincrement number in a "group by" query

I have data in a table like this:
fgid qty ntid
1 100 10
2 90 10
6 200 11
1 80 11
1 120 12
6 100 12
6 30 13
And i make query :
SELECT fgid, SUM(qty) AS total_qty, COUNT(ntid) AS nt_count FROM sofg
GROUP BY fgid
AND the result is :
fgid total_qty nt_count
1 300 3
2 90 1
6 330 3
Then i want to make the result like this :
no fgid total_qty nt_count
1 1 300 3
2 2 90 1
3 6 330 3
How to do that with a query? where 'no' is (like) autoincrement number.
Try this query.
SELECT
#rownum := #rownum + 1 rownum,
t.*
FROM (SELECT #rownum:=0) r,
(
SELECT fgid, SUM(qty) AS total_qty, COUNT(ntid) AS nt_count FROM sofg GROUP BY fgid
) t;
Basically the same as Dhinakaran's answer, but there's no need to put the whole main query into a subquery. There's no difference to his answer appart from maybe being more pleasing to the eye, but please accept Dhinakaran's answer, as he was faster.
SELECT
#rownum:=#rownum + 1 as rownumber,
fgid,
SUM(qty) AS total_qty,
COUNT(ntid) AS nt_count
FROM sofg
, (select #rownum:=0) v
GROUP BY fgid