Select values from table with multiple attributes - mysql

I have 3 tables:
1: products (id, name)
2: product_attributes (attribute_id, name)
3: product_attributes_selected (product_id, attribute_id,value)
Now I want to get all product_id that have two or more desired attributes and values
How can I accomplish that?
I tried this, but it failed:
select p.id,p.nazwa_pl
from produkty p,produkty_atrybuty_wartosci paw
where (paw.atrybut_id=2 and paw.wartosc=4)
and (paw.atrybut_id=3 and paw.wartosc=0)
and p.id=paw.produkt_id
group by p.id

I'n my example we have a table named Attribs, some how you need to create (e.g. TABLE variable, or CTE or some other way), my code will show you how to JOIN it.
CREATE TABLE Attribs( Attrib INT, Val INT )
^^ Populate some data in it... (again could be automated) ^^
select p.id,p.nazwa_pl
from produkty p
JOIN produkty_atrybuty_wartosci paw
ON p.id=paw.produkt_id
JOIN Attribs AS A
ON A.Attrib = paw.atrybut_id
AND A.Val = paw.wartosc

With the following query you get all ids. The idea is the following: you get all products that have the attribute 2 OR 3 OR 4. Afterwards, you group the result by the product id and count the grouped items (grouped). Only those entries that group 3 entries (you are searching for 3 attribute ids) are sufficient. Obviously, the resulting products can have more attributes, but you asked for at least the provided attributes.
SELECT p.id, p.nazwa_pl, paw.bibkey, paw.keywordid, COUNT(*) as grouped
FROM produkty p, produkty_atrybuty_wartosci paw
WHERE p.id=paw.produkt_id AND paw.atrybut_id IN (2, 3, 4) GROUP BY p.id
HAVING grouped = 3;

Having a table1 resource with columns {'id','name',....} and a table2 resource_attribute with columns {'id','resource_id','attribute_name','attribute_value'} where column resource_id is a foreign key pointing to Resource.id, you can do it in this way:
SELECT r.* FROM resource r LEFT JOIN resource_attribute ra ON r.id = ra.resource_id
AND (
(ra.NAME = 'height' AND ra.VALUE = '20') #First attribute
OR
(ra.NAME = 'color' AND ra.VALUE = 'blue') #Second attribute
)
GROUP BY r.id
HAVING COUNT(*) = 2 #Total number of attributes searched
of course if you want to make it dynamic, and use the attributes name and values you need, you can save the number of attributes you want to use in your query and reuse it in the final COUNT(*) function.
I use this query dynamic in my code (java) and it works.

Actualy I found ansfer that works for me
SELECT p.id,p.nazwa_pl FROM produkty p JOIN produkty_atrybuty_wartosci paw ON p.id=paw.produkt_id WHERE (paw.atrybut_id,paw.wartosc) IN ((2,4),(3,0)) GROUP BY p.id, p.nazwa_pl HAVING COUNT(DISTINCT paw.atrybut_id)=2
Thanks for Your help and time

Related

How do I get 5 values from 3 different tables? [MYSQL]

MYSQL
I am trying to get 5 values from 3 separate tables where each value is in it's own column.
The values I am trying to get are: SC_Name, SC_Description, AR_Name, AR_Description, AR_id
I have 3 tables where I am pulling the data from:
Service_Category
Area_Responsibility
Key_Scar
Service_Category contains the following columns:
id
SC_Name
SC_Description
Area_Responsibility contains the following columns:
id
AR_Name
AR_Description
Key_SCAR contains the following columns:
id
KS_id
SC_id
AR_id
The intention of having 3 separate tables is so that I can separate the data better in the first two tables and then in the third table link them together on the basis of KS_id and the id from Service_Category and the id from Area_Responsibility.
At the moment I have the following individual queries (KS_id is always 1):
SELECT a.SC_Name, a.SC_Description
FROM Service_Category AS a
where a.id IN (SELECT SC_id
FROM Key_SCAR
WHERE KS_id = 1);
SELECT a.AR_Name, a.AR_Description
FROM Area_Responsibility AS a
where a.id IN (SELECT AR_id
FROM Key_SCAR
WHERE KS_id = 1);
I would like to join these into one query that outputs: SC_Name, SC_Description, AR_Name, AR_Description and AR_id.
I have tried to union these two queries as follows:
SELECT a.SC_Name, a.SC_Description
FROM Service_Category AS a
where a.id IN (SELECT SC_id
FROM Key_SCAR
WHERE KS_id = 1)
UNION
SELECT b.AR_Name, b.AR_Description
FROM Area_Responsibility AS b
where a.id IN (SELECT AR_id
FROM Key_SCAR
WHERE KS_id = 1);
This however does not work for my output as the interpreter I'm using places AR_Name & AR_Description under the column headings SC_Name and SC_Description. I need each of the outputted values to be in their own columns.
I hope this explains it well enough, and thank you in advance for the assistance!
Here's how you can try to achieve your required results by using the join method,
SELECT SC.SC_Name, SC.SC_Description, AR.AR_Name, AR.AR_Description, KS.AR_id
FROM Key_SCAR KS
INNER JOIN Service_Category SC ON SC.id = KS.SC_id
INNER JOIN Area_Responsibility AR ON AR.id = KS.AR_id
WHERE KS.KS_id = 1;

LEFT JOIN - fetching all data from left table with no matches in the right one

In my project, I have two tables like this:
parameters (
id PRIMARY KEY,
name
)
and
parameters_offeritems (
id_offeritem,
id_parameter,
value,
PRIMARY KEY (id_offeritem, id_parameter)
)
I'm not showing structure of offeritems table, because it's not necessary.
Some sample data:
INSERT INTO parameters (id, name) VALUES
(1, 'first parameter'), (2, 'second parameter'), (3, 'third parameter')
INSERT INTO parameters_offeritems (id_offeritem, id_parameter, value) VALUES
(123, 1, 'something'), (123, 2, 'something else'), (321, 2, 'anything')
Now my question is - how to fetch (for given offer ID) list of all existing parameters, and moreover, if for the given offer ID there are some parameters set, I want to fetch their value in one query.
So far, I made query like this:
SELECT p.*, p_o.value FROM parameters p LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter WHERE id_offeritem = OFFER_ID OR id_offeritem IS NULL
But it fetches only those parameters, for which there are no existing records in parameters_offeritems table, or parameters, for which value are set only for the current offer.
To get all parameters, plus the value of any parameters set for a specific Offer Item, you need to move the Offer ID logic into the join like this (see below).
SELECT p.*, p_o.value
FROM parameters p
LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter
AND id_offeritem = OFFER_ID;
If you have logic in your WHERE clause referring to fields in a table you are doing a LEFT JOIN on, you effectively change your JOIN to an INNER JOIN (unless you are checking for a NULL).
The magic word you're looking for is OUTER JOIN. Jeff Atwood did a nice Venn-diagram explanation here.
Your query was almost perfect, just WHERE in wrong pace:
SELECT p.*, p_o.value FROM parameters p
LEFT JOIN (
SELECT * FROM parameters_offeritems
WHERE id_offeritem = OFFER_ID) as p_o
ON p.id = p_o.id_parameter

better way of doing this SELECT in MySQL

Best way to do this SELECT?
I've got this tables:
t_department
id
name
t_users
id
name
type
*type can be:
1 SuperUser
2 normalUser
t_department_superuser
(A department can have many superUsers)
-idSuperUser
-idDepartment
t_superuser_normaluser
(A superUser can have many normalusers)
-idSuperUser
-idNormalUser
and finally
t_actions
-id (autonumeric)
-idUser (this can be an id of superUser or normalUser)
-action
Given a department name, for example "mainDepartment"
I need to get all records from t_actions of all normalusers and all superusers of that department
I have this, it works, but I am not an SQL expert (I am using MySQL) and I think it is not the best way to do the select, and t_actions is going to have loads of rows:
SELECT id,idUser,action
FROM t_actions
WHERE (idUser IN (
SELECT DISTINCT t_department_superuser.idSuperUser FROM t_department
RIGHT JOIN t_department_superuser ON t_department_superuser.idDepartment = t_department.id
LEFT JOIN t_superuser_normaluser ON t_superuser_normaluser.idSuperUser = t_department_superuser.idSuperUser
WHERE name='mainDepartment'
UNION ALL
SELECT DISTINCT t_superuser_normaluser.idNormalUser
FROM t_department
RIGHT JOIN t_department_superuser ON t_department_superuser.idDepartment = t_department.id
LEFT JOIN t_superuser_normaluser ON t_superuser_normaluser.idSuperUser = t_department_superuser.idSuperUser
WHERE name='mainDepartment')
ORDER BY id;
Any suggestions to make this better? thank you!!
because you are using left and right joins there will be null records, which is why you need the UNION... you can cut out the UNION with a simple null check
SELECT id, idUser, action
FROM t_actions
WHERE idUser IN
( SELECT DISTINCT COALESCE(tsn.idNormalUser, tds.idSuperUser)
FROM t_department td
RIGHT JOIN t_department_superuser tds ON tds.idDepartment = td.id
LEFT JOIN t_superuser_normaluser tsn ON tsn.idSuperUser = tds.idSuperUser
WHERE td.name='mainDepartment'
)
ORDER BY id;
note i also added alias's to your table names so its easer to write out and read the columns you are trying to select and join on.
EDIT
with the data the only possible way to do it with this table design is like this
SELECT id, idUser, action
FROM t_actions
WHERE idUser IN
((SELECT tds.idSuperUser
FROM t_department td
JOIN t_department_superusers tds ON tds.idDepartment = td.id
WHERE td.name='MAIN')
UNION
(SELECT tsn.idNormalUser
FROM t_department td
JOIN t_department_superusers tds ON tds.idDepartment = td.id
JOIN t_superuser_normaluser tsn ON tsn.idSuperUser = tds.idSuperUser
WHERE td.name='MAIN')
)
ORDER BY id;

Using ALL with MySQL

I have three tables: Suppliers (id, name...), TypeOfServices(id, service...) and SupplierService(supplierId, ServiceId).
As search input, I have a list with type of service ids. I can have (1) or (1,3) or (1,2,3). I need a query in MySQL that will return to me all the Suppliers that provide the services that are in the search (it can provide other services as well, but it MUST provide the ones that are in the search).
I can't use IN, because IN(2,3) will return the suppliers that have serviceId 2 OR 3, and I need 2 AND 3.
I tried with ALL like this:
SELECT * FROM suppliers as s
INNER JOIN supplierservices as ss ON ss.SupplierId = s.Id
WHERE ss.ServiceId = ALL (SELECT id FROM typeofservices WHERE id IN (2, 3))
but it's not giving me any results and I have a supplier that provides both services (with id 2 and 3). I think that SQLServer accepts the query like this ALL(2,3), but in MySQL it's not working like that, you have to have a query inside ALL.
Try
SELECT * FROM suppliers as s
INNER JOIN supplierservices as ss ON ss.SupplierId = s.Id
WHERE ss.ServiceId IN(2,3)
GROUP BY s.id
HAVING COUNT (DISTINCT ServiceId)=2
You can do this as:
SELECT s.*
FROM suppliers s INNER JOIN
supplierservices ss
ON ss.SupplierId = s.Id
GROUP BY s.id
HAVING sum(id = 2) > 0 and
sum(id = 3) > 0;
Just add a new condition to the having clause for each value you need.

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