I have two MySQL tables:
attributes (attributeid, name)
productsattributes (productid, attributeid, displayvalue)
The required is for each attribute name called "Product Type" get all other attributes associated with this "Product Type". As an example — attributes table will look like:
attributeid name
1 A
2 B
3 Product Type
4 D
productsattributes table will look like:
productid attributeid displayvalue
1 3 FAN
1 1 Brown
1 2 Stand
2 3 FAN
2 4 D
3 3 CAR
3 4 imported
So the final result should be:
FAN (A,B, Product Type,D)
CAR (Product Type, imported)
Here is my try:
first I get all the "displayvalues" from productattributes:
SELECT DISTINCT displayvalue
FROM productsttributes
WHERE attributeid = 3;
then I loop through each "displayvalues" to find the other attributes:
SELECT a.name
FROM attributes a
INNER JOIN productsattributes pa
ON pa.attributeid = a.attributeid AND productid in (
SELECT productid
FROM productsttributes
WHERE dispalyvale = '$displayvalue')
ORDER BY a.name;
The problem is the productattributes table has about 7 million rows, so my script is taking forever .. of course I am not looking for 10 minutes solution but at least it will improve my queries a bit.
I would start with the following statements:
ALTER TABLE attributes ADD CONSTRAINT p_attributes PRIMARY KEY (attributeid);
ALTER TABLE productsattributes ADD CONSTRAINT p_productsattributes
PRIMARY KEY(productid, attributeid);
ANALYZE TABLE attributes, productsattributes;
This will make sure all important fields are indexed.
The query might look like this (also on SQL Fiddle):
SELECT trg.displayvalue,
group_concat(a.name ORDER BY trg.productid, a.attributeid)
FROM (
SELECT t.productid,t.displayvalue
FROM attributes a
JOIN productsattributes t USING (attributeid)
WHERE a.name = 'Product Type') AS trg
JOIN productsattributes p ON p.productid = trg.productid
JOIN attributes a ON a.attributeid = p.attributeid
GROUP BY trg.displayvalue
ORDER BY 1;
Please, kindly include the EXPLAIN output of your's and this queries into your question.
Try this ::
Select displayvalue, attribute_name
from
(Select
product_id from productsattributes pa inner join attributes_table at on (pa.attributeid=at.id) where at.name=?) as productList
inner join productsattributes pa2 on(pa2.product_id=productList.product_id)
inner join attributes_table at2 on (pa2.attributeid=at2.id)
Related
I have 3 tables: tags, products and relation table between them.
Relation table looks for example like this:
tagId | ProductId
1 | 1
2 | 1
2 | 9
The user can pick two options "All of these" or "One of these".
So if user picks All of these, it's means that the product must have exactly all of tags which the user chose.
So if user pick tags with id 1 and 2, it should select only product with id 1, because this product has exactly the same tags the user chose. (Another way is if the user picks the tag with id 2, it should select only product with id 9.)
So, the product has to have all tags which the user chose (no more, no less).
SQL that I already have for Any/One of these:
SELECT DISTINCT s.SKU
FROM SKUToEAN as s
LEFT JOIN ProductDetails as p ON s.ProductDetailID=p.id
JOIN ProductTagRelation as ptr ON (ptr.productId=p.id and ptr.tagId IN(Ids of selected tags))
Example behavior:
TagId = 1 it should select => None
TagId = 2 it should select => 9
TagId = 1,2 it should select = 1,9
So probably I need two queries. One for any/one of these ( I already have this one ) and the second for all of these.
With PHP I decide which query to use.
You can GROUP BY on the ProductID and use conditional aggregation based filtering inside the Having clause. MySQL automatically casts boolean values to 0/1 when using in numeric context. So, in order to have a specific tagID value available against a ProductID, its SUM(tagId = ..) should be 1.
All of these:
SELECT ptr.productId, s.SKU
FROM SKUToEAN AS s
LEFT JOIN ProductDetails AS p
ON p.id = s.ProductDetailID
JOIN ProductTagRelation AS ptr
ON ptr.productId = p.id
GROUP BY ptr.productId, s.SKU
HAVING SUM(ptr.tagID = 1) AND -- 1 should be there
SUM(ptr.tagID = 2) AND -- 2 should be there
NOT SUM(ptr.tagID NOT IN (1,2)) -- other than 1,2 should not be there
Is this you are looking for (for all condition)?
select product.id
from products
inner join <table> on products.id = <table>.productId
group by product.id
having group_concat(<table>.tagId order by <table>.tagId separator ',') = '1,2';
I have two tables
CATEGORY
id category parent_id
1 Electronic
2 Furniture
3 Phone 1
4 LCD 1
5 Watch 1
6 Desk 2
ORDER
id customer product category_id
1 John Smartphone 3
2 Marry Montior 4
3 King Wood-Desk 6
I want to find all of electronic result by child_id.
Like this..
SELECT product FROM order WHERE category_id = (category.id = 1)
RESULT
product
Smartphone
Monitor
Is there any expression like this in MySQL?
You can use a join for this. You also will need to encapsulate the order table name with backticks because order is reserved (alternatively you could rename that table to save yourself from encapsulating everytime).
SELECT product FROM `order` as o
join category as c
on o.category_id = c.id
WHERE c.parent_id = 1
The on tells the DB what to data to join on. The as creates an alias so the full table name doesn't need to be written out everytime. (The as also is optional, I find it easier to read, FROM `order` o would be the same)
An alternative approach could be using a sub-query:
SELECT product
FROM `order`
WHERE category_id in (SELECT id FROM CATEGORY where parent_id = 1)
You have to use INNER JOIN
SELECT order.product
FROM order
INNER JOIN category
ON order.category_id = category.id
WHERE category.parent_id = 1
The ON keyword shows what columns will be compare between these tables. When you make a JOIN you need to put the table name before the column name separated with "." because it is possible to exist a column with the same name in both tables.
Suppose i have the following 2 tables:
Persons Table:
Name
ID[Primary Key]
Fruits Table:
Name
ID[Foreign Key Persons.ID]
This is a table structure for storing persons and the fruits they like. Now if I want to find all the persons who like "Apple" and "Orange"(this would be dynamic). How can i design a SQL query for that?
You can use a query like the following to get the IDs of all persons who like Apples and Oranges:
SELECT p.ID
FROM Persons AS p
JOIN Fruits AS f ON p.ID = f.PersonsID
WHERE f.Name IN ('Apple', 'Orange')
GROUP BY p.ID
HAVING COUNT(DISTINCT f.Name) = 2
I have a products database which has a multi-tier category structure. Products are assigned to a category. The category table looks like this:
id name parent_id
================================
1 Electronics NULL
2 AV 1
3 Speakers 2
4 Wireless 3
What I want to do is, as part of my SELECT statement for products, output a concatenated string of the category tree.
The product is always assigned to the last category, so for example, Product "500w Wireless Speakers" would be assigned to category_id 4 (based on the above).
The ouputted column should be Electronics-AV-Speakers-Wireless.
Is this possible to do? I have looked at GROUP_CONCAT() but I'm having trouble working out the correct syntax.
Join as many times as you need, and concat the names:
select concat(a.name, '-', b.name, '-', c.name, '-', d.name) name
from mytable a
join mytable b on a.id = b.parent_id
join mytable c on b.id = c.parent_id
join mytable d on c.id = d.parent_id;
Using the tables below as an example and the listed query as a base query, I want to add a way to select only rows with a max id! Without having to do a second query!
TABLE VEHICLES
id vehicleName
----- --------
1 cool car
2 cool car
3 cool bus
4 cool bus
5 cool bus
6 car
7 truck
8 motorcycle
9 scooter
10 scooter
11 bus
TABLE VEHICLE NAMES
nameId vehicleName
------ -------
1 cool car
2 cool bus
3 car
4 truck
5 motorcycle
6 scooter
7 bus
TABLE VEHICLE ATTRIBUTES
nameId attribute
------ ---------
1 FAST
1 SMALL
1 SHINY
2 BIG
2 SLOW
3 EXPENSIVE
4 SHINY
5 FAST
5 SMALL
6 SHINY
6 SMALL
7 SMALL
And the base query:
select a.*
from vehicle a
join vehicle_names b using(vehicleName)
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
and a.vehicleName like '%coo%'
group
by a.id
having count(distinct c.attribute) = 2;
So what I want to achieve is to select rows with certain attributes, that match a name but only one entry for each name that matches where the id is the highest!
So a working solution in this example would return the below rows:
id vehicleName
----- --------
2 cool car
10 scooter
if it was using some sort of max on the id
at the moment I get all the entries for cool car and scooter.
My real world database follows a similar structure and has 10's of thousands of entries in it so a query like above could easily return 3000+ results. I limit the results to 100 rows to keep execution time low as the results are used in a search on my site. The reason I have repeats of "vehicles" with the same name but only a different ID is that new models are constantly added but I keep the older one around for those that want to dig them up! But on a search by car name I don't want to return the older cards just the newest one which is the one with the highest ID!
The correct answer would adapt the query I provided above that I'm currently using and have it only return rows where the name matches but has the highest id!
If this isn't possible, suggestions on how I can achieve what I want without massively increasing the execution time of a search would be appreciated!
If you want to keep your logic, here what I would do:
select a.*
from vehicle a
left join vehicle a2 on (a.vehicleName = a2.vehicleName and a.id < a2.id)
join vehicle_names b on (a.vehicleName = b.vehicleName)
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
and a.vehicleName like '%coo%'
and a2.id is null
group by a.id
having count(distinct c.attribute) = 2;
Which yield:
+----+-------------+
| id | vehicleName |
+----+-------------+
| 2 | cool car |
| 10 | scooter |
+----+-------------+
2 rows in set (0.00 sec)
As other said, normalization could be done on few levels:
Keeping your current vehicle_names table as the primary lookup table, I would change:
update vehicle a
inner join vehicle_names b using (vehicleName)
set a.vehicleName = b.nameId;
alter table vehicle change column vehicleName nameId int;
create table attribs (
attribId int auto_increment primary key,
attribute varchar(20),
unique key attribute (attribute)
);
insert into attribs (attribute)
select distinct attribute from vehicle_attribs;
update vehicle_attribs a
inner join attribs b using (attribute)
set a.attribute=b.attribId;
alter table vehicle_attribs change column attribute attribId int;
Which led to the following query:
select a.id, b.vehicleName
from vehicle a
left join vehicle a2 on (a.nameId = a2.nameId and a.id < a2.id)
join vehicle_names b on (a.nameId = b.nameId)
join vehicle_attribs c on (a.nameId=c.nameId)
inner join attribs d using (attribId)
where d.attribute in ('SMALL', 'SHINY')
and b.vehicleName like '%coo%'
and a2.id is null
group by a.id
having count(distinct d.attribute) = 2;
The table does not seems normalized, however this facilitate you to do this :
select max(id), vehicleName
from VEHICLES
group by vehicleName
having count(*)>=2;
I'm not sure I completely understand your model, but the following query satisfies your requirements as they stand. The first sub query finds the latest version of the vehicle. The second query satisfies your "and" condition. Then I just join the queries on vehiclename (which is the key?).
select a.id
,a.vehiclename
from (select a.vehicleName, max(id) as id
from vehicle a
where vehicleName like '%coo%'
group by vehicleName
) as a
join (select b.vehiclename
from vehicle_names b
join vehicle_attribs c using(nameId)
where c.attribute in('SMALL', 'SHINY')
group by b.vehiclename
having count(distinct c.attribute) = 2
) as b on (a.vehicleName = b.vehicleName);
If this "latest vehicle" logic is something you will need to do a lot, a small suggestion would be to create a view (see below) which returns the latest version of each vehicle. Then you could use the view instead of the find-max-query. Note that this is purely for ease-of-use, it offers no performance benefits.
select *
from vehicle a
where id = (select max(b.id)
from vehicle b
where a.vehiclename = b.vehiclename);
Without going into proper redesign of you model you could
1) Add a column IsLatest that your application could manage.
This is not perfect but will satisfy you question (until next problem, see not at the end)
All you need is when you add a new entry to issue queries such as
UPDATE a
SET IsLatest = 0
WHERE IsLatest = 1
INSERT new a
UPDATE a
SET IsLatest = 1
WHERE nameId = #last_inserted_id
in a transaction or a trigger
2) Alternatively you can find out the max_id before you issue your query
SELECT MAX(nameId)
FROM a
WHERE vehicleName = #name
3) You can do it in single SQL, and providing indexes on (vehicleName, nameId) it should actually have decent speed with
select a.*
from vehicle a
join vehicle_names b ON a.vehicleName = b.vehicleName
join vehicle_attribs c ON b.nameId = c.nameId AND c.attribute = 'SMALL'
join vehicle_attribs d ON b.nameId = c.nameId AND d.attribute = 'SHINY'
join vehicle notmax ON a.vehicleName = b.vehicleName AND a.nameid < notmax.nameid
where a.vehicleName like '%coo%'
AND notmax.id IS NULL
I have removed your GROUP BY and HAVING and replaced it with another join (assuming that only single attribute per nameId is possible).
I have also used one of the ways to find max per group and that is to join a table on itself and filter out a row for which there are no records that have a bigger id for a same name.
There are other ways, search so for 'max per group sql'. Also see here, though not complete.