MariaDB - sql query performance or optimization - mysql

I have a stored procedure. A little problematic in terms of performance. I want to improve the performance of the stored procedure, but I could not figure out what to do. There are approximately 3 million records in my database. When I run this query one by one, it's good in performance. But when 150 people run this stored procedure at the same time, there are spikes in the CPU.
As an example, I created my procedure and table structures.
My Stored Procedure:
BEGIN
SELECT ss.car_route from person o
inner join car_time ss on ss.inst_id =o.inst_id
and ss.start_time<=DATE_FORMAT(CURTIME(),'%H:%i') AND ss.finish_time>= date_format(curtime() ,'%H:%i') AND ss.car_id=carid
and ss.days like concat('%',(select WEEKDAY(now())+1),'%')
where (o.car_id=carid or o.back_car_id=carid ) LIMIT 1 into #route_;
select sf.stop_service from car_comp sf
inner join cars s on s.inst_id = sf.id and s.id=carid and s.active=1 limit 1
into #stop_ser;
if #route_ = 1 and #stop_ser=0 THEN
select DISTINCT ss.start_time,ss.finish_time ,o.id,o.name,r.photo, oh.state ,oh.datee,ss.car_route,
ifnull(bh.id,0) AS called,
ifnull(mh.excuse_id,0) AS excuse_id,
ifnull(o.latitude_1,0) AS latitude_1,
ifnull(o.longitude_1,0) AS longitude_1,
ifnull(o.latitude_2,0) AS latitude_2,
ifnull(o.longitude_2,0) AS longitude_2,
case when (ifnull(o.call_notify,0)=1 or ifnull(o.mes_notify,0)=1) then 1 else 0 end AS call_notify ,
ifnull(o.rownumber,0) AS rownumber,
ifnull(o.number_1,0) AS number_1,
ifnull(o.number_2,0) AS number_2,
ifnull(o.brownumber,0) AS brownumber,
ifnull(ROUND(o.notify_meter_1/2),0) AS notify_meter_1,
ifnull(ROUND(o.notify_meter_2/2),0) AS notify_meter_2
from person o
inner join car_time ss on ss.inst_id =o.inst_id and o.car_id=ss.car_id
and ss.start_time<=DATE_FORMAT(CURTIME(),'%H:%i') AND ss.finish_time>= date_format(curtime() ,'%H:%i')
and ss.days like concat('%',(select WEEKDAY(now())+1),'%')
LEFT JOIN notify_records bh ON bh.table_id=o.id AND bh.car_route=#route_
and bh.table_name='person' AND bh.notify=4 AND bh.car_id=o.car_id and bh.date_ >= CURDATE() and bh.date_ < CURDATE() + INTERVAL 1 DAY
left join person_records oh on oh.person_id=o.id
and oh.car_id=o.car_id
and date_format(oh.datee,'%H:%i') >=ss.start_time
and date_format(oh.datee,'%H:%i') <=ss.finish_time
AND oh.car_route= #route_
and
oh.id in(select max(id) from person_records
where date_time >= CURDATE() and date_time < CURDATE() + INTERVAL 1 DAY and car_id = carid and car_id = carid
GROUP by person_id
)
left join inst ok on o.inst_id = ok.id and o.car_id=carid
left join excuse_records mh on mh.person_id=o.id and mh.date_time >= CURDATE() and mh.date_time < CURDATE() + INTERVAL 1 DAY and (mh.car_route=ss.car_route)
left join photo_ r on r.table_id = o.id and r.table_name = 'person'
where
(ss.car_route=o.cars_route_ or o.cars_route_=3) and
o.car_id = carid and o.active=1
AND o.work_time=ss.work_time;
elseif #route_ = 2 and #stop_ser=0 then
select DISTINCT ss.start_time,ss.finish_time ,o.id,o.name,r.photo, oh.state ,oh.datee,ss.car_route,
ifnull(bh.id,0) AS called,
ifnull(mh.excuse_id,0) AS excuse_id,
ifnull(o.latitude_1,0) AS latitude_1,
ifnull(o.longitude_1,0) AS longitude_1,
ifnull(o.latitude_2,0) AS latitude_2,
ifnull(o.longitude_2,0) AS longitude_2,
case when (ifnull(o.call_notify,0)=1 or ifnull(o.mes_notify,0)=1) then 1 else 0 end AS call_notify ,
ifnull(o.rownumber,0) AS rownumber,
ifnull(o.number_1,0) AS number_1,
ifnull(o.number_2,0) AS number_2,
ifnull(o.brownumber,0) AS brownumber,
ifnull(ROUND(o.notify_meter_1/2),0) AS notify_meter_1,
ifnull(ROUND(o.notify_meter_2/2),0) AS notify_meter_2
from person o
inner join car_time ss on ss.inst_id =o.inst_id and o.back_car_id=ss.car_id
and ss.start_time<=DATE_FORMAT(CURTIME(),'%H:%i') AND ss.finish_time>= date_format(curtime() ,'%H:%i')
and ss.days like concat('%',(select WEEKDAY(now())+1),'%')
LEFT JOIN notify_records bh ON bh.table_id=o.id AND bh.car_route=#route_
and bh.table_name='person' AND bh.notify=4 AND bh.car_id=o.back_car_id and bh.date_ >= CURDATE() and bh.date_ < CURDATE() + INTERVAL 1 DAY
left join person_records oh on oh.person_id=o.id
and oh.car_id=o.back_car_id and oh.car_route=2
and date_format(oh.datee,'%H:%i') >=ss.start_time
and date_format(oh.datee,'%H:%i') <=ss.finish_time
AND oh.car_route= #route_
and
oh.id in (select max(id) from person_records
where date_time >= CURDATE() and date_time < CURDATE() + INTERVAL 1 DAY and car_id = carid
GROUP by person_id
)
left join inst ok on o.inst_id = ok.id and o.car_id=carid
left join excuse_records mh on mh.person_id=o.id and mh.date_time >= CURDATE() and mh.date_time < CURDATE() + INTERVAL 1 DAY and (mh.car_route=ss.car_route)
left join photo_ r on r.table_id = o.id and r.table_name = 'person'
where
(ss.car_route=o.cars_route_ or o.cars_route_=3) and
o.back_car_id = carid and o.active=1
AND o.work_time=ss.work_time;
END IF;
end
I have a database example here.
I made my.cnf improvement but still have difficulties with performance. What is wrong with this query? What can I change?
Thank you from now.
Edit:
Server version: 10.1.41-MariaDB - MariaDB Server
I have indexes. I forgot to add indexes while creating test data.

What the heck is this?
ss.days like concat('%',(select WEEKDAY(now())+1),'%')
It can at least be sped up by changing to
ss.days like concat('%',WEEKDAY(now()),'%')
And, won't that lead to checking against 2, 21, 20, 12, ... if the WEEKDAY is "2"?
These might be useful for ss:
(car_id, inst_id, start_time)
(inst_id, car_id, finish_time)
LIMIT 1 without ORDER BY leads to some random row being returned? Is the LIMIT redundant? Or is an ORDER BY needed?
Suggest you get some timings -- It is not obvious which of the SELECTs is chewing up the most CPU.
If the PRIMARY KEY of cars is id, then why test for inst_id and active? Yikes! You don't seem to have a PK for cars! Please verify that every table has a PK.
Redundant:
and car_id = carid
and car_id = carid
Why twice? And what tables are those columns in? Please qualify columns so we can understand what is going on.
When #stop_ser=0, the procedure does nothing? In which case, perform that test first, so you can avoid computing #route.
Change start_time to datatype TIME; then you can get rid of DATE_FORMAT in
and ss.start_time<=DATE_FORMAT(CURTIME(),'%H:%i')
AND ss.finish_time>= date_format(curtime() ,'%H:%i')
Also, beware of the inequality tests, it may lead to some edge cases you did not want.
Don't use (m,n) on FLOAT (eg, float(11,7)); it does rounding that is unnecessary. Also, you can't get 7 decimal places for lat/lng except very near the equator and longitude=0. More on precision: http://mysql.rjweb.org/doc.php/latlng#representation_choices
After you have cleaned up those and provided the requested info, I will take another look.

Related

MySql Declaration and Use of Select Variables

I'm struggling to write a query in the correct way. Although it does work I'm given the error:
A new statement was found but no delimiter between it and the previous one (near select)
Obviously I can still work with this error but #1. I don't particularly like 'hacky' methods of coping and #2. It's causing problems in php further down the line trying to run the query.
From Google Research it suggests that the error may be a bug however I'm dubious to believe this.
What I'm Actually Trying To Do :
I have 3 Tables:
Table P [Process_ID+, Process_Name, Risk_ID*]
Table V [Validation_ID, Process_ID+, Validation_Date]
Table R [Risk_ID*, Risk_TimePeriod]
The plan is to:
Select Process Details
Join Validation Details
Join Risk Details
Generate a "Due_Date" Based on Validation_Date and Risk_TimePeriod (i.e. If Time Period is 150 then Add 150 Days to Validation_Date).
Something like the below:
Process_ID | Process_Name | Validation_Date | Due_Date | Due_Days | Risk_Level
1 My_Process 2017-02-17 2017-07-17 -150 High
So here; the Due_Date has been created by adding 150 (Risk_Level) to the Validation Date.
Current Query
Set #TimePeriod =
(Select r.Risk_TimePeriod
from processes_active p
inner join processes_risk_config r
on r.Risk_ID = p.Process_Risk
where p.Process_ID = 2);
Select p.Process_ID,
p.Process_Name,
v.Validation_Date,
Date_Add(v.Validation_Date, interval #TimePeriod Day) as Due_Date,
Datediff(Now(), Date_Add(v.Validation_Date, interval #TimePeriod Day)) as Due_Days,
r.Risk_Level
From processes_active p
left JOIN processes_validations v
on p.Process_ID = v.Validation_Process_ID
inner join processes_risk_config r
on r.Risk_ID = p.Process_Risk
Where p.Process_ID = 2
Order By v.Validation_Date Desc
My First "Select" clause is highlighted red. This is where the error message appears. If anyone can point me as to where I've gone wrong that would be greatly appreciated! Thanks
Turns out I was over-complicating this completely!
New Working Query :
Select
p.Process_ID,
p.Process_Name,
v.Validation_Date,
r.Risk_TimePeriod,
Date_Add(v.Validation_Date, interval r.Risk_TimePeriod Day) as Due_Date,
Datediff(Now(), Date_Add(v.Validation_Date, interval r.Risk_TimePeriod Day)) as Due_Days,
r.Risk_Level
From pdc_processes.processes_active p
left JOIN pdc_processes.processes_validations v
on p.Process_ID = v.Validation_Process_ID
inner join processes_risk_config r
on r.Risk_ID = p.Process_Risk
Where p.Process_ID = 2
Order By v.Validation_Date Desc

Mysql replace column value with other column value

I have 2 tables:
table: transaction:
====================
id billed_date amount
1 2016-09-30 5
2 2016-10-04 15
3 2016-10-06 10
table: report_date
====================
transaction_id report_date
1 2016-10-01
I want:
Create a report which sum all transactions's amount in October 2016
Base on report date, not billed date
When report date is not set, it's base on billed_date
In above example, I want result is 30 (not 25)
Then I write:
The First:
SELECT
sum(t.amount),
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END AS new_date
FROM
transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
WHERE new_date BETWEEN '2016-10-01' AND '2016-10-30'
The Second:
SELECT sum(amount) FROM
(SELECT t.amount,
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END AS date
FROM transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
) t
WHERE t.date BETWEEN '2016-10-01' AND '2016-10-30'
Result:
The First:
Unknown column 'new_date' in 'where clause'
If I replace 'new_date' by 'date': result = 25 (exclude id=1)
The Second:
result = 30 => Correct, but in my case, when transaction table have about 30k records, the process is too slow.
Anybody can help me?
First of all - the part
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END
can be written shorter as
COALESCE(d.report_date, t.billed_date)
or as
IFNULL(d.report_date, t.billed_date)
In your first query you are using a column alias in the WHERE clause, wich is not allowed. You can fix it by moving the expression behind the alias to the WHERE clause:
SELECT sum(t.amount)
FROM transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
WHERE COALESCE(d.report_date, t.billed_date) BETWEEN '2016-10-01' AND '2016-10-30'
This is almost the same as your own solution.
Your second query is slow because MySQL has to store the subquery result (30K rows) into a temporary table. Trying to optimize it, you will end up with the same solution above.
However if you have indexes on transaction.billed_date and report_date.report_date this query still can not use them. In order to use the indexes, you can split the query into two parts:
Entries with a report (will use report_date.report_date index):
SELECT sum(amount)
FROM transaction t JOIN report_date d ON id = transaction_id
WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30'
Entries without a report (will use transaction.billed_date index):
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE d.report_date IS NULL AND t.billed_dateBETWEEN '2016-10-01' AND '2016-10-30'
Both queries can use an index. You just need to sum the results, wich can also be done combining the two queries:
SELECT (
SELECT sum(amount)
FROM transaction t JOIN report_date d ON id = transaction_id
WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30'
) + (
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE d.report_date IS NULL AND t.billed_dateBETWEEN '2016-10-01' AND '2016-10-30'
) AS sum_amount
I finally find out the solution with the help from my brother:
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE (report_date BETWEEN '2016-10-01' AND '2016-10-30') OR (report_date IS NULL AND billed_date BETWEEN '2016-10-01' AND '2016-10-30')
Thank you for caring me!
Is fill table: report_date with absent values from table: transaction: the case?
SELECT id FROM report_date WHERE report_date BETWEEN '2016-10-01' AND '2016-10-30';
INSERT INTO report_date SELECT id, billed_date FROM transaction WHERE billed_date BETWEEN '2016-10-01' AND '2016-10-30' AND id NOT IN (ids_from previous_query);
SELECT sum(t.amount) FROM transaction LEFT JOIN report_date d ON (t.id = d.transaction_id) WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30';
Your Second Query is correct,no need to re-write query. But I have one thing to tell you, which will help you a lot when dealing with thousand/millions of records. We have focus on some other things too. Because when your table contains large amount of data(in thousands and millions) of records then it takes time to execute query. It may causes locking also, might be query lock or database gone away kind of issue. To avoid this issue,you just create INDEX of one column. Create INDEX on that column which act/use on where clauses. Like in your case you can create INDEX on billed_date column from transaction table. Because your result is based on transaction table. For more details how to create index in mysql/phpmyadmin you can take reference from this http://www.yourwebskills.com/dbphpmyadmintable.php link.
I had been faced same issue at some point of time then I created INDEX on column. Now I am dealing with millions of records using mysql.

Mysql Group Join Optimization Issue

I'm trying to optimize this query it returns multiple rows from the building_rent_prices and the building_weather and then groups them and calculates the average of their field. So far the tables are all under a million rows yet it takes several seconds, does anyone know how i could optimize this from composite indexes or rewriting the query? I'm assuming it should be able to be a 100ms or quicker query but so far it seems like it cant
SELECT b.*
, AVG(r.rent)
, AVG(w.high_temp)
FROM buildings b
LEFT
JOIN building_rent_prices r
ON r.building_id = b.building_id
LEFT
JOIN building_weather w
ON w.building_id = b.building_id
WHERE w.date BETWEEN CURDATE() AND CURDATE + INTERVAL 4 DAY
AND r.date BETWEEN CURDATE() AND CURDATE + INTERVAL 10 day
GROUP
BY b.building_id
ORDER
BY AVG(r.rent) / b.square_feet DESC
LIMIT 10;
Explain said the following:
1 SIMPLE building_rent_prices range
1 SIMPLE buildings eq_ref
1 SIMPLE building_weather ref
Using where; Using index; Using temporary; Using filesort
Using where
Using where; Using index
Im working on some test data heres the create table
CREATE TABLE building(
building_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
square_feet INT
);
CREATE TABLE building_weather(
building_weather_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
high_temp INT
);
CREATE TABLE building_rates(
building_rate_id INT PRIMARY KEY AUTO_INCREMENT,
building_id INT,
weather_date DATE,
rate double
);
ALTER TABLE building_rates INDEX(building_id);
ALTER TABLE buildings INDEX(building_id);
ALTER TABLE building_weather INDEX(building_id);
This seems to working in under 1 second based on DRapp's answer without indexes(I still need to test that its valid)
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + 10
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from building_weather
where date BETWEEN CURDATE() AND CURDATE() + 10
group by building_id) BW
on BRP.building_id = BW.building_id
GROUP BY BRP.building_id
ORDER BY BRP.avgRent / 1 DESC
LIMIT 10;
Let's take a look at this query in detail. You want to report two different kinds of averages for each building. You need to compute those in separate subqueries. If you don't you'll get the cartesian combinatorial explosion.
One is an average of eleven days' worth of rent prices. You get that data with this subquery:
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
This subquery can be optimized by a compound covering index on building_rent_prices, consisting of (date, building_id, rent).
The next is an average of five days' worth of temperature.
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
This can be optimized by a compound covering index on building_weather, consisting of (date, building_id, high_temp).
Finally, you need to join these two subqueries to your buildings table to generate the final result set.
SELECT buildings.*, a.rent, b.high_temp
FROM buildings
LEFT JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
LEFT JOIN (
SELECT building_id, AVG(high_temp) high_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10
Once the two subqueries are optimized, this one doesn't need anything except the building_id primary key.
In summary, to speed up this query, create the two compound indexes mentioned on the building_rent_prices and building_weather queries.
Don't use CURDATE + 4:
mysql> select CURDATE(), CURDATE() + 30, CURDATE() + INTERVAL 30 DAY;
+------------+----------------+-----------------------------+
| CURDATE() | CURDATE() + 30 | CURDATE() + INTERVAL 30 DAY |
+------------+----------------+-----------------------------+
| 2015-03-15 | 20150345 | 2015-04-14 |
+------------+----------------+-----------------------------+
Add INDEX(building_id) to the second and third tables.
If those don't fix it; come back with a revised query and schema, and I will look deeper.
First, your query to the WEATHER based table is only for 4 days, the RENT PRICES table is for 10 days. Since you don't have any join correlation between the two, you will result in a Cartesian result of 40 records per one building ID. Was that intentional or just not identified as an oops...
Second, I would adjust the query as I have below, but also, I have adjusted BOTH WEATHER and RENT PRICES tables to reflect the same date range period. I start with an sub query of just the prices and group by building and date, then join to buildings, then another sub query to weather grouped by building and date. But here, I join from the rent prices sub query to the weather sub query on both building ID AND date so it will at most retain a 1:1 ratio. I don't know why weather is even a consideration spanning date ranges.
However to help with indexes, I would suggest the following
Table Index on
buildings (Building_ID) <-- probably already exists as a PK
building_rent_prices (date, building_id, rent)
building_weather (date, building_id, hi_temp)
The purpose of the index is to take advantage of the WHERE clause (date first), THEN the GROUP BY ( building ID), and is a COVERING INDEX (includes the rent). Similarly for the building weather table for same reasons.
select
B.*,
BRP.avgRent,
BW.avgTemp
from
( select building_id,
AVG( rent ) avgRent
from
building_rent_prices
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id
order by
building_id ) BRP
JOIN buildings B
on BRP.building_id = B.building_id
left join ( select building_id,
AVG( hi_temp ) avgTemp
from
building_weather
where
date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
group by
building_id ) BW
on BRP.building_id = BW.building_id
GROUP BY
BRP.building_id
ORDER BY
BRP.avgRent / B.square_feet DESC
LIMIT 10;
CLARIFICATION...
I cant guarantee the execution order, but in essence, the two ( queries ) for BPR and BW aliases, they would be done and executed quickly before any join took place. If you wanted the average across the (in my example) 10 days vs a per-day join, then I have removed the "date" as a component of the group, so each will return respectively at most, 1 per building.
Now, joining to the building table on just the 1:1:1 ratio will limit the records in the final result set. This should take care of your concern of the average over those days in question.
For anyone who has issues similar to mine the solution is to GROUP each table you would like to join using building_id that way you are joining one to one with every average. Ollie Jones query with JOIN rather than LEFT JOIN is the closest answer if you do not want results that don't have data in all tables. Also The main issue I had was that I forgot to place an index on a avg(low_temp) column so the INDEXES. What I learned from this is that if you do an aggregated function in your select it belongs in your indexes. I added low_temp to it.
building_weather (date, building_id, hi_temp, low_temp) AS suggested by Ollie and DR APP
ALTER TABLE building_weather ADD index(date, building_id, hi_temp, low_temp);
SELECT buildings.*, a.rent, b.high_temp, b.low_temp
FROM buildings
JOIN (
SELECT building_id, AVG(rent) rent
FROM building_rent_prices
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
GROUP BY building_id
) AS a ON buildings.building_id = a.building_id
JOIN (
SELECT building_id, AVG(high_temp) high_temp, AVG(low_temp) low_temp
FROM building_weather
WHERE date BETWEEN CURDATE() AND CURDATE() + INTERVAL 4 DAY
GROUP BY building_id
) AS b ON buildings.building_id = b.building_id
ORDER BY a.rent / buildings.square_feet DESC
LIMIT 10

MySQL - get the nearest date and show it for every row in table

I have 2 tables - Equipments and Reservations.
Equipment table looks like this:
ID NAME
1 termo
2 hydro
3 force
4 hammer
Reservation table:
ID EQUIPMENT_ID START END
1 2 25.3.2015 26.3.2015
2 2 26.3.2015 27.3.2015
3 1 28.3.2015 29.3.2015
4 3 27.3.2015 28.3.2015
I want to be able to print all equipments and the nearest date in the future from the current date for each equipment.
So the output looks like this when the date is (24.3.2015):
NAME START END
termo 28.3.2015 29.3.2015
hydro 25.3.2015 26.3.2015
force 27.3.2015 28.3.2015
hammer null null
I tried some nested queries, not functioning. To find the nearest future date from the current date is that i know
SELECT start FROM reservations WHERE start > NOW() ORDER BY start
LIMIT 1
but i dont know how to join the tables to find the nearest date for each equipment. Any ideas?
I think a correlated subquery is the easiest way to do this. However, you want three columns from the reservation table, so let's get the id and join in for the rest:
select e.*,
(select r.id
from reservations r
where r.equipment_id = e.id and r.start > now()
order by r.start asc
limit 1
) as next_resid
from equipment e;
The join then requires a subquery:
select e.name, r.start, r.end
from (select e.*,
(select r.id
from reservations r
where r.equipment_id = e.id and r.start > now()
order by r.start asc
limit 1
) as next_resid
from equipment e
) er join
reservations r
on er.next_resid = r.id;
Note that end is not a good name for a column. Although it is not a MySQL reserved word, it is a SQL keyword.

Select ... CASE WHEN ... ORDER BY alias

Here are my tables:
Table PROGRAMME prg
prg_id
ln1_id
pmt_id
prg_commission
dep_id
Table COMMISSION com
com_id
pmt_id
ln1_id
dep_id
com_commission
Table PROMOTEUR pmt
pmt_id
pmt_txcommission
I need to get the "commission" of the table programme
But when it's null or empty (prg.commission), i need to get the "commission" value from the table "Commission" (com.commission - by the ln1_id, pmt_id and dep_id matching from both table).
If there is no result matching (no result in table COMMISSION where com.ln1_id = prg.ln1_id AND com.pmt_id = prg.ln1_id and com.dep_id = prg.dep_id) i need to get the "Commission" of PROMOTEUR (pmt.commission)
I don't really have idea how to do it in Sql... it would be easier in PHP condition but i have to do it on MySQL because after getting the good value of "Commission" for each of my programmes, i need to ORDER them by ASC...
I'm not sure if i'm easily understandable (english not my native language). Here is an exemple of what i tried (not successfully sadly) because there is a bit too much condition for me !
SELECT prg.commision AS commission, pmt.commission AS commission, com.commission AS commission
FROM (((PROGRAMME prg
LEFT JOIN LOINIVEAU1 ln1 ON ln1.ln1_id = prg.ln1_id)
LEFT JOIN PROMOTEUR pmt ON pmt.pmt_id = prg.pmt_id)
LEFT JOIN COMMISSION com ON com.pmt_id = pmt.pmt_id)
WHERE
CASE prg.comission != null
THEN prg.comission
ELSE CASE com.commission != null
THEN com.commission
ELSE pmt.commission
THEN pmt.commission
ORDER BY commission ASC
Is this what you want:
SELECT COALESCE(prg.commission, com.commission, pmt.commission) resolved_commission
FROM PROGRAMME prg
LEFT JOIN COMMISSION com
ON com.pmt_id = prg.pmt_id
AND com.ln1_id = prg.ln1_id
AND com.dep_id = prg.dep_id
LEFT JOIN PROMOTEUR pmt
ON pmt.pmt_id = prg.pmt_id
ORDER BY resolved_commission
I have taken the following steps:
Removed the LEFT JOIN to LOINIVEAU1 as this does not appear to be necessary
Updated the other LEFT JOIN conditions to reflect those in your description
Used COALESCE to return the first not null value from the comma-separated columns.. this replaces the CASE statement that you have incorrectly placed in the WHERE clause.
Removed the unnecessary parentheses.
As another pointer.. never use != NULL use IS NOT NULL instead.. as it turns out I did not need this in my solution.
UPDATE
Following further information:
SELECT CASE
WHEN prg.commission > 0
AND prg.commissionstart >= CURDATE()
AND prg.commissionend < CURDATE() + INTERVAL 1 DAY
THEN prg.commission
WHEN com.commission > 0
THEN com.commission
WHEN pmt.commission > 0
THEN pmt.commission
ELSE 0 /* Whatever you want, probably 0 */
END resolved_commission
FROM PROGRAMME prg
JOIN PROGRAMME_DEPARTMENT prg_dep
ON prg_dep.prg_id = prg.id /* Guessing the JOIN here */
LEFT JOIN COMMISSION com
ON com.pmt_id = prg.pmt_id
AND com.ln1_id = prg.ln1_id
AND com.dep_id = prg_dep.dep_id
LEFT JOIN PROMOTEUR pmt
ON pmt.pmt_id = prg.pmt_id
AND pmt.commissionstart >= CURDATE()
AND pmt.commissionend < CURDATE() + INTERVAL 1 DAY
ORDER BY resolved_commission
Should get you a little closer..
You could replace x > 0 with x IS NOT NULL and x > 0 in the CASE for added clarity.
I would also seriously consider placing all the commissions in the COMMISSION table with a start and end and replacing prg.commission* and pmt.commission* with a link through an intermediate to this table.. this way you can rid yourself of all the 0 values and use a LEFT JOIN with COALESCE to get the resolved_commission.
PERFORMANCE TWEAKS
Use EXPLAIN [EXTENDED] ... to see how your query is being executed and play around with composite indexing combinations of the following columns on each table:
PROGRAMME: ([id,], pmt_id, ln1_id, commission, commissionstart, commissionend)
PROGRAMME_DEPARTMENT: (pmt_id, ln1_id, dep_id)
COMMISSION: (pmt_id, ln1_id, dep_id, commission)
PROMOTEUR: (pmt_id, commission, commissionstart, commissionend)