How to use right join correctly - mysql

Table Locations
loc_id loc_name hier2 hier3 hier4 hier5 hier6 hier7 hier8 hier9
152675 Castelli 105 109 0 319 14356 152673 152675 0
14356 Rome 105 109 0 319 14356 0 0 0
...
Table Lacations References
oid name loc_id
12 Demo Villa 152675
...
Now i try to find some entries a user searches by entering the string "caste":
SELECT geo.loc_id, geo.loc_name AS name
FROM locations AS geo
LEFT JOIN locations AS geoh3 ON geo.hier3 = geoh3.loc_id
LEFT JOIN locations AS geoh4 ON geo.hier4 = geoh4.loc_id
LEFT JOIN locations AS geoh8 ON geo.hier8 = geoh8.loc_id
WHERE geo.loc_name LIKE 'caste%'
GROUP BY geo.loc_name
This works. I get the entry with loc_id 152675
Now i only want to get those entrys we have objects in. So i join the reference table:
SELECT geo.loc_id, geo.loc_name AS name
FROM locations AS geo
LEFT JOIN locations AS geoh3 ON geo.hier3 = geoh3.loc_id
LEFT JOIN locations AS geoh4 ON geo.hier4 = geoh4.loc_id
LEFT JOIN locations AS geoh8 ON geo.hier8 = geoh8.loc_id
RIGHT JOIN locations_xref AS gx ON geo.loc_id = gx.loc_id
WHERE geo.loc_name LIKE 'caste%'
GROUP BY geo.loc_name
This works. Again i get the location entry with loc_id 152675 because theres a reference.
PROBLEM
Now the user searches for "rome". I dont get any entry because there is no object reference directly to the city "Rome". The existing object is referenced to a district of rome.
As you can see the district and the city entries have hierarchy IDs which can be used to identify the correct structure. I just cant get it to work together with the reference table, so i only get those objects which are in "rome" or in a district, which is part of rome.
Any help is greatly appreciated!

I don't know much about your data but from first sight it seems that hier6 has the information about relationship between Rome and Castelli. So the query you need may look something more or less like this:
SELECT geo.loc_id, geo.loc_name AS name
FROM locations AS geo
LEFT JOIN locations AS geoh3 ON geo.hier3 = geoh3.loc_id
LEFT JOIN locations AS geoh6 ON geo.hier6 = geoh6.loc_id
LEFT JOIN locations AS geoh4 ON geo.hier4 = geoh4.loc_id
LEFT JOIN locations AS geoh8 ON geo.hier8 = geoh8.loc_id
RIGHT JOIN locations_xref AS gx ON geo.loc_id = gx.loc_id
WHERE geoh6.loc_name LIKE 'rome%'
GROUP BY geo.loc_name

You can tweak your query (as Karolis suggested), but I don't think that will give you what you want. Your query will return 'Castelli' when you search for 'Rome%', but it won't return 'Rome'. It won't return 'Rome', because 'Rome' isn't in your xref table.
To return 'Rome' with this kind of query, you'll need to insert a row for 'Rome' into the xref table.
You can get all the things that are "in" Rome with a UNION, but it doesn't reference your xref table at all.
select la.loc_id, la.loc_name
from locations la
where la.loc_name like 'Rome%'
union
select lb.loc_id, lb.loc_name
from locations lb
inner join locations lc on lc.hier6 = lb.hier6
It's not clear to me how you determine which column to use for the join at run time.
Later . . .
If you don't know which columns to use at run time, you have to LEFT JOIN on all of them. And since you don't know whether the search string might refer to a value joined through column hier2, hier3, hier4, etc., then you have to check each of those columns for a match, too.
If locations_xref acts like a filter, then you need an inner join on that table, along with a row for 'Rome'. (Because you have properties in Rome.) Probably something along these lines.
SELECT geo.loc_id, geo.loc_name AS name
FROM locations AS geo
LEFT JOIN locations AS geoh2 ON geo.hier2 = geoh2.loc_id
LEFT JOIN locations AS geoh3 ON geo.hier3 = geoh3.loc_id
LEFT JOIN locations AS geoh4 ON geo.hier4 = geoh4.loc_id
LEFT JOIN locations AS geoh5 ON geo.hier5 = geoh5.loc_id
LEFT JOIN locations AS geoh6 ON geo.hier6 = geoh6.loc_id
LEFT JOIN locations AS geoh7 ON geo.hier7 = geoh7.loc_id
LEFT JOIN locations AS geoh8 ON geo.hier8 = geoh8.loc_id
LEFT JOIN locations AS geoh9 ON geo.hier9 = geoh9.loc_id
INNER JOIN locations_xref lx on lx.loc_id = geo.loc_id
WHERE geo.loc_name LIKE 'Rom%'
or geoh2.loc_name like 'Rom%'
or geoh3.loc_name like 'Rom%'
or geoh4.loc_name like 'Rom%'
or geoh5.loc_name like 'Rom%'
or geoh6.loc_name like 'Rom%'
or geoh7.loc_name like 'Rom%'
or geoh8.loc_name like 'Rom%'
or geoh9.loc_name like 'Rom%'
Before you get too invested in this model, you should take a look at Bill Karwin's database antipatterns. "Naive Trees" starts on slide 48.

Related

Returning value from query based on results

I have this query I'm trying to build to display specific information for a stored table. I'm needing the query to also display the Enemy Guild Name but I'm having trouble getting the query to take the Enemy Guild ID and link it to the name.
SELECT g.wPos as wPos, g.szGuildName as szGuildName, g.dwGuildExpWeek as dwGuildExpWeek, g.dwEnemyGuildID as dwEnemyGuildID, gm.wPower as wPower, gd.szName as szName
FROM guild as g
LEFT JOIN guild_member AS gm ON gm.dwGuildID = g.dwGuildID AND gm.wPower = '1'
LEFT JOIN gamedata AS gd ON gd.dwID = gm.dwRoleID
WHERE g.wPos = '1'
The output of the query right now results in the following:
Query Results Currently
What I need it to do now is take the dwEnemyGuildID it finds and then use that ID to search for the szGuildName while also displaying the other data it finds.
Use the concept of SELF JOIN, in which we will join same table again if we have a field which is a reference to the same table. Here dwEnemyGuildID is reference to the same table.
A trivial example of the same is finding Manager for an employee from employees table.
Reference: Find the employee id, name along with their manager_id and name
SELECT
g.wPos as wPos,
g.szGuildName as szGuildName,
g.dwGuildExpWeek as dwGuildExpWeek,
g.dwEnemyGuildID as dwEnemyGuildID,
enemy_g.szGuildName as szEnemyGuildName, -- pick name from self joined table
gm.wPower as wPower,
gd.szName as szName
FROM guild as g
LEFT JOIN guild_member AS gm ON gm.dwGuildID = g.dwGuildID AND gm.wPower = '1'
LEFT JOIN guild AS enemy_g ON g.dwEnemyGuildID = enemy_g.dwGuildID -- Use self join
LEFT JOIN gamedata AS gd ON gd.dwID = gm.dwRoleID
WHERE g.wPos = '1';

how to get all records with multple joins in MySQL

i have 4 tables in MYSQL and i want to show count of learner_history table's status column district wise for all 36 districts, however when i query it shows only 1 district result even i tried left join to show district names weather there is data available or not it must show null or zero in its position.Data should be like this for all districts:
database structure is like this
[enter image description here][i draw the relation of tables in this image]
please suggest what query would give results like this, thanks.
i draw the relation of tables in this image13.jpg
It was pretty awkward to work from with images, but it should get you what you are asking for. Also wasn't sure what you were wanting with 'Str.#' - the example image you gave with 22 as every result wasn't the most helpful.
select
District.District_Name as District,
sum(Learner_History.`Status` = 'catchment') as Catchment,
sum(Learner_History.`Status` = 'fresher') as Fresher,
sum(Learner_History.`Status` = 'dropout') as Dropout,
sum(Learner_History.`Status` = 'missout') as Missout,
sum(Learner_History.`Status` = 'mainstrain') as Mainstrain,
sum(Learner_History.`Status` = 'pec') as PEC
from District
left join School on School.District_ID = District.District_ID
left join Learner on Learner.School_ID = School.School_ID
left join Learner_History on Learner_History.Learner_ID = Learner.Learner_ID
group by District.District_Name;

MySQL create Tables/Views

I don't know if the title is correct but here goes my problem.
So, I want to create View in phpmyadmin using data from several tables (picture below), View that represents maintenance for lamps with fields from several tables (substation, post_type, area, lamp_type, failure and maintenance)
Here are tables with connections:
I've manage somehow and created something like this(picture below), which I managed to create using this block of code:
CREATE OR REPLACE VIEW
v_lamp_I
AS
SELECT
lamps.id,
substation.code AS sub_code,
substation.name AS sub_name,
lamps.lamp_code,
post_type.descript AS post_ty,
lamp_type.descript AS lamp_ty,
area.descript AS area_name,
rasvjeta.adress,
DATE_FORMAT(date_maintenance, "%d.%m.%Y.") AS date_main,
lamps.geo_long,
lamps.geo_lat
FROM lamps
INNER JOIN substiation ON substiation.id = lamps.substiation_id
INNER JOIN post_type ON post_type.id = lamps.post_type_id
INNER JOIN lamp_type ON lamp_type.id = lamps.lamp_type_id
INNER JOIN area ON area.id = rasvjeta.area_id
INNER JOIN maintenance ON maintenance.lamps_id = lamps.id
I've managed to create view but the problem is with that view I can see only rows/lamps(sifra_lampe) which were maintained, only 4. In table lamps
I've 24 entries in and only 4 entries for maintenance.But, I want to see all 24 entries and if there was no maintenance for that particular lamp, field can be empty with date format (00-00-00 or it can be NULL) and for entries/lamps that were maintained I want to be visible date field.
Here is table lamps with entries.
And here is view with maintenance date. As you can see there are only 6 entries
I want to see the rest of the entries, for lamps that were not maintained entries can be null or date format like this (00-00-00) and for lamps that were maintained date format can stay the same, in short I want to see all entries not only those which were maintained. Thank you and sorry for long question. I didn't know how to construct meaningful and short question so wrote everything.
create or replace view
v_lamp_I as
SELECT lamps.id,
substation.code AS sub_code,
substation.name AS sub_name,
lamps.lamp_code,
post_type.descript AS post_ty,
lamp_type.descript AS lamp_ty,
area.descript AS area_name,
rasvjeta.adress,
lamps.geo_long,
lamps.geo_lat
FROM lamps
INNER JOIN substiation ON substiation.id = lamps.substiation_id
INNER JOIN post_type ON post_type.id = lamps.post_type_id
INNER JOIN lamp_type ON lamp_type.id = lamps.lamp_type_id
INNER JOIN area ON area.id = rasvjeta.area_id;
CREATE OR REPLACE VIEW
v_lamp_new
AS
select v_lamp_I.*,DATE_FORMAT(date_maintenance, "%d.%m.%Y.") AS date_main
from v_lamp_I
LEFT JOIN maintenance ON v_lamp_I.id = maintenance.lamps_id;
CREATE OR REPLACE VIEW
v_lamp_I
AS
select tbl_lamp.*,DATE_FORMAT(date_maintenance, "%d.%m.%Y.") AS date_main
from (SELECT lamps.id,
substation.code AS sub_code,
substation.name AS sub_name,
lamps.lamp_code,
post_type.descript AS post_ty,
lamp_type.descript AS lamp_ty,
area.descript AS area_name,
rasvjeta.adress,
lamps.geo_long,
lamps.geo_lat
FROM lamps
INNER JOIN substiation ON substiation.id = lamps.substiation_id
INNER JOIN post_type ON post_type.id = lamps.post_type_id
INNER JOIN lamp_type ON lamp_type.id = lamps.lamp_type_id
INNER JOIN area ON area.id = rasvjeta.area_id) as tbl_lamp
LEFT JOIN maintenance ON tbl_lamp.id = maintenance.lamps_id

SQL Statement to pull records from multiple tables

I've trying to pull contacts from my email server's SQL database.
Need to pull the User, the Contact, and all the contacts details.
However, everything is in a separate table.
This is what i'm running:
SELECT [Contacts].[ID]
,[Users].[LoginName]
,[Contacts].[JobTitle]
,[Contacts].[Company]
,[Contacts].[WebPageAddress]
,[Contacts].[FirstName]
,[Contacts].[LastName]
,[EmailAddresses].[Address]
,[EmailAddresses].[Name]
,[Addresses].[Name]
,[Addresses].[Address1]
,[Addresses].[Town]
,[Addresses].[County]
,[Addresses].[Country]
,[Addresses].[Postcode]
,[PhoneNumbers].[Name]
,[PhoneNumbers].[Number]
FROM [WorkgroupShare].[dbo].[Contacts]
INNER JOIN [WorkgroupShare].[dbo].[Users]
ON [WorkgroupShare].[dbo].[Contacts].[Owner]=[WorkgroupShare].[dbo].[Users].[ID]
FULL OUTER JOIN [WorkgroupShare].[dbo].[EmailAddresses]
ON [WorkgroupShare].[dbo].[Contacts].[ID]= [WorkgroupShare].[dbo].[EmailAddresses].[OwnerID]
FULL OUTER JOIN [WorkgroupShare].[dbo].[Addresses]
ON [WorkgroupShare].[dbo].[Contacts].[ID]= [WorkgroupShare].[dbo].[Addresses].[OwnerID]
FULL OUTER JOIN [WorkgroupShare].[dbo].[PhoneNumbers]
ON [WorkgroupShare].[dbo].[Contacts].[ID]= [WorkgroupShare].[dbo].[PhoneNumbers].[OwnerID]
order by [Contacts].[ID]
Since the Name field in the EmailAddress.Name contains either Email1 or Email2 I get a double return for each record. And the Address.Name field contains business, home or other I get a triple. and the PhoneNumbers.Name contains 4 field I get quadruple. Then you multiple that together an a list of 6000 contacts turns into several tens of thousands. Can't figure out how to write a loop or while statement. I think I have the logic just can't figure out the syntax. Any help would be greatly appreciated.
If you have a fixed set of values in each of the Name fields, then you can hard-code them into columns, so you get 1 row per contact with columns representing each 'flavour' of reference data. Something like below (I haven't added it for the 4 phone numbers as you didn't supply the possible values for Name in that case, but you should be able to get the drift):
SELECT [Contacts].[ID]
,[Users].[LoginName]
,[Contacts].[JobTitle]
,[Contacts].[Company]
,[Contacts].[WebPageAddress]
,[Contacts].[FirstName]
,[Contacts].[LastName]
,email1.[Address] as email1Address
,email2.[Address] as email2Address
,Address1.[Address1] as HomeAddress1
,Address1.[Town] as HomeAddressTown
,Address1.[County] as HomeAddressCounty
,Address1.[Country] as HomeAddressCountry
,Address1.[Postcode] as HomeAddressPostcode
,Address2.[Address1] as BusinessAddress1
,Address2.[Town] as BusinessAddressTown
,Address2.[County] as BusinessAddressCounty
,Address2.[Country] as BusinessAddressCountry
,Address2.[Postcode] as BusinessAddressPostcode
,Address3.[Address1] as OtherAddress1
,Address3.[Town] as OtherAddressTown
,Address3.[County] as OtherAddressCounty
,Address3.[Country] as OtherAddressCountry
,Address3.[Postcode] as OtherAddressPostcode
,[PhoneNumbers].[Name]
,[PhoneNumbers].[Number]
FROM [WorkgroupShare].[dbo].[Contacts]
INNER JOIN [WorkgroupShare].[dbo].[Users]
ON [WorkgroupShare].[dbo].[Contacts].[Owner]=[WorkgroupShare].[dbo].[Users].[ID]
FULL OUTER JOIN [WorkgroupShare].[dbo].[EmailAddresses] email1
ON [WorkgroupShare].[dbo].[Contacts].[ID]= email1.[OwnerID] AND email1.[Name] = 'Email1'
FULL OUTER JOIN [WorkgroupShare].[dbo].[EmailAddresses] email2
ON [WorkgroupShare].[dbo].[Contacts].[ID]= email1.[OwnerID] AND email1.[Name] = 'Email2'
FULL OUTER JOIN [WorkgroupShare].[dbo].[Addresses] Address1
ON [WorkgroupShare].[dbo].[Contacts].[ID]= Address1.[OwnerID] AND Address1.Name = 'Home'
FULL OUTER JOIN [WorkgroupShare].[dbo].[Addresses] Address2
ON [WorkgroupShare].[dbo].[Contacts].[ID]= Address2.[OwnerID] AND Address2.Name = 'Business'
FULL OUTER JOIN [WorkgroupShare].[dbo].[Addresses] Address3
ON [WorkgroupShare].[dbo].[Contacts].[ID]= Address3.[OwnerID] AND Address3.Name = 'Other'
FULL OUTER JOIN [WorkgroupShare].[dbo].[PhoneNumbers]
ON [WorkgroupShare].[dbo].[Contacts].[ID]= [WorkgroupShare].[dbo].[PhoneNumbers].[OwnerID]
order by [Contacts].[ID]

MySQL view joining table with multiple rows

I have a few tables which I have joined together and would like to join a table that has multiple columns. My current query is as follows:
select
usrs.firstname, usrs.middleNames, usrs.surname,
if (usrs.sex=0,'Male','Female') as sex,
usrs.DOB,
(YEAR(CURDATE())-YEAR(usrs.DOB)) - (RIGHT(CURDATE(),5)<RIGHT(usrs.DOB,5)) AS age,
birth.townName AS 'birthTown', birth.regionName AS 'birthRegion', birth.countryName AS 'birthCountry',
location.townName AS 'curTown', location.regionName AS 'curRegion', location.countryName AS 'curCountry',
usrs.email, emails.email AS 'alternateEmail',
numbers.number,
usrs.website,
usrs.aboutMe,
family.mother, family.father, family.partner, marital.status, family.aboutFamily,
children.name AS 'childsName'
from ch09.tbl_users usrs
LEFT JOIN vw_town_region_country birth ON birth.townID = usrs.birthPlace
LEFT JOIN vw_town_region_country location ON location.townID = usrs.currentLocation
LEFT JOIN tbl_alternate_emails emails ON emails.userID = usrs.id
LEFT JOIN tbl_contact_numbers numbers ON numbers.userID = usrs.id
LEFT JOIN tbl_family family ON family.userID = usrs.id
LEFT JOIN tbl_marital_status marital ON family.maritalStatusID = marital.id
LEFT JOIN tbl_children children ON family.id = children.familyID
I put my whole query it might be a bit wrong or cleaner way to do it. The issue is with the tbl_children, as it is "one to many" it results in multiple rows for a single user for every child that user has in the tbl_children table.
So my results are:
userID:1 firstName middleNames surname ....... childsName
userID:1 firstName middleNames surname ....... childsName
userID:1 firstName middleNames surname ....... childsName
I would prefer:
userID:1 firstName middleNames surname ....... childsName childsName2 childsName3
Is it possible to do this through a Join somehow? Obviously it isn't acceptable for me to have multiple entries per user on the view.
You could use the function GROUP_CONCAT in combination with GROUP BY for this. GROUP_CONCAT let's you aggregate values from a column by concatenating them. Note that this will not give you a column for every child, but one column with a string containing all the names.
EDIT; your query would become something like:
select
usrs.firstname, usrs.middleNames, usrs.surname,
if (usrs.sex=0,'Male','Female') as sex,
usrs.DOB, (YEAR(CURDATE())-YEAR(usrs.DOB)) - (RIGHT(CURDATE(),5)<RIGHT(usrs.DOB,5)) AS age,
birth.townName AS 'birthTown', birth.regionName AS 'birthRegion', birth.countryName AS 'birthCountry',
location.townName AS 'curTown', location.regionName AS 'curRegion', location.countryName AS 'curCountry',
usrs.email, emails.email AS 'alternateEmail',
numbers.number,
usrs.website,
usrs.aboutMe,
family.mother, family.father, family.partner, marital.status, family.aboutFamily,
GROUP_CONCAT(children.name SEPERATOR ",") AS 'childsName'
FROM ch09.tbl_users usrs
LEFT JOIN vw_town_region_country birth ON birth.townID = usrs.birthPlace
LEFT JOIN vw_town_region_country location ON location.townID = usrs.currentLocation
LEFT JOIN tbl_alternate_emails emails ON emails.userID = usrs.id
LEFT JOIN tbl_contact_numbers numbers ON numbers.userID = usrs.id
LEFT JOIN tbl_family family ON family.userID = usrs.id
LEFT JOIN tbl_marital_status marital ON family.maritalStatusID = marital.id
LEFT JOIN tbl_children children ON family.id = children.familyID
GROUP BY userID
Assuming that the number of children is unknown at the time of writing the query (i.e., a user could have 0 or 1 or 5 children), making a pivot like this probably isn't the best route for getting data into a front end application.
Depending on how you're accessing the data, you're better off either returning multiple rows per user as you have or retrieving the children (and emails, etc.) for each user as you need them. The key here is to only retrieve them if you need them. I believe that this is known as Lazy Loading in the Object Oriented world.
If you're doing this to fill a list box of some kind, and therefore you need them for each user, then you might consider setting some limit on the number of children that you'll retrieve based on how your list will appear and then use LEFT JOINs to get exactly that number for the rows that you retrieve, rather than doing the round trip to the server for every user.
In other words, it all depends. :)