MySQL: INNER JOIN query in an EITHER-OR situation - mysql

I need to find all Users that belong to a certain Country (ID = 4321). But a User's Location can have...
EITHER a City
OR a Cityaddition, as you can see in...
The database schema:
User -< Locations >- City >- District >- Region >- Country
>- Cityaddition >- District >- Region >- Country
Legend: (read from left to right)
-< : "...has many..."
>- : "...has one..."
How do I have to modify my query below in order to make this EITHER-OR condition work in 1 single query?
(Currently, this query only selects Users that have BOTH a City AND a Cityaddition, which is never the case in reality.)
SELECT users.* FROM users
INNER JOIN locations ON locations.user_id = users.id
INNER JOIN cities ON cities.id = locations.city_id
INNER JOIN districts ON districts.id = cities.district_id
INNER JOIN regions ON regions.id = districts.region_id
INNER JOIN cityadditions ON cityadditions.id = locations.cityaddition_id
INNER JOIN districts
districts_cityadditions ON districts_cityadditions.id = cityadditions.district_id
INNER JOIN regions
regions_districts ON regions_districts.id = districts_cityadditions.region_id
WHERE (regions.country_id = 4321)

As a single query, you can do based on a parallel hierarchy and doing a logical NOT NULL on either side. Two separate via would be better as Barmar indicates due to the explicit OR.
Also, use aliases to shorten and make more readable and you can remove all those ticks. You typically only need those when dealing with column names that are also reserved words.
SELECT
u.*
FROM
users u
JOIN locations l
ON u.id = l.user_id
LEFT JOIN cities c
ON l.city_id = c.id
JOIN districts d
ON c.district_id = d.id
INNER JOIN regions r
ON d.region_id = r.id
AND r.country_id = 4321
LEFT JOIN cityadditions ca
ON l.cityaddition_id = ca.id
JOIN districts d2
ON ca.district_id = da.id
JOIN regions r2
ON d2.region_id = r2.id
AND r2.country_id = 4321
where
r.id IS NOT NULL
OR r2.id IS NOT NULL

Using UNION, but minimising code repetition, and the number of redundant joins...
The planner is also able to use indexing to minimise query cost
SELECT DISTINCT -- all queries MAY need this, depending on the structure of your data
`users`.*
FROM
`regions`
INNER JOIN
`districts`
ON `districts`.`region_id` = `regions`.`id`
INNER JOIN
(
SELECT
`locations`.`id`,
`cities`.`district_id`
FROM
`locations`
INNER JOIN
`cities`
ON `cities`.`id` = `locations`.`city_id`
UNION ALL
SELECT
`locations`.`id`,
`cityadditions`.`district_id`
FROM
`locations`
INNER JOIN
`cityadditions`
ON `cityadditions`.`id` = `locations`.`cityaddition_id`
)
AS `locations`
ON `locations`.`district_id` = `district`.`id`
INNER JOIN
`users`
ON `users`.`location_id` = `locations`.`id`
WHERE
`regions`.`country_id` = 4321

Related

Improve MySql query left outer joins with subquery

We are maintaining a history of Content. We want to get the updated entry of each content, with create Time and update Time should be of the first entry of the Content. The query contains multiple selects and where clauses with so many left joins. The dataset is very huge, thereby query is taking more than 60 seconds to execute. Kindly help in improving the same. Query:
select * from (select * from (
SELECT c.*, initCMS.initcreatetime, initCMS.initupdatetime, user.name as partnerName, r.name as rightsName, r1.name as copyRightsName, a.name as agelimitName, ct.type as contenttypename, cat.name as categoryname, lang.name as languagename FROM ContentCMS c
left join ContentCategoryType ct on ct.id = c.contentType
left join User user on c.contentPartnerId = user.id
left join Category cat on cat.id = c.categoryId
left join Language lang on lang.id = c.languageCode
left join CopyRights r on c.rights = r.id
left join CopyRights r1 on c.copyrights = r1.id
left join Age a on c.ageLimit = a.id
left outer join (
SELECT contentId, createTime as initcreatetime, updateTime as initupdatetime from ContentCMS cms where cms.deleted='0'
) as initCMS on initCMS.contentId = c.contentId WHERE c.deleted='0' order by c.id DESC
) as temp group by contentId) as c where c.editedBy='0'
Any help would be highly appreciated. Thank you.
Just a partial eval and suggestion because your query seems non properly formed
This left join seems unuseful
FROM ContentCMS c
......
left join (
SELECT contentId
, createTime as initcreatetime
, updateTime as initupdatetime
from ContentCMS cms
where cms.deleted='0'
) as initCMS on initCMS.contentId = c.contentId
same table
the order by (without limit) in a subquery in join is unuseful because join ordered values or unordered value produce the same result
the group by contentId is strange beacuse there aren't aggregation function and the sue of group by without aggregation function is deprecated is sql
and in the most recente version for mysql is not allowed (by deafult) if you need distinct value or just a rows for each contentId you should use distinct or retrive the value in a not casual manner (the use of group by without aggregation function retrive casual value for not aggregated column .
for a partial eval your query should be refactored as
SELECT c.*
, c.initcreatetime
, c.initupdatetime
, user.name as partnerName
, r.name as rightsName
, r1.name as copyRightsName
, a.name as agelimitName
, ct.type as contenttypename
, cat.name as categoryname
, lang.name as languagename
FROM ContentCMS c
left join ContentCategoryType ct on ct.id = c.contentType
left join User user on c.contentPartnerId = user.id
left join Category cat on cat.id = c.categoryId
left join Language lang on lang.id = c.languageCode
left join CopyRights r on c.rights = r.id
left join CopyRights r1 on c.copyrights = r1.id
WHERE c.deleted='0'
) as temp
for the rest you should expiclitally select the column you effectively need add proper aggregation function for the others
Also the nested subquery just for improperly reduce the rows don't help performance ... you should also re-eval you data modelling and design.

Selecting from multiple tables that use the same joins

I'm not really sure how to explain what I need to do without an example, so I hope I can explain myself well enough!
Lets say I have the following tables in my MySQL database:
buyers
sellers
adverts
addresses
locations
object_addresses
The tables buyers, sellers and adverts are all "objects". They are associated with addresses by object_addresses which has object_type, object_id and address_id.
The addresses table has a location_id to associate it with a location.
What I ultimately want is to select all types of objects that are within a certain distance (by using a latitude and longitude I have on the locations table).
I don't have a problem with the distance calculation itself. However, I am having trouble selecting all "objects" along with their respective addresses/ locations (since they all make use of object_address).
I am able to do the following:
SELECT * FROM buyers as b
INNER JOIN object_addresses as oa on oa.ObjectId = b.Id
INNER JOIN addresses as a on oa.AddressId = a.Id
INNER JOIN locations as l on a.LocationId = l.Id
WHERE oa.ObjectType = 'buyer';
I'm having a hard time incorporating sellers and adverts into the statement above.
This is likely an simple answer, but I just can't see it tonight. Can anyone point me in the right direction?
SELECT * FROM buyers as b
INNER JOIN object_addresses as oa on oa.ObjectId = b.Id
INNER JOIN addresses as a on oa.AddressId = a.Id
INNER JOIN locations as l on a.LocationId = l.Id
WHERE oa.ObjectType = 'buyer'
union
SELECT * FROM sellers as s
INNER JOIN object_addresses as oa on oa.ObjectId = s.Id
INNER JOIN addresses as a on oa.AddressId = a.Id
INNER JOIN locations as l on a.LocationId = l.Id
WHERE oa.ObjectType = 'seller'
etc?
if you don't like that - basically, you need to include the address bits multiple times - so that each one can be linked to each object type...
Try this one using join on specific conditions oa.ObjectType = 'seller',oa.ObjectType = 'advert',oa.ObjectType = 'buyer' so only related result set will join
SELECT * FROM buyers as b
INNER JOIN object_addresses as oa on (oa.ObjectId = b.Id AND oa.ObjectType = 'buyer')
INNER JOIN advert as ad on (oa.ObjectId = ad.Id AND oa.ObjectType = 'advert')
INNER JOIN seller as s on (oa.ObjectId = s.Id AND oa.ObjectType = 'seller')
INNER JOIN addresses as a on oa.AddressId = a.Id
INNER JOIN locations as l on a.LocationId = l.Id
Other solution would be join all your table and then check the object type
SELECT * FROM buyers as b
INNER JOIN object_addresses as oa on (oa.ObjectId = b.Id )
INNER JOIN advert as ad on (oa.ObjectId = ad.Id )
INNER JOIN seller as s on (oa.ObjectId = s.Id )
INNER JOIN addresses as a on oa.AddressId = a.Id
INNER JOIN locations as l on a.LocationId = l.Id
WHERE oa.ObjectType IN('buyer' , 'advert' , 'seller')

mysql select statement not returning rows where some tables in query aren't populated

I'm having a real mind blank - This code selects rows quite nicely apart from those entries where I change st.station_id from a value of '1' to a different (but still valid) number but where there are no entries for that station_id in either the station_owner_map table, the organisation table or the cap_gen_data_table. I basically need to amend my sql to ignore any table where there are no entries.
Select st.station_id, st.station_name , st.st_town, st.st_state, c1.country_name, o1.organisation_name, som1.equity, st.river_basin, st.cost, st.cost_ref, st.comm_year,cg1.caporgen, ht1.hydro_name, cg1.value, srs1.srs_description, cg1.ref_year
FROM station st
inner join station_country_map scm1 on st.station_id = scm1.station_id
inner join country c1 on scm1.country_id = c1.country_id
inner join station_owner_map som1 on st.station_id = som1.station_id
inner join organisation o1 on som1.owner_id = o1.org_id
inner join cap_gen_data cg1 on st.station_id = cg1.station_id
inner join value_lookup vl1 on cg1.caporgen = vl1.id
inner join hydro_type ht1 on cg1.hydro_type_id = ht1.type_id
inner join station_record_status srs1 on cg1.capacity_status = srs1.st_rec_stat_id
where st.station_id = 1
It's caused by your inner joins. Inner join means there has to be a value in both tables for the record to show up in the result set.
Use left join instead, then only the table 'on the left' has to have a value.
Use left join on tables where the value may not be present.
If you have two tables A and B an inner join will only return the rows from A where the join condition is met. A left join will return all rows from A regardless of if the join condition is satisfied. Columns in the select statement associated with B will be null when a left join is used.
I have only added the left join to the tables you have indicated. If other tables may not satisfy the join condition change the join type from inner to left.
Select st.station_id, st.station_name , st.st_town, st.st_state, c1.country_name, o1.organisation_name, som1.equity, st.river_basin, st.cost, st.cost_ref, st.comm_year,cg1.caporgen, ht1.hydro_name, cg1.value, srs1.srs_description, cg1.ref_year
FROM station st
inner join station_country_map scm1 on st.station_id = scm1.station_id
inner join country c1 on scm1.country_id = c1.country_id
left join station_owner_map som1 on st.station_id = som1.station_id
left join organisation o1 on som1.owner_id = o1.org_id
left join cap_gen_data cg1 on st.station_id = cg1.station_id
inner join value_lookup vl1 on cg1.caporgen = vl1.id
inner join hydro_type ht1 on cg1.hydro_type_id = ht1.type_id
inner join station_record_status srs1 on cg1.capacity_status = srs1.st_rec_stat_id
where st.station_id = 1

Mysql Group concat and then queries based on results

Hi all I have a table business which has alot of many to many relationships. It was suggested i perform a group concat first to get the ideas from the many table and then look at these ids to get the values from the many tables
In the below instance OK i can see i get a list of announcement ids back via the GROUP_CONCAT(DISTINCT ba.announcement_id) as 'announcement', how from here do i set
SELECT * from announcement
where id IN(_______)
where the in represents what was returned from the group_concat
id b
BEGIN
/* Business Information and Categories */
SELECT
b.alias_title, b.title, b.premisis_name,
a.address_line_1, a.address_line_2, a.postal_code,tvc.town_village_city,spc.state_province_county, c.country,
GROUP_CONCAT(DISTINCT be.event_id) as 'event',
GROUP_CONCAT(DISTINCT ba.announcement_id) as 'announcement',
GROUP_CONCAT(DISTINCT bd.document_id) as 'document',
GROUP_CONCAT(DISTINCT bi.image_id) as 'image',
GROUP_CONCAT(DISTINCT bprod.product_id ) as 'product',
GROUP_CONCAT(DISTINCT bt.tag_title_id) as 'tag'
FROM business AS b
INNER JOIN business_category bc_1 ON b.primary_category = bc_1.id
INNER JOIN business_category bc_2 ON b.secondary_category = bc_2.id
LEFT OUTER JOIN business_category bc_3 ON b.tertiary_category = bc_3.id
INNER JOIN address a ON b.address_id = a.id
LEFT OUTER JOIN town_village_city tvc ON a.town_village_city_id = tvc.id
LEFT OUTER JOIN state_province_county spc ON a.state_province_county_id
INNER JOIN country c ON a.country_id = c.id
LEFT OUTER JOIN geolocation g ON b.geolocation_id = g.id
LEFT OUTER JOIN business_event be ON b.id = be.event_id
LEFT OUTER JOIN business_announcement ba ON b.id = ba.announcement_id
LEFT OUTER JOIN business_document bd ON b.id = bd.business_id
LEFT OUTER JOIN business_image bi ON b.id = bi.business_id
LEFT JOIN business_property bp ON b.id= bp.business_id
LEFT JOIN business_product bprod ON b.id= bprod.business_id
LEFT JOIN business_tag bt ON b.id = bt.business_id
WHERE b.id= in_business_id;
SELECT * from announcement
where
END
In your first select statement you may assign the announcementId's to a variable and then use it to get all announcements in the second query:
set #announcementIds = '';
select ...........,
#announcementIds:= GROUP_CONCAT(DISTINCT announcement_id) as 'announcement',
...........;
Select * from announcement
where announcement_id REGEXP REPLACE(#announcementIds,',','|');
Some links:
Replace function
Regexp

Making the query for more efficient

What I want is the Participants (people, with role as participant) of one event, combined with people of the same companies as that of participants, but whose role is manager.
A Person's role is stores in person_role_membership, but a person's company is stored in People. I have crafted the following query, which I think does the job pretty well. Please ignore the extra joins and information. I am fetching all the participants, then UNION-ing with all the managers, for which I am once again fetching all the participants. Now the trouble is this whole query, for a small subset is taking 9 seconds. Is there a way to make it faster?
select distinct *
from
(
SELECT
p.id as p_id, p.first_name as p_first_name, p.last_name as p_last_name,p_r.name as p_role,
p.job_title as p_job_title,
p_d.email as p_email, p_d.phone_1 as p_phone, p_d.phone_ext_1 as p_ext,
c.name as p_company, p_c.name as p_parent_company
FROM person_role_memberships as prm
left join people as p on prm.person_id = p.id and prm.person_role_id between 32 and 35
left join person_roles as p_r on p_r.id = prm.person_role_id
left join person_details as p_d on p.id = p_d.person_id and p_d.type = 'BusinessDetail'
left join companies as c on c.id = p.company_id
left join companies as p_c on p_c.id = p.parent_company_id
where
p.id is not null
) as parts
union (
select p.id, p.first_name,p.last_name,prm.person_role_id, p.job_title, 'cp email', 'phone','ext',c.name, d.name
from people as p -- All those people
left join person_role_memberships as prm on prm.person_id = p.id -- whose roles are like this
left join companies as c on p.company_id = c.id
left join companies as d on c.parent_id = d.id
-- and whose companies are like those people whose roles are like this
where company_id = any
(
select company_id from people as p
left join person_role_memberships as prm on prm.person_id = p.id
where prm.person_role_id between 32 and 35-- and other conditions;
)
and (person_role_id = 14 or person_role_id = 15))
My guess would be that the 'any' part of your query is slowing things down. By the looks of it I think you don't even need it because you could filter on person_role_memberships directly:
select p.id, p.first_name,p.last_name,prm.person_role_id, p.job_title, 'cp email', 'phone','ext',c.name, d.name
from people as p -- All those people
left join person_role_memberships as prm on prm.person_id = p.id -- whose roles are like this
left join companies as c on p.company_id = c.id
left join companies as d on c.parent_id = d.id
-- and whose companies are like those people whose roles are like this
where prm.person_role_id between 32 and 35 -- and other conditions;
and (person_role_id = 14 or person_role_id = 15))
The 'any'-statement is causing the database to look through the entire PEOPLE table for every record.