How to subtract date, then sum the results of the previous operation - mysql

I have a database with table Plane, the table Flight, which is basically a Flight that a Plane did, then for each flight I have solved M:N table which just holds foreign keys for Flight and Destination, 2 entries in table Destination, to know the difference between Departure and Arrival, table Destination has a connection to table Type( Departure and Arrival, which is set in another table, of 2 rows, "Zacetna" = Departure, "Koncna" = Arrival ). Table Destination also has a connection to table Date which contains Datetime.
I want to Subtract Arrival and Departure time for each Flight, then Sum the times if Plane has multiple Flights.
I have tried to just Subtract them all and then sub but that didn't work, then I have tried to Group By, but with no luck.
In the attached picture.
Plane called "Falcon FX 3000" has 2 flights, idFlight 5 and 6. To get the expected result I would have to do
( Destination_6_Date - Destination_5_Date) + (Destination_4_Date - Destination_7_Date)
but none of my solutions worked
SELECT d.Ime, letD.Destinacija_idDestinacija, l.idLet,tip.Tip, t.Termin
FROM Letalo p
INNER JOIN Let l ON l.Letalo_idLetalo = p.idLetalo
INNER JOIN Let_Has_Destinacija letD on letD.Let_idLet = l.idLet
INNER JOIN Destinacija d on d.idDestinacija = letD.Destinacija_idDestinacija
INNER JOIN Termin t on t.idTermin = d.Termin_idTermin
INNER JOIN Tip_Destinacije tip on tip.idTip_Destinacije = Tip_Destinacije_idTip_Destinacije WHERE p.Naziv='Falcon FX 3000';
https://i.imgur.com/5r2G9yI.png
https://i.imgur.com/3CIfbUN.png
Sorry for the ERDiagram for being in the different language
Letalo = Plane
Let = Flight
Let_Has_Destinacija = Solved M:N, holding Flight ID and Destination ID
Destinacija = Destination
Termin = Date
Tip_Destinacija = Type
EDIT
This is the successful GROUP BY query that returns valid calculations, now I just need to sum them. I am guessing this will be done with subquery, but I don't really understand that, as I am new to MySQL
SELECT d.Ime as Name, letD.Destinacija_idDestinacija as Destination, l.idLet as idFlight,tip.Tip as Type, CAST(t.Termin as DATE), timediff(Max(t.Termin), min(t.Termin))
FROM Letalo p
INNER JOIN Let l ON l.Letalo_idLetalo = p.idLetalo
INNER JOIN Let_Has_Destinacija letD on letD.Let_idLet = l.idLet
INNER JOIN Destinacija d on d.idDestinacija = letD.Destinacija_idDestinacija
INNER JOIN Termin t on t.idTermin = d.Termin_idTermin
INNER JOIN Tip_Destinacije tip on tip.idTip_Destinacije = Tip_Destinacije_idTip_Destinacije WHERE p.Naziv='Falcon FX 3000'
GROUP BY l.idLet;
https://i.imgur.com/hUXZDnQ.png
EDIT 2
By using temp tables, I managed to SUM the flights times, but I have to use 2 queries for this, which is not what I want.
DROP TABLE IF EXISTS tempTime;
CREATE TEMPORARY TABLE tempTime (
SELECT timediff(Max(t.Termin), min(t.Termin)) as FlightTime
FROM Letalo p
INNER JOIN Let l ON l.Letalo_idLetalo = p.idLetalo
INNER JOIN Let_Has_Destinacija letD on letD.Let_idLet = l.idLet
INNER JOIN Destinacija d on d.idDestinacija = letD.Destinacija_idDestinacija
INNER JOIN Termin t on t.idTermin = d.Termin_idTermin
INNER JOIN Tip_Destinacije tip on tip.idTip_Destinacije = Tip_Destinacije_idTip_Destinacije WHERE p.Naziv='Falcon FX 3000'
GROUP BY l.idLet);
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(FlightTime))) as FlownTime FROM tempTime;
DROP TABLE IF EXISTS tempTime;
https://i.imgur.com/HlPtgHr.png
ACCEPTED ANSWER
SELECT
`Name`,
`Destination`,
`Type`,
`Date`,
SEC_TO_TIME(SUM(TIME_TO_SEC(`Time_diff`))) as `Tot_Diff`
FROM (
SELECT
d.`Ime` as `Name`,
letD.`Destinacija_idDestinacija` as `Destination`,
l.`idLet` as idFlight,tip.`Tip` as `Type`,
CAST(t.`Termin` as DATE) as `Date`,
timediff(Max(t.`Termin`), min(t.`Termin`)) as `Time_diff`
FROM Letalo p
INNER JOIN `Let` l
ON l.`Letalo_idLetalo` = p.`idLetalo`
INNER JOIN `Let_Has_Destinacija` letD
on letD.`Let_idLet` = l.`idLet`
INNER JOIN `Destinacija` d
on d.`idDestinacija` = letD.`Destinacija_idDestinacija`
INNER JOIN `Termin` t
on t.`idTermin` = d.`Termin_idTermin`
INNER JOIN `Tip_Destinacije` tip
on tip.`idTip_Destinacije` = `Tip_Destinacije_idTip_Destinacije`
WHERE p.Naziv = 'Falcon FX 3000'
GROUP BY l.idLet
) as `main`

I believe this should produce the result you describe. Basically, it takes the query you have, uses it as a sub-query
then sums the time differences.
SELECT
`Name`,
`Destination`,
`Type`,
`Date`,
SEC_TO_TIME(SUM(TIME_TO_SEC(`Time_diff`))) as `Tot_Diff`
FROM (
SELECT
d.`Ime` as `Name`,
letD.`Destinacija_idDestinacija` as `Destination`,
l.`idLet` as idFlight,tip.`Tip` as `Type`,
CAST(t.`Termin` as DATE) as `Date`,
timediff(Max(t.`Termin`), min(t.`Termin`)) as `Time_diff`
FROM Letalo p
INNER JOIN `Let` l
ON l.`Letalo_idLetalo` = p.`idLetalo`
INNER JOIN `Let_Has_Destinacija` letD
on letD.`Let_idLet` = l.`idLet`
INNER JOIN `Destinacija` d
on d.`idDestinacija` = letD.`Destinacija_idDestinacija`
INNER JOIN `Termin` t
on t.`idTermin` = d.`Termin_idTermin`
INNER JOIN `Tip_Destinacije` tip
on tip.`idTip_Destinacije` = `Tip_Destinacije_idTip_Destinacije`
WHERE p.Naziv = 'Falcon FX 3000'
GROUP BY l.idLet
) as `main`
GROUP BY `Name`,`Destination`,`Type`,`Date`;

Related

Is there a method of counting an attribute that is in a GROUP BY clause?

I need have created a select statement to list out all the customers that have been to multiple merchants below.
I want to create another statement to display how many of those customers have been to each merchant.
What is the optimal method of approaching this problem?
Lists out all customers that have been to multiple merchants.
WITH valentinesDayMerchant AS (
SELECT m.MerchantId, m.MerchantGroupId, m.WebsiteName
FROM Merchant m
INNER JOIN OpeningHours oh ON m.MerchantId = oh.MerchantId AND oh.DayOfWeek = 'TUE'
LEFT JOIN devices.DeviceConnectionState AS dcs ON dcs.MerchantId = oh.MerchantId
WHERE MerchantStatus = '-' AND (m.PrinterType IN ('V','O') OR dcs.State = 1 OR dcs.StateTransitionDateTime > '2023-01-23')
)
SELECT DISTINCT ul.UserLoginId, ul.FullName, ul.EmailAddress, ul.Mobile
FROM dbo.UserLogin AS ul
INNER JOIN dbo.Patron AS p ON p.UserLoginId = ul.UserLoginId
INNER JOIN valentinesDayMerchant AS m ON (m.MerchantId = ul.ReferringMerchantId OR m.MerchantId IN (SELECT pml.MerchantId FROM dbo.PatronMerchantLink AS pml WHERE pml.PatronId = p.PatronId AND ISNULL(pml.IsBanned, 0) = 0))
LEFT JOIN (
SELECT mg.MerchantGroupId, mg.MerchantGroupName, groupHost.HostName [GroupHostName]
FROM dbo.MerchantGroup AS mg
INNER JOIN dbo.Merchant AS parent ON parent.MerchantId = mg.ParentMerchantId
INNER JOIN dbo.HttpHostName AS groupHost ON groupHost.MerchantID = parent.MerchantId AND groupHost.Priority = 0
) mGroup ON mGroup.MerchantGroupId = m.MerchantGroupId
LEFT JOIN (
SELECT po.PatronId, MAX(po.OrderDateTime) [LastOrder]
FROM dbo.PatronsOrder AS po
GROUP BY po.PatronId
) orders ON orders.PatronId = p.PatronId
INNER JOIN dbo.HttpHostName AS hhn ON hhn.MerchantID = m.MerchantId AND hhn.Priority = 1
WHERE ul.UserLoginId NOT IN (1,2,100,372) AND ul.UserStatus <> 'D' AND (
ISNULL(orders.LastOrder, '2000-01-01') > '2020-01-01' OR ul.RegistrationDate > '2022-01-01'
)
GROUP BY ul.UserLoginId, ul.FullName, ul.EmailAddress, ul.Mobile
HAVING COUNT(m.MerchantId) > 1
Methods I have tried include adding the merchant name to a group by and displaying the count of the customers, however this does not work as I cannot have anything related to the Merchant in the GROUP BY, or I wouldn't be able to use HAVING clause to identify the customers that have been to multiple merchants. I have also tried selecting all the merchants and counting the distinct customers which doesn't work as it takes into account all the customers, not specifically the customers that have been to multiple merchants only.

SQL UPDATE TABLE from SELECT query from other tables

I have bd hf3 and 5 tables there:
active_preset with columns (id , preset_id)
preset with columns (id , birja_id, trend_id, fractal, interval_up)
birja with columns (id , name)
trend with columns (id , name)
uq_active_preset with columns (id , birja, trend, fractal, interval_up)
In table preset I have a few records. Some of them are in table active_preset by foreign key preset_id. In table active_preset a few records exist once , a few more than once.
I need to update table uq_active_preset with records from table active_preset disregarding repetitions of records if they are present.
I did query from active_preset and it works good:
SELECT
b.name AS birja, p.fractal AS fractal , tre.name AS trend, p.interval_up AS interval_up
FROM hf3.active_preset AS ap
INNER JOIN hf3.preset AS p on p.id = ap.preset_id
INNER JOIN hf3.birja AS b on b.id = p.birja_id
INNER JOIN hf3.trend AS tre on tre.id = p.trend_id
GROUP BY b.name, p.fractal, tre.name, p.interval_up
HAVING COUNT(*) >= 1
But I don't know how to update uq_active_preset
I tried this and it returns syntax error:1064 :
UPDATE hf3.uq_active_preset uap SET
uap.birja = st.birja ,
uap.fractal = st.fractal,
uap.trend = st.trend,
uap.interval_up = st.interval_up,
FROM (SELECT b.name AS birja, p.fractal AS fractal , tre.name AS trend, p.interval_up AS interval_up
from hf3.active_preset AS ap
INNER JOIN hf3.preset AS p on p.id = ap.preset_id
INNER JOIN hf3.birja AS b on b.id = p.birja_id
INNER JOIN hf3.trend AS tre on tre.id = p.trend_id
GROUP BY b.name, p.fractal, tre.name, p.interval_up
HAVING COUNT(*) >= 1
) st
when you make an update using from is like you join the updated table with your query result. So, you need also a where statement in order to tell where those two are connected. Also, don't use alias of your updated table on set statement.
You need something like that:
UPDATE hf3.uq_active_preset uap SET birja=st.birja,fractal=st.fractal,trend=st.trend,interval_up=st.interval_up
FROM (SELECT b.name AS birja, p.fractal AS fractal , tre.name AS trend, p.interval_up AS interval_up
from hf3.active_preset AS ap
INNER JOIN hf3.preset AS p on p.id = ap.preset_id
INNER JOIN hf3.birja AS b on b.id = p.birja_id
INNER JOIN hf3.trend AS tre on tre.id = p.trend_id
GROUP BY b.name, p.fractal, tre.name, p.interval_up
HAVING COUNT(*) >= 1
) st
where uap.fkey=st.fkey

MySQL select with group and one to many relations condition

For example have such structure:
CREATE TABLE clicks
(`date` varchar(50), `sum` int, `id` int)
;
CREATE TABLE marks
(`click_id` int, `name` varchar(50), `value` varchar(50))
;
where click can have many marks
So example data:
INSERT INTO clicks
(`sum`, `id`, `date`)
VALUES
(100, 1, '2017-01-01'),
(200, 2, '2017-01-01')
;
INSERT INTO marks
(`click_id`, `name`, `value`)
VALUES
(1, 'utm_source', 'test_source1'),
(1, 'utm_medium', 'test_medium1'),
(1, 'utm_term', 'test_term1'),
(2, 'utm_source', 'test_source1'),
(2, 'utm_medium', 'test_medium1')
;
I need to get agregated values of click grouped by date which contains all of selected values.
I make request:
select
c.date,
sum(c.sum)
from clicks as c
left join marks as m ON m.click_id = c.id
where
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1') OR
(m.name = 'utm_term' AND m.value='test_term1')
group by date
and get 2017-01-01 = 700, but I want to get 100 which means that only click 1 has all of marks.
Or if condition will be
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1')
I need to get 300 instead of 600
I found answer in getting distinct click_id by first query and then sum and group by date with condition whereIn, but on real database which is very large and has id as uuid this request executes extrimely slow. Any advices how to get it work propely?
You can achieve it using below queries:
When there are the three conditions then you have to pass the HAVING count(*) >= 3
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
OR (
m.NAME = 'utm_term'
AND m.value = 'test_term1'
)
GROUP BY id
HAVING count(*) >= 3
) AS t ON cc.id = t.id
GROUP BY cc.DATE
When there are the three conditions then you have to pass the HAVING count(*) >= 2
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
GROUP BY id
HAVING count(*) >= 2
) AS t ON cc.id = t.id
GROUP BY cc.DATE
Demo: http://sqlfiddle.com/#!9/fe571a/35
Hope this works for you...
You're getting 700 because the join generates multiple rows for the different IDs. There are 3 rows in the mark table with ID=1 and sum=100 and there are two rows with ID=2 and sum=200. On doing the join where shall have 3 rows with sum=100 and 2 rows with sum=200, so adding these sum gives 700. To fix this you have to aggregate on the click_id too as illustrated below:
select
c.date,
sum(c.sum)
from clicks as c
inner join (select * from marks where (name = 'utm_source' AND
value='test_source1') OR (name = 'utm_medium' AND value='test_medium1')
OR (name = 'utm_term' AND value='test_term1')
group by click_id) as m
ON m.click_id = c.id
group by c.date;
DEMO SQL FIDDLE
I found the right way myself, which works on large amounts of data
The main goal is to make request generate one table with subqueries(conditions) which do not depend on amount of data in results, so the best way is:
select
c.date,
sum(c.sum)
from clicks as c
join marks as m1 ON m1.click_id = c.id
join marks as m2 ON m2.click_id = c.id
join marks as m3 ON m3.click_id = c.id
where
(m1.name = 'utm_source' AND m1.value='test_source1') AND
(m2.name = 'utm_medium' AND m2.value='test_medium1') AND
(m3.name = 'utm_term' AND m3.value='test_term1')
group by date
So we need to make as many joins as many conditions we have

Select last distinct records where table1.column1 = table2.column2

I have two tables from the same database:
Table READINGS:
Table SENSORS:
I'm trying to create a query that will return, for each sensor registered in SENSORS, it's last row registered in READINGS. Note that the same sensor can have many registers in READINGS, but ONLY ONE in SENSORS.
The result should be something like:
Dump of the database: https://dl.dropboxusercontent.com/u/85576999/redeSensores.sql
http://sqlfiddle.com/#!9/95818/5
SELECT readings.*
FROM readings
INNER JOIN sensors
ON sensors.idSensor = readings.sensorid
LEFT JOIN readings r
ON r.sensorid = readings.sensorid
AND r.`datetime` > readings.`datetime`
WHERE r.id IS NULL
Or if you need info from both tables:
http://sqlfiddle.com/#!9/95818/6
SELECT readings.*,
sensors.*
FROM readings
INNER JOIN sensors
ON sensors.idSensor = readings.sensorid
LEFT JOIN readings r
ON r.sensorid = readings.sensorid
AND r.`datetime` > readings.`datetime`
WHERE r.id IS NULL
UPDATE AVG last day
http://sqlfiddle.com/#!9/95818/10
SELECT t.*,
AVG(r_avg.temperature),
AVG(r_avg.pollution),
AVG(r_avg.noise),
AVG(r_avg.humidity)
FROM (SELECT readings.*, sensors.*
FROM readings
INNER JOIN sensors
ON sensors.idSensor = readings.sensorid
LEFT JOIN readings r
ON r.sensorid = readings.sensorid
AND r.`datetime` > readings.`datetime`
WHERE r.id IS NULL
) t
LEFT JOIN readings r_avg
ON t.sensorid = r_avg.sensorid
AND r_avg.`datetime` >= DATE_ADD(t.datetime,INTERVAL -1 DAY)
GROUP BY t.id;
UPDATE 2 To filter aggregated records you can use HAVING:
http://sqlfiddle.com/#!9/95818/11
HAVING AVG(r_avg.temperature)!=42

SQL - Multiple many-to-many relations filtering SELECT

These are my tables:
Cadastros (id, nome)
Convenios (id, nome)
Especialidades (id, nome)
Facilidades (id, nome)
And the join tables:
cadastros_convenios
cadastros_especialidades
cadastros_facilidades
The table I'm querying for: Cadastros
I'm using MySQL.
The system will allow the user to select multiple "Convenios", "Especialidades" and "Facilidades". Think of each of these tables as a different type of "tag". The user will be able to select multiple "tags" of each type.
What I want is to select only the results in Cadastros table that are related with ALL the "tags" from the 3 different tables provided. Please note it's not an "OR" relation. It should only return the row from Cadastros if it has a matching link table row for EVERY "tag" provided.
Here is what I have so far:
SELECT Cadastro.*, Convenio.* FROM Cadastros AS Cadastro
INNER JOIN cadastros_convenios AS CadastrosConvenio ON(Cadastro.id = CadastrosConvenio.cadastro_id)
INNER JOIN Convenios AS Convenio ON (CadastrosConvenio.convenio_id = Convenio.id AND Convenio.id IN(2,3))
INNER JOIN cadastros_especialidades AS CadastrosEspecialidade ON (Cadastro.id = CadastrosEspecialidade.cadastro_id)
INNER JOIN Especialidades AS Especialidade ON(CadastrosEspecialidade.especialidade_id = Especialidade.id AND Especialidade.id IN(1))
INNER JOIN cadastros_facilidades AS CadastrosFacilidade ON (Cadastro.id = CadastrosFacilidade.cadastro_id)
INNER JOIN Facilidades AS Facilidade ON(CadastrosFacilidade.facilidade_id = Facilidade.id AND Facilidade.id IN(1,2))
GROUP BY Cadastro.id
HAVING COUNT(*) = 5;
I'm using the HAVING clause to try to filter the results based on the number of times it shows (meaning the number of times it has been successfully "INNER JOINED"). So in every case, the count should be equal to the number of different filters I added. So if I add 3 different "tags", the count should be 3. If I add 5 different tags, the count should be 5 and so on. It works fine for a single relation (a single pair of inner joins). When I add the other 2 relations it starts to lose control.
EDIT
Here is something that I believe is working (thanks #Tomalak for pointing out the solution with sub-queries):
SELECT Cadastro.*, Convenio.*, Especialidade.*, Facilidade.* FROM Cadastros AS Cadastro
INNER JOIN cadastros_convenios AS CadastrosConvenio ON(Cadastro.id = CadastrosConvenio.cadastro_id)
INNER JOIN Convenios AS Convenio ON (CadastrosConvenio.convenio_id = Convenio.id)
INNER JOIN cadastros_especialidades AS CadastrosEspecialidade ON (Cadastro.id = CadastrosEspecialidade.cadastro_id)
INNER JOIN Especialidades AS Especialidade ON(CadastrosEspecialidade.especialidade_id = Especialidade.id)
INNER JOIN cadastros_facilidades AS CadastrosFacilidade ON (Cadastro.id = CadastrosFacilidade.cadastro_id)
INNER JOIN Facilidades AS Facilidade ON(CadastrosFacilidade.facilidade_id = Facilidade.id)
WHERE
(SELECT COUNT(*) FROM cadastros_convenios WHERE cadastro_id = Cadastro.id AND convenio_id IN(1, 2, 3)) = 3
AND
(SELECT COUNT(*) FROM cadastros_especialidades WHERE cadastro_id = Cadastro.id AND especialidade_id IN(3)) = 1
AND
(SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = Cadastro.id AND facilidade_id IN(2, 3)) = 2
GROUP BY Cadastro.id
But I'm concerned about performance. It looks like these 3 sub-queries in the WHERE clause are gonna be over-executed...
Another solution
It joins subsequent tables only if the previous joins were a success (if no rows match one of the joins, the next joins are gonna be joining an empty result-set) (thanks #DRapp for this one)
SELECT STRAIGHT_JOIN
Cadastro.*
FROM
( SELECT Qualify1.cadastro_id
from
( SELECT cc1.cadastro_id
FROM cadastros_convenios cc1
WHERE cc1.convenio_id IN (1, 2, 3)
GROUP by cc1.cadastro_id
having COUNT(*) = 3 ) Qualify1
JOIN
( SELECT ce1.cadastro_id
FROM cadastros_especialidades ce1
WHERE ce1.especialidade_id IN( 3 )
GROUP by ce1.cadastro_id
having COUNT(*) = 1 ) Qualify2
ON (Qualify1.cadastro_id = Qualify2.cadastro_id)
JOIN
( SELECT cf1.cadastro_id
FROM cadastros_facilidades cf1
WHERE cf1.facilidade_id IN (2, 3)
GROUP BY cf1.cadastro_id
having COUNT(*) = 2 ) Qualify3
ON (Qualify2.cadastro_id = Qualify3.cadastro_id) ) FullSet
JOIN Cadastros AS Cadastro
ON FullSet.cadastro_id = Cadastro.id
INNER JOIN cadastros_convenios AS CC
ON (Cadastro.id = CC.cadastro_id)
INNER JOIN Convenios AS Convenio
ON (CC.convenio_id = Convenio.id)
INNER JOIN cadastros_especialidades AS CE
ON (Cadastro.id = CE.cadastro_id)
INNER JOIN Especialidades AS Especialidade
ON (CE.especialidade_id = Especialidade.id)
INNER JOIN cadastros_facilidades AS CF
ON (Cadastro.id = CF.cadastro_id)
INNER JOIN Facilidades AS Facilidade
ON (CF.facilidade_id = Facilidade.id)
GROUP BY Cadastro.id
Emphasis mine
"It should only return the row from Cadastros if it has a matching row for EVERY "tag" provided."
"where there is a matching row"-problems are easily solved with EXISTS.
EDIT After some clarification, I see that using EXISTS is not enough. Comparing the actual row counts is necessary:
SELECT
*
FROM
Cadastros c
WHERE
(SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = c.id AND id IN (2,3)) = 2
AND
(SELECT COUNT(*) FROM cadastros_especialidades WHERE cadastro_id = c.id AND id IN (1)) = 1
AND
(SELECT COUNT(*) FROM cadastros_facilidades WHERE cadastro_id = c.id AND id IN (1,2)) = 2
The indexes on the link tables should be (cadastro_id, id) for this query.
Depending on the size of the tables (records), WHERE-based subqueries, running a test on every row CAN SIGNIFICANTLY hit performance. I have restructured it which MIGHT better help, but only you would be able to confirm. The premise here is to have the first table based on getting distinct IDs that meet the criteria, join THAT set to the next qualifier criteria... joined to the FINAL set. Once that has been determined, use THAT to join to your main table and its subsequent links to get the details you are expecting. You also had an overall group by by the ID which will eliminate all other nested entries as found in the support details table.
All that said, lets take a look at this scenario. Start with the table that would be EXPECTED TO HAVE THE LOWEST RESULT SET to join to the next and next. if cadastros_convenios has IDs that match all the criteria include IDs 1-100, great, we know at MOST, we'll have 100 ids.
Now, these 100 entries are immediately JOINED to the 2nd qualifying criteria... of which, say it only matches ever other... for simplicity, we are now matched on 50 of the 100.
Finally, JOIN to the 3rd qualifier based on the 50 that qualified and you get 30 entries. So, within these 3 queries you are now filtered down to 30 entries with all the qualifying criteria handled up front. NOW, join to the Cadastros and then subsequent tables for the details based ONLY on the 30 that qualified.
Since your original query would eventually TRY EVERY "ID" for the criteria, why not pre-qualify it up front with ONE query and get just those that hit, then move on.
SELECT STRAIGHT_JOIN
Cadastro.*,
Convenio.*,
Especialidade.*,
Facilidade.*
FROM
( SELECT Qualify1.cadastro_id
from
( SELECT cc1.cadastro_id
FROM cadastros_convenios cc1
WHERE cc1.convenio_id IN (1, 2, 3)
GROUP by cc1.cadastro_id
having COUNT(*) = 3 ) Qualify1
JOIN
( SELECT ce1.cadastro_id
FROM cadastros_especialidades ce1
WHERE ce1.especialidade_id IN( 3 )
GROUP by ce1.cadastro_id
having COUNT(*) = 1 ) Qualify2
ON Qualify1.cadastro_id = Qualify2.cadastro_id
JOIN
( SELECT cf1.cadastro_id
FROM cadastros_facilidades cf1
WHERE cf1.facilidade_id IN (2, 3)
GROUP BY cf1.cadastro_id
having COUNT(*) = 2 ) Qualify3
ON Qualify2.cadastro_id = Qualify3.cadastro_id ) FullSet
JOIN Cadastros AS Cadastro
ON FullSet.Cadastro_id = Cadastro.Cadastro_id
INNER JOIN cadastros_convenios AS CC
ON Cadastro.id = CC.cadastro_id
INNER JOIN Convenios AS C
ON CC.convenio_id = C.id
INNER JOIN cadastros_especialidades AS CE
ON Cadastro.id = CE.cadastro_id
INNER JOIN Especialidades AS E
ON CE.especialidade_id = E.id
INNER JOIN cadastros_facilidades AS CF
ON Cadastro.id = CF.cadastro_id
INNER JOIN Facilidades AS F
ON CF.facilidade_id = F.id