Am I doing nested joins incorrectly? - mysql

Folks! I am scratching my head on this one. Right when I add UserRole part in last join I start seeing this error. What am I doing wrong? Is there a limit on number of nested joins in MySQL?
Error:
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ON Opportunity.AccountId = AccountD.Id' at line 31
Table Structure
Opportunity
-----------
Id, Name, AccountId, OwnerId
Account
-------
Id,Name,OwnerId
User
----
Id,Name,UserRoleId
UserRole
--------
Id,Name
RecordType
----------
Id,Name
SQL
SELECT
Opportunity.Id AS 'Opportunity_ID',
Opportunity.Opportunity_Number__c AS 'Opportunity_Number__c',
UserA.Name AS 'Opportunity_Owner',
Opportunity.Name AS 'Opportunity_Name',
Opportunity.Probability AS 'Opportunity_Probability',
Opportunity.StageName AS 'Opportunity_Stage',
Opportunity.Amount AS 'Opportunity_Amount',
Opportunity.CloseDate AS 'Opportunity_CloseDate',
Opportunity.Roll_Out_End_Formula__c AS 'Opportunity_Rollout_End_Date',
RecordTypeA.Name AS 'Record_Type',
Opportunity.Division__c AS 'Division',
Pricebook2.Name AS 'Price_Book',
Opportunity.Won_Date__c AS 'Opportunity_Won_Date',
Opportunity.CreatedDate AS 'Opportunity_Created_Date',
AccountA.Id AS 'Account_ID',
AccountA.Name AS 'Account_Name',
AccountA.Type AS 'Account_Type',
RecordTypeB.Name AS 'Account_Record_Type',
AccountA.Key_Account__c AS 'Key_Account',
UserB.Name AS 'Account_Owner',
UserB.Sales_Team__c AS 'Account_Owner_Sales_Team',
UserRoleA.Name AS 'Account_Owner_User_Role'
FROM Opportunity
LEFT JOIN User UserA ON Opportunity.OwnerId = UserA.Id
LEFT JOIN RecordType RecordTypeA ON Opportunity.RecordTypeId = RecordTypeA.Id
LEFT JOIN Pricebook2 ON Opportunity.Pricebook2Id = Pricebook2.Id
LEFT JOIN Account AccountA ON Opportunity.AccountId = AccountA.Id
LEFT JOIN Account AccountB JOIN RecordType RecordTypeB ON AccountB.RecordTypeId = RecordTypeB.Id ON Opportunity.AccountId = AccountB.Id
LEFT JOIN Account AccountC JOIN User UserB ON AccountC.OwnerId = UserB.Id ON Opportunity.AccountId = AccountC.Id
LEFT JOIN Account AccountD JOIN User UserC JOIN UserRole UserRoleA ON UserC.UserRoleId = UserRoleA.Id ON AccountD.OwnerId = UserC.Id ON Opportunity.AccountId = AccountD.Id
LIMIT 5\G

If you want to do nested joins to your "Account" instances, you have to use parentheses.
LEFT JOIN Account AccountA ON Opportunity.AccountId = AccountA.Id
LEFT JOIN (Account AccountB JOIN RecordType RecordTypeB ON AccountB.RecordTypeId = RecordTypeB.Id)
ON Opportunity.AccountId = AccountB.Id
LEFT JOIN (Account AccountC JOIN User UserB ON AccountC.OwnerId = UserB.Id)
ON Opportunity.AccountId = AccountC.Id
LEFT JOIN (Account AccountD JOIN User UserC ON AccountD.OwnerId = UserC.Id JOIN UserRole UserRoleA ON UserC.UserRoleId = UserRoleA.Id)
ON Opportunity.AccountId = AccountD.Id
Edit: I fixed the last line (the one with AccountD).

This looks like the sort of code that gets auto-generated when you use the Query Designer (tends to be a dead giveaway actually)
That said it does work, it's just difficult to read.
Replace your from clause with the following and see if you're still having problems...
FROM
Opportunity
LEFT JOIN User UserA
ON Opportunity.OwnerId = UserA.Id
LEFT JOIN RecordType RecordTypeA
ON Opportunity.RecordTypeId = RecordTypeA.Id
LEFT JOIN Pricebook2
ON Opportunity.Pricebook2Id = Pricebook2.Id
LEFT JOIN Account AccountA
ON Opportunity.AccountId = AccountA.Id
JOIN RecordType RecordTypeB
ON AccountB.RecordTypeId = RecordTypeB.Id
LEFT JOIN Account AccountB
ON Opportunity.AccountId = AccountB.Id
JOIN User UserB
ON AccountC.OwnerId = UserB.Id
LEFT JOIN Account AccountC
ON Opportunity.AccountId = AccountC.Id
LEFT JOIN Account AccountD
ON Opportunity.AccountId = AccountD.Id
JOIN User UserC
ON AccountD.OwnerId = UserC.Id
JOIN UserRole UserRoleA
ON UserC.UserRoleId = UserRoleA.Id

A nested join is nothing that you write in your query, but a technique the DBMS uses to perform a join.
When you join two tables, say, from a join b on a.aa = b.bb, the DBMS has several methods to put this into practise. For example it could read a and sort it by aa. Then it would read b and sort it by bb. Then it would read the two result list sequentially and write all the matches in a final result list.
Another method would be to read one record from a, then read b to find all matches on aa = bb. Then it would read the next record from a and read b again to find matches for this second record, and so on. This method is called a nested join.
You however, simply tell the DBMS what to join and on what criteria to join, but not what method to use. The DBMS will (hopefully) find the best method for each join.
As to your query: You select all records from Opportunity. Then you outer join the table User. So for some reason you expect some opportunities not to have an owner, but want a result row for the opportunity nonetheless. Same for RecordType, Pricebook2 and Account; you expect some opportunities not to have a match in these tables, but want the opportunity still in the results. From the names we can gather that there are foreign keys on the columns, e.g. you cannot store an opportunity with RecordTypeId = 'X' when 'X' doesn't exist in RecordType. So you outer join obviously, because these columns are optional (nullable).
Then it gets queer. You try to outer join Account again. But this time you don't specify an ON clause. This is invalid SQL, but MySQL lets it slip. The join becomes a CROSS JOIN internally, i.e. you join every row you got so far with every account. Let's say you have 1000 records in Opportunity and 500 in Account, this makes 500,000 rows. Then you suddenly inner join RecordType again. But this time you want the record type for the accounts, not for the opportunities. You join on AccountB.RecordTypeId = RecordTypeB.Id So every row with an account with a RecordTypeId null gets dismissed, the others are kept. Then you have another ON clause which is misplaced. I don't know how MySQL interprets this. Either this results in a syntax error or its treated as a WHERE clause or its treated as part of the former ON clause. This is invalid and should raise an error, but so should the missing ON clause before which doesn't.
To make a long story short: you are doing things very wrong here. We'd have to know the expected results to get this straight.
Apart from this: Single quotes are for string literals:
'lalala' -- this is a string conform with the SQL standard
lalala -- this is a name conform with the SQL standard
"lalala" -- this is a name conform with the SQL standard
-- it could also be a string in MySQL violating the SQL standard
`lalala` -- this is a name in MySQL violating the SQL standard
Queries with many tables get easier to read with short alias names
FROM opprtunity o
JOIN User u ON u.Id = o.OwnerId

Related

MySQL return record where left joined table has no record

I'm having a database with two tables: users, users_addresses and countries.
When I'm selecting user record and binding it to the model I'm using the following statement:
SELECT
`u`.`id`, `u`.`first_name`, `u`.`last_name`,
`a`.`address_1`, `a`.`address_2`,
`a`.`town`, `a`.`region`, `a`.`post_code`, `a`.`country`,
`c`.`name` AS `country_name`,
`c`.`eu` AS `eu_member`
FROM `users` `u`
LEFT JOIN `users_addresses` `a`
ON `a`.`user` = `u`.`id`
LEFT JOIN `countries` `c`
ON `c`.`id` = `a`.`country`
WHERE `a`.`default` = 1
AND `u`.`id` = 3
The problem I'm having is that if table users_addresses does not contain corresponding record for the user then I get an empty result. If there is a record - it should only return one marked as default = 1, but obviously it would be better to ensure that it always returns just one in case, for any reason one user will have more than one addresses marked as default.
So my question is - how could I make sure that even if there is no corresponding record in the users_addresses table I will still get at least user record and how to ensure that query will always match just one address record.
Any help would be very much appreciated.
LEFT JOIN will include entries from the left table if there's no corresponding entry in the right tables.
However in your case you then filter by a.default = 1 which remove entries with no default address.
To avoid that, you will need to either join with a subquery
LEFT JOIN (
SELECT * FROM user_adresses
WHERE `default` = 1
) a
ON a.user = u.id
With this option you can limit to at most one 'default' address per user by using a GROUP BY user in the subselect.
Or you could use the a.default = 1 as a join condition and not a where condition, i.e.
LEFT JOIN user_addresses a
ON a.user = u.id and a.default = 1
Not 100% sure about that last suggestion, but I'm pretty confident this would work.
Edit: and you obviously also have the option suggested by #steinmas, i.e. extending the filter on default to accept also null values.
To ensure you get at most one default address by user, you'll most likely need a GROUP BY user command at some point
Try changing your WHERE clause to this:
WHERE (`a`.`default` = 1 OR `a`.`default` IS NULL)
AND `u`.`id` = 3
This is by definition
The LEFT JOIN keyword returns all rows from the left table (table1),
with the matching rows in the right table (table2). The result is NULL
in the right side when there is no match.
If you can reorganize the query

MySQL left join with multiple views

I have the following query, which I designed to compile data from a number of views based on client data.
SELECT
vw_clients.client_id,
name,
exts,
vms,
ivrs,
queues,
conf10,
conf20,
conf30
FROM
vw_clients,
vw_exts,
vw_vms,
vw_ivrs,
vw_queues,
vw_conf10,
vw_conf20,
vw_conf30
WHERE
vw_clients.client_id = vw_exts.client_id AND
vw_clients.client_id = vw_vms.client_id AND
vw_clients.client_id = vw_ivrs.client_id AND
vw_clients.client_id = vw_queues.client_id AND
vw_clients.client_id = vw_conf10.client_id AND
vw_clients.client_id = vw_conf20.client_id AND
vw_clients.client_id = vw_conf30.client_id;
The query works fine so long as there are records in every view relating to the records in vw_clients. However I need to modify this to use a left join, so that it returns all records from vm_clients and only those from the other views that have records for those clients.
I've read about left joins, but at most I have only found info on joining one or maybe two tables - but I need to join 8. Do I do a left join on vw_clients.client_id to the corresponding client_id field in all of the views? What's the syntax for that?
Would be grateful for any help. I'm very close to solving this issue and I think this is the last piece of the puzzle!
Many thanks.
You can use left join by putting vw_clients in the first in the from list, then all other tables followed after left join. The left join can join only two tables or one "result set" and a table,where the result set is the result of the former join.
In your case:
SELECT
T0.client_id, name, exts, vms, ivrs, queues, conf10, conf20, conf30
FROM
vw_clients T0
left join vw_exts T1 on T0.client_Id=T1.client_id
Left join vw_vms T2 on ...
...
Where ...
Maybe here you don't need where clause.
Yes, you’d just replace your WHERE with a LEFT JOIN.
LEFT JOIN vw_exts ON vw_clients.client_id = vw_exts.client_id
Then you can remove those extra tables from the FROM part.

MySQL - Join a table twice with a main table

I'm not sure if this can be done. But I just wanted to check with the experts out here.
My case is:
I have a table tbl_campaign which basically stores a campaigns which has a one to many relation with a table called tbl_campaign_user where the users that were selected during the campaign are stored along with the campaign id (tbl_campagin_user.cu_campaign_id = tbl_campaign.campaign_id ).
The second table (tbl_campaign_user) has a status field which is either 0 / 1 denoting unsent/sent. I wanted to write a single sql query which would read the campaign data as well as the number of sent and unsent campaign users (which is why I'm joining twice on the second table).
I tried this below, but I get the same number of count as sent and unsent.
SELECT `tbl_campaign`.*,
COUNT(sent.cu_id) as numsent,
COUNT(unsent.cu_id) as num_unsent FROM (`tbl_campaign`)
LEFT JOIN tbl_campaign_user as sent on (sent.cu_campaign_id = tbl_campaign.campaign_id and sent.cu_status='1')
LEFT JOIN tbl_campaign_user as unsent on (unsent.cu_campaign_id = tbl_campaign.campaign_id and unsent.cu_status='0')
WHERE `tbl_campaign`.`campaign_id` = '19'
I tried debugging by breaking the query into two parts:
=>
SELECT `tbl_campaign`.*,
COUNT(unsent.cu_id) as num_unsent FROM (`tbl_campaign`)
Left join tbl_campaign_user as unsent on (unsent.cu_campaign_id = tbl_campaign.campaign_id and unsent.cu_status='0')
WHERE `tbl_campaign`.`campaign_id` = '19'
The above works exactly as wanted. And so does the one below:
=>
SELECT `tbl_campaign`.*,
COUNT(sent.cu_id) as numsent FROM (`tbl_campaign`)
Left join tbl_campaign_user as sent on (sent.cu_campaign_id = tbl_campaign.campaign_id and sent.cu_status='1')
WHERE `tbl_campaign`.`campaign_id` = '19'
I am not sure what I've been doing wrong while merging the two. I know I don't know much about joins so possibly a conceptual error? Please could anyone help me?
Thx in advance!
You only need to join tbl_campaign_user once and
count (sum, whatever) how many times cu_status was zero/one.
SELECT `tbl_campaign`.id,
count(u.id) as num_all_campaign_users
sum(u.cu_status) as num_sentcampaign_users,
count(u.id) - sum(u.cu_status) as num_unsent_campaign_users
FROM `tbl_campaign` c
LEFT JOIN tbl_campaign_user as u on (u.cu_campaign_id = c.campaign_id)
WHERE `tbl_campaign`.`campaign_id` = '19'
group by `tbl_campaign`.id
Note that this is sort of pseudo code, you may have to elaborate
the sum/count in the select clause and the group by clause as well.

How do I decide when to use right joins/left joins or inner joins Or how to determine which table is on which side?

I know the usage of joins, but sometimes I come across such a situation when I am not able to decide which join will be suitable, a left or right.
Here is the query where I am stuck.
SELECT count(ImageId) as [IndividualRemaining],
userMaster.empName AS ID#,
CONVERT(DATETIME, folderDetails.folderName, 101) AS FolderDate,
batchDetails.batchName AS Batch#,
Client=#ClientName,
TotalInloaded = IsNull(#TotalInloaded,0),
PendingUnassigned = #PendingUnassigned,
InloadedAssigned = IsNull(#TotalAssigned,0),
TotalProcessed = #TotalProcessed,
Remaining = #Remaining
FROM
batchDetails
Left JOIN folderDetails ON batchDetails.folderId = folderDetails.folderId
Left JOIN imageDetails ON batchDetails.batchId = imageDetails.batchId
Left JOIN userMaster ON imageDetails.assignedToUser = userMaster.userId
WHERE folderDetails.ClientId =#ClientID and verifyflag='n'
and folderDetails.FolderName IN (SELECT convert(VARCHAR,Value) FROM dbo.Split(#Output,','))
and userMaster.empName <> 'unused'
GROUP BY userMaster.empName, folderDetails.folderName, batchDetails.batchName
Order BY folderDetails.Foldername asc
Yes, it depends on the situation you are in.
Why use SQL JOIN?
Answer: Use the SQL JOIN whenever multiple tables must be accessed through an SQL SELECT statement and no results should be returned if there is not a match between the JOINed tables.
Reading this original article on The Code Project will help you a lot: Visual Representation of SQL Joins.
Also check this post: SQL SERVER – Better Performance – LEFT JOIN or NOT IN?.
Find original one at: Difference between JOIN and OUTER JOIN in MySQL.
In two sets:
Use a full outer join when you want all the results from both sets.
Use an inner join when you want only the results that appear in both
sets.
Use a left outer join when you want all the results from set a, but
if set b has data relevant to some of set a's records, then you also
want to use that data in the same query too.
Please refer to the following image:
I think what you're looking for is to do a LEFT JOIN starting from the main-table to return all records from the main table regardless if they have valid data in the joined ones (as indicated by the top left 2 circles in the graphic)
JOIN's happen in succession, so if you have 4 tables to join, and you always want all the records from your main table, you need to continue LEFT JOIN throughout, for example:
SELECT * FROM main_table
LEFT JOIN sub_table ON main_table.ID = sub_table.main_table_ID
LEFT JOIN sub_sub_table on main_table.ID = sub_sub_table.main_table_ID
If you INNER JOIN the sub_sub_table, it will immediately shrink your result set down even if you did a LEFT JOIN on the sub_table.
Remember, when doing LEFT JOIN, you need to account for NULL values being returned. Because if no record can be joined with the main_table, a LEFT JOIN forces that field to appear regardless and will contain a NULL. INNER JOIN will obviously just "throw away" the row instead because there's no valid link between the two (no corresponding record based on the ID's you've joined)
However, you mention you have a where statement that filters out the rows you're looking for, so your question on the JOIN's are null & void because that is not your real problem. (This is if I understand your comments correctly)

Mysql join query

SELECT
description
FROM
diagnosis_mapping
LEFT JOIN
diagnosis_codes
ON
diagnosis_codes.codeid = diagnosis_mapping.codeid
SELECT
description
FROM
diagnosis_mapping
LEFT JOIN
diagnosis_codes
ON
diagnosis_codes.codeid = diagnosis_mapping.secondarycodeid
How to merge these 2 queries and get info in a single resultset?
In first i need to match with codeid and in second i need to match with secondarycodeid to the same mastertable to fetch the description of both.
You can do two joins in one query, just give an alias to the table names so MySQL knows what you want to get:
SELECT
a.description desc_a,
b.description desc_b
FROM
diagnosis_mapping
LEFT JOIN
diagnosis_codes a
ON
a.codeid = diagnosis_mapping.codeid
LEFT JOIN
diagnosis_codes b
ON
b.codeid = diagnosis_mapping.secondarycodeid
In this example, a is an alias for the first diagnosis_codes table and b to the other. When you give alias names to the tables, MySQL (and any other SQL aware database) treats them basically as two separate tables and fetches data from them independently.
SELECT description FROM (diagnosis_mapping LEFT JOIN diagnosis_codes ON (diagnosis_codes.codeid = diagnosis_mapping.codeid)) LEFT JOIN diagnosis_mapping ON (diagnosis_codes.codeid = diagnosis_mapping.secondarycodeid)
Not sure if thats what you need, but try ;] (might need some spelling checking and adding some "AS" if there are any sql errors)