MySql dynamic select - mysql

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.

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.

MySQL Query limiting results by sub table

I'm really struggling with this query and I hope somebody can help.
I am querying across multiple tables to get the dataset that I require. The following query is an anonymised version:
SELECT main_table.id,
sub_table_1.field_1,
main_table.field_1,
main_table.field_2,
main_table.field_3,
main_table.field_4,
main_table.field_5,
main_table.field_6,
main_table.field_7,
sub_table_2.field_1,
sub_table_2.field_2,
sub_table_2.field_3,
sub_table_3.field_1,
sub_table_4.field_1,
sub_table_4.field_2
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
WHERE sub_table_4.field_1 = '' AND sub_table_4.field_2 = '0' AND sub_table_2.field_1 != ''
The query works, the problem I have is sub_table_1 has a revision number (int 11). Currently I get duplicate records with different revision numbers and different versions of sub_table_1.field_1 which is to be expected, but I want to limit the result set to only include results limited by the latest revision number, giving me only the latest sub_table_1_field_1 and I really can not figure it out!
Can anybody lend me a hand?
Many Thanks.
It's always important to remember that a JOIN can be on a subquery as well as a table. You could build a subquery that returns the results you want to see then, once you've got the data you want, join it in the parent query.
It's hard to 'tailor' an answer that's specific to you problem, as it's too obfuscated (as you admit) to know what the data and tables really look like, but as an example:
Say table1 has four fields: id, revision_no, name and stuff. You want to return a distinct list of name values, with their latest version of stuff (which, we'll pretend varies by revision). You could do this in isolation as:
select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no;
(Note: see fiddle at the end)
That would return each individual name with the latest revision of stuff.
Once you've got that nailed down, you could then swap out
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
....with....
INNER JOIN (select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no) sub_table_1
ON sub_table_1.id = main_table.id
...which would allow a join with a recordset that is more tailored to that which you want to join (again, don't get hung up on the actual query I've used, it's just there to demonstrate the method).
There may well be more elegant ways to achieve this, but it's sometimes good to start with a simple solution that's easier to replicate, then simplify it once you've got the general understanding of the what and why nailed down.
Hope that helps - as I say, it's as specific as I could offer without having an idea of the real data you're using.
(for the sake of reference, here is a fiddle with a working version of the above example query)
In your case where you only need one column from the table, make this a subquery in your select clause instead of than a join. You get the latest revision by ordering by revision number descending and limiting the result to one row.
SELECT
main_table.id,
(
select sub_table_1.field_1
from sub_table_1
where sub_table_1.id = main_table.id
order by revision_number desc
limit 1
) as sub_table_1_field_1,
main_table.field_1,
...
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
WHERE sub_table_4.field_1 = ''
AND sub_table_4.field_2 = '0'
AND sub_table_2.field_1 != '';

MySQL combination of view, subquery and left join produces a strange result

Update 1
I discover when it does the wrong behaviour. If the view is composed by two tables, only the fields in the first table has values inside the subquery. I don't know why, but if I change the JOIN order, it works. As soon as I try to match another field with the second table it returns NULL again.
Update 2
I've created a working example here: http://sqlfiddle.com/#!2/d4eb97/1
Update 3
The same example works in a newer MySQL version (5.6.6) so maybe there is a bug in the 5.5 - http://sqlfiddle.com/#!9/4e140/2
I've a schema in which I ended doing a SQL like this:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
WHERE tableA.user = view.user
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
What I'm doing here is simple:
Select two fields from view
view is a MySQL view, not a real table.
The second field is a subquery of:
2.1 The field user of the table tableA
2.2 Left join with the table tableB with the relational field
There are no rows in tableB yet
2.3 Only where the the tableA user is the same as in the view
2.4 Limit 1, just for this example
Limit results to user = 1
The strange thing here is that in some situations the field b_user is NULL, but the data is ok.
I can make three changes to make it works:
fix 1
Put the user id manually make it works
SELECT view.user,
(
SELECT tableA.user
FROM tableA
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
WHERE tableA.user = 1
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
fix 2
Remove the left join also make it works:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
WHERE tableA.user = view.user
LIMIT 1
) as b_user
FROM view
WHERE view.user = 1
fix 3
Another option is not to use the MySQL view:
SELECT view.user,
(
SELECT tableA.user
FROM tableA
WHERE tableA.user = view_table_a.user
LEFT JOIN tableB ON tableA.id = tableB.tableA_id
LIMIT 1
) as b_user
FROM view_table_a INNER JOIN view_table_b ON condition
WHERE table_a.user = 1
I'm not being able to reproduce this recreating a new database schema manually, it only happens in my current setup, which I cannot expose here due to security reasons.
Why the subquery return NULL values? I need to make the first query works since I can't use any of the three fixes.
Why have the subquery in the first place? I like subqueries, they are very handy things of have around. But they shouldn't be used if they don't have to be. Queries can get complicated enough with no help from us.
You are looking for a particular user from the main table (the fact that it is really a view is irrelevant) then using the same User value to join with TableA and then optionally joining to TableB using the ID value associated with that user:
select rs.Origin, a.Origin as Same_Origin
from requests_status rs
join assignments a
on a.employee = rs.employee
and a.origin = rs.origin
left join assignments_author aa
on aa.assignment = a.id
where rs.employee = 1;
Then I noticed that in your fiddles, you create the assignments_author table but never populate it. But that doesn't really matter because you left join to it. But you don't use any data from that table. So in actuality, you don't need that table in your query at all. Thus the equivalent query would be:
select rs.Origin, a.Origin as Same_Origin
from requests_status rs
join assignments a
on a.employee = rs.employee
and a.origin = rs.origin
where rs.employee = 1;
I don't know why you get a NULL in one but not the other. But since the query above returns the same answer in both fiddles and it is the expected results, my work here is finished.
I assume this is a bug, maybe this one (http://bugs.mysql.com/bug.php?id=52051) because the query fails in MySQL 5.5 (http://sqlfiddle.com/#!2/d4eb97/1) but works in 5.6 (http://sqlfiddle.com/#!9/4e140/2)

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;