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. :)
Related
Hopefully i can explain this well enough. I have a bit of a unique issue where the customer system we use can change a ID in the database in the background based on the products status.
What this means is when i want to report old products we don't use anymore along side active products there ID differs between the two key tables depending on there status. This means Active products in the product table match that of the stock item table with both showing as 647107376 but when the product is no long active the StockItem table will present as 647107376 but the table that holds the product information the id presents as -647107376
This is proving problematic for me when i comes to joining the tables together to get the information needed. Originally i had my query set up like this:
SELECT
Company_0.CoaCompanyName
,SopProduct_0.SopStiStockItemCode AS hbpref
,SopProduct_0.SopStiCustomerStockCode AS itemref
,SopProduct_0.SopDescription AS ldesc
,StockMovement_0.StmOriginatingEntityID AS Goodsin
FROM
SBS.PUB.StockItem StockItem_0
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON StockItem_0.StockItemID = SopProduct_0.StockItemID
LEFT JOIN SBS.PUB.Company Company_0 ON SopProduct_0.CompanyID = Company_0.CompanyID
LEFT JOIN SBS.PUB.StockMovement StockMovement_0 ON StockItem_0.StockItemID = StockMovement_0.StockItemID
WHERE
Company_0.CoaCompanyName = ?
AND StockMovement_0.MovementTypeID = '173355'
AND StockMovement_0.StmMovementDate >= ? AND StockMovement_0.StmMovementDate <= ?
AND StockMovement_0.StmQty <> 0
AND StockMovement_0.StockTypeID ='12049886'
Unfortunately though what this means is any of the old product will not show because there is no matching id due to the SopProduct table presenting the StockItemID with a leading -
So from this i thought best to use a case when statement with a nested concat and left in it to bring through the results but this doesn't appear to work either sample of the join below:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON (CASE WHEN LEFT(SopProduct_0.StockItemID,1) = "-" THEN CONCAT("-",StockItem_0.StockItemID) ELSE StockItem_0.StockItemID END) = SopProduct_0.StockItemID
Can anyone else think of a way around this issue? I am working with a Progress OpenEdge ODBC.
Numbers look like numbers. If they are, you can use abs():
ON StockItem_0.StockItemID = ABS(SopProduct_0.StockItemID)
Otherwise a relatively simple method is:
ON StockItem_0.StockItemID IN (SopProduct_0.StockItemID, CONCAT('-', SopProduct_0.StockItemID))
Note that non-equality conditions often slow down JOIN operations.
Using an or in the join should work:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = StockItem_0.StockItemID
OR
SopProduct_0.StockItemID = CONCAT("-", StockItem_0.StockItemID)
You might need to cast the result of the concat to a number (if the ids are stored as numbers).
Or you could use the abs function too (assuming the ids are numbers):
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = abs(StockItem_0.StockItemID)
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';
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]
How do you find out who did not contribute to a particular fund raiser that we all just did. There are many titles to the different charities, I however just want to extract the non-contributors for a particular charity title. Is there anyway to do this? When I do the the syntax below it comes up as an empty set. The search is done by way of the table Id matching and left joins. Please see below.
SELECT
moiid,
trim(concat(name.fname,' ' ,name.mname,' ',name.lname)) as Brother,
name.moiid as Members_ID,
sum(otherpay.othpayamt) as NO_Contribution,
quadlt.ltfname as quad
FROM name
LEFT JOIN OTHERPAY ON name.moiid = otherpay.othpaymoiid
LEFT JOIN quadlt ON name.quadlt = quadlt.ltid
WHERE Otherpay.othpaytitle like '%food drive%'
AND otherpay.othpaymoiid IS NULL
AND name.type = 'BOI'
AND name.type <> 'jrboi'
AND name.city = 'SUFFOLK'
GROUP BY brother
ORDER BY name.quadlt, brother
When you add conditions to the where clause for tables that are left joined, you effectively turn them into an inner join, requiring them to return records.
You can move the conditions to the join itself:
SELECT moiid, trim(concat(name.fname,' ' ,name.mname,' ',name.lname)) as Brother, name.moiid as Members_ID, sum(otherpay.othpayamt) as NO_Contribution, quadlt.ltfname as quad
FROM name
LEFT JOIN OTHERPAY
ON name.moiid = otherpay.othpaymoiid
AND Otherpay.othpaytitle like '%food drive%'
LEFT JOIN quadlt ON name.quadlt = quadlt.ltid
WHERE
otherpay.othpaymoiid IS NULL
AND name.type = 'BOI'
AND name.type <> 'jrboi'
AND name.city = 'SUFFOLK'
GROUP BY brother
ORDER BY name.quadlt, brother
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.