Select all rows with multiple possible values - mysql

I am trying to get all fields from the product table (*) that have the following set of sub_property:
subprop_name=X
subprop_value=Y
I have the following tables :
https://imgur.com/a/y4LGqMI (couldn't upload the picture because the format was not accepted)
So, for an example, if I have two products which has in their sub_property table a entry like this:
subprop_name=X
subprop_value=Y
I would like to return it. As described by the schema, a product can have multiple sub_property entries!
So far, this is what I have:
SELECT prod_id,prod_name from product WHERE product.prod_id IN
(
SELECT property.product_prod_id FROM property WHERE property.prop_id IN
(
SELECT property_prop_id from sub_property WHERE
(
sub_property.subprop_name="Type de scanner" AND sub_property.subprop_value="par transparence"
)
OR
(
sub_property.subprop_name="Pages/minute maximum" AND subprop_value="8.5 pages"
)
)
)
But obviously, it doesn't work because of the 'OR'.
It returns me all items that have one of the set of sub_property instead of all the products that have all the sets of sub_property.
DATABASE HERE

Using JOIN's and an IN for the tupples could be a simple solution for this.
SELECT p.prod_id, p.prod_name
FROM product p
JOIN property AS prop
ON prop.product_prod_id = p.prod_id
JOIN sub_property AS subprop
ON subprop.property_prop_id = prop.prop_id
WHERE (subprop.subprop_name, subprop.subprop_value) IN (
('Type de scanner', 'par transparence'),
('Pages/minute maximum', '8.5 pages')
)
GROUP BY p.prod_id, p.prod_name

So... I am not sure if it's the correct way to validate two answers, but I got 2 working answers.
This is the first one from #Steff Mächtel using LEFT JOIN (https://stackoverflow.com/a/53915792/5454875)
And the second one is from #Shidersz (see comments below the original question), using INNER JOIN)
"I used and approach using INNER JOIN, is something like this what you need? db-fiddle.com/f/t6RrnhDPQuEamjf2bTxFeX/5"
EDIT
#Shidersz solution doesn't work because it selects all products who as at least one of the condition, wich I don't want. I want the product to have all the conditions.

UPDATE 2:
I would suggest to use LEFT JOIN for each property and each sub_property and then check the value inside WHERE condition:
SELECT product.prod_id, product.prod_name
FROM product
LEFT JOIN property AS property_a ON property_a.product_prod_id = product.prod_id AND
property_a.prop_name = "PROPERTY_GROUP_3"
LEFT JOIN sub_property AS sub_property_a ON sub_property_a.property_prop_id = property_a.prop_id AND
sub_property_a.subprop_name="property_4"
LEFT JOIN property AS property_b ON property_b.product_prod_id = product.prod_id AND
property_b.prop_name = "PROPERTY_GROUP_4"
LEFT JOIN sub_property AS sub_property_b ON sub_property_b.property_prop_id = property_b.prop_id AND
sub_property_b.subprop_name="property_3"
WHERE sub_property_a.subprop_value="value_of_property_4" AND
sub_property_b.subprop_value="value_of_property_3"
GROUP BY product.prod_id
Example: https://www.db-fiddle.com/f/wk344Gt6hm98xEhM4jei92/6
Example with 2 new "KEYS" (Index) for better performance:
ALTER TABLE `property`
... ADD KEY `prop_name` (`prop_name`);
ALTER TABLE `sub_property`
... ADD KEY `subprop_name` (`subprop_name`);
https://www.db-fiddle.com/f/wk344Gt6hm98xEhM4jei92/7
Example with INNER JOIN instead of LEFT JOIN
I see no difference with EXPLAIN on test data, maybe Mysql optimizer handles this internal equal
SELECT product.prod_id, product.prod_name
FROM product
INNER JOIN property AS property_a ON property_a.product_prod_id = product.prod_id AND
property_a.prop_name = "PROPERTY_GROUP_3"
INNER JOIN sub_property AS sub_property_a ON sub_property_a.property_prop_id = property_a.prop_id AND
sub_property_a.subprop_name="property_4" AND
sub_property_a.subprop_value="value_of_property_4"
INNER JOIN property AS property_b ON property_b.product_prod_id = product.prod_id AND
property_b.prop_name = "PROPERTY_GROUP_4"
INNER JOIN sub_property AS sub_property_b ON sub_property_b.property_prop_id = property_b.prop_id AND
sub_property_b.subprop_name="property_3" AND
sub_property_b.subprop_value="value_of_property_3"
GROUP BY product.prod_id
https://www.db-fiddle.com/f/wk344Gt6hm98xEhM4jei92/8

Related

Querying Many to Many When 3 Tables Connected to A junction

DB MODEL
I have Model Which is Connected in above Order Now I want to Select Style Names From Style Table.
Case 1 : I want the Style Names which are both intersecting Category and Gender
Case 2 : I want the Style Names which are intersecting Gender only regardless of category.
I am newbie to SQL is there any efficient way to Get the desired result using SQL JOINS . Any Help in such case will be appreciated or is there any modelling solutions to deal with such type of situations.
Here is how you would join your tables:
SELECT *
FROM `style`
INNER JOIN `StyleCategoryGender` ON `StyleCategoryGender`.`SID` = `style`.`Code`
INNER JOIN `StyleCategoryGender` ON `Category`.`Code` = `StyleCategoryGender`.`GID`
INNER JOIN `StyleCategoryGender` ON `Gender`.`Code` = `StyleCategoryGender`.`CID`
then you can add WHERE clauses as needed
SELECT *
FROM `style`
INNER JOIN `StyleCategoryGender` ON `StyleCategoryGender`.`SID` = `style`.`CODE`
INNER JOIN `StyleCategoryGender` ON `Category`.`Code` = `StyleCategoryGender`.`GID`
INNER JOIN `StyleCategoryGender` ON `Gender`.`Code` = `StyleCategoryGender`.`CID`
WHERE `Gender`.`Name` = 'Female'

Rails - How to force associations to use alias table name

p = Patient.find(30)
p.patient_problems
The above code generates the following query
SELECT `patient_problem`.* FROM `patient_problem` WHERE `patient_problem`.`patient_id` = 30 AND (`patient_problem`.`record_status_id` = 1)
But is there any way to assign/use alias table_name like
p.patient_problems(:alias=>'p1') # just for Ex.. This code will not work
p.patient_problems(:alias=>'p2') # just for Ex.. This code will not work
So it will generate the following queries
SELECT `p1`.* FROM `patient_problem` AS `p1` WHERE `p1`.`patient_id` = 30 AND (`p1`.`record_status_id` = 1)
SELECT `p2`.* FROM `patient_problem` AS `p2` WHERE `p2`.`patient_id` = 30 AND (`p2`.`record_status_id` = 1)
Additional Info
My problem is when I try to use joins
p.patient_problems(:all,:joins=>joins)
I get this error
ActionView::Template::Error (Mysql2::Error: Not unique table/alias: 'patient_problem': SELECT `patient_problem`.* FROM `patient_problem` LEFT OUTER JOIN party on party.id = patient_problem.patient_id
LEFT OUTER JOIN party_identifier on party.id = party_identifier.party_id
LEFT OUTER JOIN blood_type on blood_type.id = party.blood_type_id
LEFT OUTER JOIN education_level on education_level.id = party.education_level_id
LEFT OUTER JOIN religion on religion.id = party.religion_id
LEFT OUTER JOIN living_arrangement on living_arrangement.id = party.living_arrangement_id
LEFT OUTER JOIN patient_problem patient_problem on patient_problem.patient_id = party.id and patient_problem.record_status_id = 1
left join (select user_type,username,user_id,auditable_id from (select MAX(id) id from audits where audits.auditable_type = 'PatientProblem' and user_type is not null group by auditable_id ) t inner join audits v on v.id=t.id ) entered_by1 on entered_by1.auditable_id = patient_problem.id
left outer join user user1 on entered_by1.user_id = user1.id
left outer join party as party_user1 on party_user1.id = user1.person_id
LEFT OUTER JOIN patient_patient_search patient_patient_search1 on patient_patient_search1.patient_id = party.id
left join search search1 on patient_patient_search1.patient_search_id = search1.id
and patient_patient_search1.patient_search_id = '75' WHERE `patient_problem`.`patient_id` = 45 AND (`patient_problem`.`record_status_id` = 1) AND ( (patient_problem.occurrence_date > '2013-01-01 00:00:00' and patient_problem.occurrence_date < '2013-06-30 23:59:59' and patient_problem.patient_problem_status_id in (5) and patient_problem.code is not null and patient_problem.code in ('10725009') ) and ( patient_patient_search1.patient_search_id in (75.0) ) ))
Ofcourse I could do some string manipulation on the generated joins query and set alias to patient_problem. But I thought setting alias for associations would be more cleaner since the joins query generated are unpredictable(in my scenario)
I am not sure what the variable joins is or how it was constructed. To alias tables in a join build your query like
Rails 3
PatientProblem.joins("as p1 OUTER JOIN patient_problem as p2 on ...")
or
PatientProblem.find(:all, :joins => "as p1 OUTER JOIN patient_problem as p2 ON ...")
you can make singleton methods for that and write the query one time and use may time like
def self.p1
#your active record query here.
end
and call like
PatientProblem.p1
Update
You can simply change the table name in your code:
Patient.table_name="p2"
I'm not sure if this would break anything else though ... so good luck!
Orignal Answer
One solution may be to define a separate model for each type of patient_problem and then do something like this:
class PatientProblem2 < ActiveRecord::Base
self.set_table_name "p2"
...
end
Another solution may be to use the ActiveRecord query interface which will allows for significant query flexibility:
http://guides.rubyonrails.org/active_record_querying.html
Perhaps you can be more specific on the nature problem you are trying to solve.

MySQL Database design advice - using joins

I am building an AJAX like search page which allows a customer to select a number filters that will narrow down the search. For instance, a user has selected an 'iPhone 5' and has additional filters for capacity (32GB, 64GB) & colour (black, white..).
The user can only select a single radio box per category (so they could select 32GB & Black).. but they could not select (32GB & 64GB & black as two of these belong to the 'capacity' category).
I have added the schema here on sqlfiddle (please ignore the fact i've removed the primary keys they exist in the proper app they have just been removed along with some other fields/data to minimise the sqlfiddle)
http://sqlfiddle.com/#!2/964425
Can anyone suggest the best way to create the query to do the following:
Get all the prices for device_id '2939' (iPhone 5) which has the 'attributes' of '32GB' AND 'Black'
I currently have this - but this only works when selecting for a single attribute:
// search for device with '64GB' & 'Black' attributes (this currently doesn't return any rows)
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19'
AND `attribute_option_id` = '47';
// search for device with '64GB' attribute only (this currently DOES return a row)
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19';
Any advice on the database design would be appreciated too
Note: I was thinking to have a new column within the 'prices' table that has the matching attribute_ids serialised - would this be not good for optimisation however (e.g would it be slower than the current method)
Since attribute_option_id is an atomic value, it cannot have two different values for the same row. So your WHERE clause cannot match any record:
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
AND `attribute_option_id` = '19' # Here for one row, attribute_option_id is either 19
AND `attribute_option_id` = '47'; # of '47'. Cannot be the both
Instead of JOIN, you could try a subquery if you feel that is more readable. I think MySQL allow that syntax:
SELECT `prices`.*
FROM `prices`
WHERE `prices`.`device_id` = '2939'
AND EXISTS (SELECT *
FROM prices_attributes
WHERE price_id = `prices`.`id`
AND attribute_option_id IN ('19', '47') )
I don't know how MySQL will optimize the above solution. An alternative would be:
SELECT `prices`.*
FROM `prices`
WHERE `prices`.`id` IN (
SELECT DISTINCT `price_id`
FROM prices_attributes
WHERE attribute_option_id IN ('19', '47')
)
I think you should use the IN operator for the attribute_option_id and you set the values dynamically to the query; Also, using group_by you have only one row per price so in effect you get all the prices. Apart from this, the design is ok.
Here, I have made an example:
SELECT `prices`.*
FROM (`prices`)
LEFT JOIN `prices_attributes` ON `prices_attributes`.`price_id` = `prices`.`id`
WHERE `prices`.`device_id` = '2939'
and `attribute_option_id` in ('19','47')
group by `prices`.`device_id`, `prices`.`price`;
Here, you can also add an order clause to order by price:
order by `prices`.`price` desc;
Another way to solve this would be to use a distinct on price, like this:
select distinct(prices.price)
from prices
where prices.device_id = 2939
and id in (select price_id from prices_attributes where attribute_option_id in (19,47));
Join against the devices_attributes_options table several times, once for each attribute the item must have
Something like this:-
SELECT *
FROM devices a
INNER JOIN prices b ON a.id = b.device_id
INNER JOIN prices_attributes c ON b.id = c.price_id
INNER JOIN devices_attributes_options d ON c.attribute_option_id = d.id AND d.attribute_value = '32GB'
INNER JOIN devices_attributes_options e ON c.attribute_option_id = e.id AND e.attribute_value = 'Black'
WHERE a.id = 2939
As to putting serialised details into a field, this is a really bad idea and would come back to bite you in the future!
SELECT * FROM prices WHERE device_id=2939 AND id IN (SELECT price_id FROM prices_attributes WHERE attribute_option_id IN (19,47));
Is it what you're looking for?
EDIT: sorry, didn't notice you're asking for query using joins

MySQL join & search

I have a problem with joining some tables, heres my structure:
tbl_imdb:
fldID fldTitle fldImdbID
1 Moviename 0000001
tbl_genres:
fldID fldGenre
1 Action
2 Drama
tbl_genres_rel:
fldID fldMovieID fldGenreID
1 1 1
2 1 2
What I’m trying to do is a query that will find all movies that is both an action movie and drama, is this possible to do without a subquery, if so, how?
What I'm trying right now is:
SELECT tbl_imdb.*
FROM tbl_imdb
LEFT JOIN tbl_imdb_genres_rel ON ( tbl_imdb.fldID = tbl_imdb_genres_rel.fldMovieID )
LEFT JOIN tbl_imdb_genres ON ( tbl_imdb_genres_rel.fldGenreID = tbl_imdb_genres.fldID )
WHERE tbl_imdb_genres.fldGenre = 'Drama'
AND tbl_imdb_genres.fldGenre = 'Action';
But this dosnt work, however it does work if I only keep one of the two WHERE's, but thats not what I want.
Two ways to do it:
1
SELECT tbl_imdb.*
FROM tbl_imdb
INNER JOIN tbl_genres_rel rel_action
ON tbl_imdb.fldID = rel_action.fldMovieID
INNER JOIN tbl_genres genre_action
ON rel_action.fldGenreId = genre_action.fldID
AND 'Action' = genre_action.fldGenre
INNER JOIN tbl_genres_rel rel_drama
ON tbl_imdb.fldID = rel_drama.fldMovieID
INNER JOIN tbl_genres genre_drama
ON rel_drama.fldGenreId = genre_drama.fldID
AND 'Drama' = genre_drama.fldGenre
This method is on the same path as your original solution. 2 differences:
The join should be inner, not left because you're trying to get movies that certainly have the corresponding genre entry
Since you want to find 2 different generes, you'll have to do the join with tbl_genres_rel and tbl_genres twice, once for each particular genre you're interested in.
2
SELECT tbl_imdb.*
FROM tbl_imdb
INNER JOIN tbl_genres_rel
ON tbl_imdb.fldID = tbl_genres_rel.fldMovieID
INNER JOIN tbl_genres
ON tbl_genres_rel.fldGenreId = tbl_genres.fldID
AND tbl_genres.fldGenre IN ('Action', 'Drama')
GROUP BY tbl_imdb.fldID
HAVING COUNT(*) = 2
Again, the basic join plan is the same. Difference here is that we join to the tbl_genres_rel and tbl_genres path just once. This on itself fetches all genres for one film, and then filters for the one's you're interested in. The ones that qualify will now have 2 rows for each distinct value of tbl_imdb.fldId. The GROUP BY aggregates on that, flattening that into one row. By asserting in the HAVING clause that we have exactly 2 rows, we ensure that we keep only those rows that have both the genres.
(Note that this assumes that there is a unique constraint on tbl_genres_rel over {fldMovieID, fldGenreID}. If such a constraint is not present, you should consider adding it.)
LEFT JOIN is not applicable in your case because records should exist on both tables. And you need to count the instances of the movie
SELECT *
FROM tbl_imdb a
INNER JOIN tbl_genres_rel b
on a.fldID = fldMovieID
INNER JOIN tbl_genres c
on c.fldGenreID = b.fldID
WHERE c.fldGenre IN ('Drama', 'Action')
GROUP BY a.Moviename
HAVING COUNT(*) > 1

MySQL - Using column value for joining in the same query

I have three tables that looks something like this:
Table joins
|ID|JOIN_NAME|
1 persons
2 companies
Table information
|ID|JOIN_ID|
1 1
2 2
Table information_extra_persons
|ID|INFORMATION_ID|NAME|
1 1 John
Table information_extra_companies
|ID|INFORMATION_ID|NAME|
1 2 IBM
How can i join together these tables in one SQL? I've tried something like:
SELECT * FROM `information`
INNER JOIN `information_extra_(SELECT `name` FROM `joins` WHERE `id` = `join_id`)`
ON `information_extra_(SELECT `name` FROM `joins` WHERE `id` = `join_id`)`.`information_id` = `information`.`id`
but I can't get it to work. Of course this isn't my actual table setup, but it's the same principle. Does anyone know how to get all the info in just one SQL?
That's actually four tables, not three. This isn't just a nitpick - it looks as though the substance of your question is "how can I use the name of the table as part of the join criteria?" (ie. how can the information_extra_ tables be treated as a single table?)
To which the answer is: you can't. (Outside of dynamic SQL.)
In this specific case, the following should return what I think you are looking for:
select j.join_name joined_entity,
case when j.join_name = 'persons' then p.name
else c.name
end joined_entity_name
from information i
inner join joins j on i.join_id = j.id
left join information_extra_persons p on i.id = p.information_id
left join information_extra_companies c on i.id = c.information_id
Alternatively, a less efficient (but more general) approach might be:
select j.join_name joined_entity,
v.name joined_entity_name
from information i
inner join joins j on i.join_id = j.id
inner join (select 'persons' entity, information_id, name from information_extra_persons
union all
select 'companies' entity, information_id, name from information_extra_companies) v
on i.id = v.information_id and j.join_name = v.entity