Conditional join columns based on column value - sql-server-2014

I would like to conditionally join to a table on either one or two fields, depending on how a specific column is valued. I tried using a CASE statement to accomplish this but it is not working:
SELECT *
FROM TEST_TABLE1 A
INNER JOIN ALT_TABLE B ON B.UNIT = A.UNIT AND B.USERID = A.USERID
INNER JOIN OTHER_TABLE 2 ON CASE WHEN F.DEPARTMENT <> 'All'
THEN 'F.UNIT = A.UNIT AND F.DEPARTMENT = B.DEPARTMENT'
ELSE 'F.UNIT = A.UNIT'
END
However I am getting an error: An expression of non-boolean type specified in a context where a condition is expected, near 'INNER'.
Is there another way to change what the join conditions are depending on a column value?

You need this:
...
INNER JOIN OTHER_TABLE 2 ON F.UNIT = A.UNIT AND
(F.DEPARTMENT = 'All' OR F.DEPARTMENT = B.DEPARTMENT)
You cannot "dynamic code" a query like you did without using dynamic SQL (bad idea here).
Also, if you analyze your logic, you will see that UNIT is always joined, and that DEPARTMENT only needs to be joined when <> 'All', in other words: either it is = 'ALL' or we need the join condition.

Related

Not quite a good enough JOIN? [duplicate]

I need to retrieve all default settings from the settings table but also grab the character setting if exists for x character.
But this query is only retrieving those settings where character is = 1, not the default settings if the user havent setted anyone.
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1'
So i should need something like this:
array(
'0' => array('somekey' => 'keyname', 'value' => 'thevalue'),
'1' => array('somekey2' => 'keyname2'),
'2' => array('somekey3' => 'keyname3')
)
Where key 1 and 2 are the default values when key 0 contains the default value with the character value.
The where clause is filtering away rows where the left join doesn't succeed. Move it to the join:
SELECT `settings`.*, `character_settings`.`value`
FROM `settings`
LEFT JOIN
`character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1'
When making OUTER JOINs (ANSI-89 or ANSI-92), filtration location matters because criteria specified in the ON clause is applied before the JOIN is made. Criteria against an OUTER JOINed table provided in the WHERE clause is applied after the JOIN is made. This can produce very different result sets. In comparison, it doesn't matter for INNER JOINs if the criteria is provided in the ON or WHERE clauses -- the result will be the same.
SELECT s.*,
cs.`value`
FROM SETTINGS s
LEFT JOIN CHARACTER_SETTINGS cs ON cs.setting_id = s.id
AND cs.character_id = 1
If I understand your question correctly you want records from the settings database if they don't have a join accross to the character_settings table or if that joined record has character_id = 1.
You should therefore do
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT OUTER JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1' OR
`character_settings`.character_id is NULL
You might find it easier to understand by using a simple subquery
SELECT `settings`.*, (
SELECT `value` FROM `character_settings`
WHERE `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1') AS cv_value
FROM `settings`
The subquery is allowed to return null, so you don't have to worry about JOIN/WHERE in the main query.
Sometimes, this works faster in MySQL, but compare it against the LEFT JOIN form to see what works best for you.
SELECT s.*, c.value
FROM settings s
LEFT JOIN character_settings c ON c.setting_id = s.id AND c.character_id = '1'
For this problem, as for many others involving non-trivial left joins such as left-joining on inner-joined tables, I find it convenient and somewhat more readable to split the query with a with clause. In your example,
with settings_for_char as (
select setting_id, value from character_settings where character_id = 1
)
select
settings.*,
settings_for_char.value
from
settings
left join settings_for_char on settings_for_char.setting_id = settings.id;
The way I finally understand the top answer is realising (following the Order Of Execution of the SQL query ) that the WHERE clause is applied to the joined table thereby filtering out rows that do not satisfy the WHERE condition from the joined (or output) table. However, moving the WHERE condition to the ON clause applies it to the individual tables prior to joining. This enables the left join to retain rows from the left table even though some column entries of those rows (entries from the right tables) do not satisfy the WHERE condition.
The result is correct based on the SQL statement. Left join returns all values from the right table, and only matching values from the left table.
ID and NAME columns are from the right side table, so are returned.
Score is from the left table, and 30 is returned, as this value relates to Name "Flow". The other Names are NULL as they do not relate to Name "Flow".
The below would return the result you were expecting:
SELECT a.*, b.Score
FROM #Table1 a
LEFT JOIN #Table2 b
ON a.ID = b.T1_ID
WHERE 1=1
AND a.Name = 'Flow'
The SQL applies a filter on the right hand table.

MySQL Sum even if records doesnt exist [duplicate]

I need to retrieve all default settings from the settings table but also grab the character setting if exists for x character.
But this query is only retrieving those settings where character is = 1, not the default settings if the user havent setted anyone.
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1'
So i should need something like this:
array(
'0' => array('somekey' => 'keyname', 'value' => 'thevalue'),
'1' => array('somekey2' => 'keyname2'),
'2' => array('somekey3' => 'keyname3')
)
Where key 1 and 2 are the default values when key 0 contains the default value with the character value.
The where clause is filtering away rows where the left join doesn't succeed. Move it to the join:
SELECT `settings`.*, `character_settings`.`value`
FROM `settings`
LEFT JOIN
`character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1'
When making OUTER JOINs (ANSI-89 or ANSI-92), filtration location matters because criteria specified in the ON clause is applied before the JOIN is made. Criteria against an OUTER JOINed table provided in the WHERE clause is applied after the JOIN is made. This can produce very different result sets. In comparison, it doesn't matter for INNER JOINs if the criteria is provided in the ON or WHERE clauses -- the result will be the same.
SELECT s.*,
cs.`value`
FROM SETTINGS s
LEFT JOIN CHARACTER_SETTINGS cs ON cs.setting_id = s.id
AND cs.character_id = 1
If I understand your question correctly you want records from the settings database if they don't have a join accross to the character_settings table or if that joined record has character_id = 1.
You should therefore do
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT OUTER JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1' OR
`character_settings`.character_id is NULL
You might find it easier to understand by using a simple subquery
SELECT `settings`.*, (
SELECT `value` FROM `character_settings`
WHERE `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1') AS cv_value
FROM `settings`
The subquery is allowed to return null, so you don't have to worry about JOIN/WHERE in the main query.
Sometimes, this works faster in MySQL, but compare it against the LEFT JOIN form to see what works best for you.
SELECT s.*, c.value
FROM settings s
LEFT JOIN character_settings c ON c.setting_id = s.id AND c.character_id = '1'
For this problem, as for many others involving non-trivial left joins such as left-joining on inner-joined tables, I find it convenient and somewhat more readable to split the query with a with clause. In your example,
with settings_for_char as (
select setting_id, value from character_settings where character_id = 1
)
select
settings.*,
settings_for_char.value
from
settings
left join settings_for_char on settings_for_char.setting_id = settings.id;
The way I finally understand the top answer is realising (following the Order Of Execution of the SQL query ) that the WHERE clause is applied to the joined table thereby filtering out rows that do not satisfy the WHERE condition from the joined (or output) table. However, moving the WHERE condition to the ON clause applies it to the individual tables prior to joining. This enables the left join to retain rows from the left table even though some column entries of those rows (entries from the right tables) do not satisfy the WHERE condition.
The result is correct based on the SQL statement. Left join returns all values from the right table, and only matching values from the left table.
ID and NAME columns are from the right side table, so are returned.
Score is from the left table, and 30 is returned, as this value relates to Name "Flow". The other Names are NULL as they do not relate to Name "Flow".
The below would return the result you were expecting:
SELECT a.*, b.Score
FROM #Table1 a
LEFT JOIN #Table2 b
ON a.ID = b.T1_ID
WHERE 1=1
AND a.Name = 'Flow'
The SQL applies a filter on the right hand table.

How to do INNER JOIN with 2 Column COUNTS equal

I am trying to perform the following query:
SELECT wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
FROM wp_wp_pro_quiz_statistic_ref AS wwpqsr
INNER JOIN wp_wp_pro_quiz_statistic AS wwpqs
ON ( wwpqs.statistic_ref_id = wwpqsr.statistic_ref_id
AND COUNT(wwpqs.correct_count) AS correct =
COUNT(wwpqs.incorrect_count) AS incorrect)
INNER JOIN wp_wp_pro_quiz_master AS wwpqm
ON (wwpqm.id = wwpqsr.quiz_id)
WHERE wwpqsr.user_id = 1;
I need to do a limit on the result here at the end, that is not being shown right now for functionality purposes, since I need to only get results returned from the p_wp_pro_quiz_statistic table where the count of correct_count equals the count of rows from the incorrect_count column. How can I do this within an INNER JOIN here? All within 1 query? Possible? The above code returns empty result, where it should not be an empty result. How should something like this be done?
As I said in comments, you can't use aggregate functions as a where clause unless it is a field from a subquery. For your case I think you are looking for:
SELECT wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
FROM wp_wp_pro_quiz_statistic_ref AS wwpqsr
INNER JOIN wp_wp_pro_quiz_statistic AS wwpqs
ON ( wwpqs.statistic_ref_id = wwpqsr.statistic_ref_id )
INNER JOIN wp_wp_pro_quiz_master AS wwpqm
ON (wwpqm.id = wwpqsr.quiz_id)
WHERE wwpqsr.user_id = 1
GROUP
BY wwpqsr.statistic_ref_id,
wwpqsr.create_time,
wwpqm.name
HAVING COUNT(wwpqs.correct_count) = COUNT(wwpqs.incorrect_count);

MySql dynamic select

Table Name: Look
FieldName: LookUp
example fieldname value : Country.CountryCode
While making a select inside table 'Look' I should dynamically split on value of the fieldname 'LookUp' and get the first value as Tablename and second value as Fieldname to do a dynamic select. I have the split function in place the problem is how to make it work in a case statement or maybe somebody has an alternative solution. currently i have this which is clearly not working
SELECT l.Id,
case when l.lookup is not null then
SELECT t.Id
FROM (SPLIT_STR(l.LOOKUP,'.',1)) AS t
WHERE t.(SPLIT_STR(l.LOOKUP,'.',2)) = l.attValue
LIMIT 1
END AS attValue
FROM look as l
Don't believe it is possible to pick up the table name from a field. Does suggest that there is an issue with your database design though.
Previous similar question:-
MYSQL query using variable as table name in LEFT JOIN
If there is a limited number of related tables / fields to join on and you know them all in advance then something like the following might do it:-
SELECT l.Id,
CASE
WHEN SPLIT_STR(l.LOOKUP,'.',1) = 'tableA' THEN tableA.Id
WHEN SPLIT_STR(l.LOOKUP,'.',1) = 'tableB' THEN tableB.Id
WHEN SPLIT_STR(l.LOOKUP,'.',1) = 'tableC' THEN tableC.Id
WHEN SPLIT_STR(l.LOOKUP,'.',1) = 'tableD' THEN tableD.Id
ELSE NULL
END AS SubId
FROM look as l
LEFT OUTER JOIN tableA ON tableA.ColA = l.attValue
LEFT OUTER JOIN tableB ON tableA.ColB = l.attValue
LEFT OUTER JOIN tableC ON tableA.ColC = l.attValue
LEFT OUTER JOIN tableD ON tableA.ColD = l.attValue
Ie, join against every possible sub table and use a CASE to return the field from the one you want.
But if you are reduced to doing this then I would suggest redesigning the database at the earliest opportunity.

IF/CASE in an outer join

We have two tables that I need to join on where a column (which, at this point, I can't change how/what values are used in that column) aren't using the same value. So depending on the value of the column in the first table, I need to join/select a particular value in the second table. Here is an example (which obviously doesn't work) of what I am trying to do:
SELECT Alert.*, Comments.Comment FROM
Alert
LEFT OUTER JOIN Comments ON Comments.ObjectId = Alert.ObjectId AND
CASE
WHEN Alert.ObjectType = 'U' THEN Comments.ObjectType = 'USER'
WHEN Alert.ObjectType = 'E' THEN Comments.ObjectType = 'EVENT'
END CASE
So I want everything from the Alert table and, if there are corresponding records in the Comments table, I want those, too. But only for the appropriate/matching ObjectType.
I've tried this using both CASE and IF but I can't seem to get it to work. Is something like this possible?
The CASE statement is made to return a value, not to perform an operation.
Also change the last END CASE to just END.
Use that returned value to compare with in the join condition.
Try:
SELECT Alert.*, Comments.Comment FROM
Alert
LEFT OUTER JOIN Comments ON Comments.ObjectId = Alert.ObjectId AND
Comments.ObjectType =
CASE Alert.ObjectType
WHEN 'U' THEN 'USER'
WHEN 'E' THEN 'EVENT'
END
I recommend you handle this with a UNION. In one union join the user comments in another the event comments:
SELECT Alert.*, userComments.Comment
FROM alert
LEFT OUTER JOIN comments usercomments ON userComments.ObjectId = Alert.ObjectId AND usercomments.objecttype='USER'
WHERE alert.objecttype = 'U'
UNION
SELECT Alert.*, eventComments.Comment
FROM alert
LEFT OUTER JOIN comments eventcomments ON eventComments.ObjectId = Alert.ObjectId AND eventcomments.objecttype='EVENT'
WHERE alert.objecttype = 'E'
You don't have to alias them they way I did - it just helps readability.
Why not just:
SELECT Alert.*, Comments.Comment FROM
Alert
LEFT OUTER JOIN Comments ON Comments.ObjectId = Alert.ObjectId AND
Alert.ObjectType = LEFT(Comments.ObjectType, 1);
It seems a lot simpler...
EDIT
Based on your comment, it seems that not all 'matching' values start with the same letter. In this case, I would recommend designing an intermediate table with columns AlertType char(1) and CommentType varchar(50). Insert each combination of TypeId, like U, User; E, Event; etc. The you can modify your SQL to read
SELECT Alert.*, Comments.Comment FROM
Alert
LEFT OUTER JOIN Intermediate i on Alert.ObjectType = i.AlertType
LEFT OUTER JOIN Comments ON Comments.ObjectId = Alert.ObjectId AND
Comments.ObjectType = i.CommentType;