How make this eav query to make horizontal result - mysql

The case:
tables:
product:
product_id|name |
------------------------
1 |iphone 4 |
2 |gallaxy 2 |
3 |blackbery 6 |
product_attribute:
id|product_id|attribute_id
--------------------------------------------------
1 |1 |2
2 |1 |6
. . .
attribute:
------------------------------
attribute_id|name |value|
1 |width |300
2 |width |320
3 |width |310
4 |height|390
5 |height|370
6 |height|380
should get result:
product_id|height|width
1 |380 |320
......................
Edit:
height and width attributes its only part of product attributes - product need to have dynamic ability to be added by the user in backend as it done in magento, because that i choose eav db design.
Please write queries if it possible in case we dont know which names product have.
Thanks

There are several ways to implement this. Something like this should work joining back on the table multiple times for each attribute value:
SELECT p.product_id,
a.value height,
a2.value width
FROM Product p
JOIN Product_Attribute pa ON p.product_id = pa.product_id
JOIN Attribute a ON pa.attribute_id = a.attribute_id AND a.name = 'height'
JOIN Product_Attribute pa2 ON p.product_id = pa2.product_id
JOIN Attribute a2 ON pa2.attribute_id = a2.attribute_id AND a2.name = 'width'
And here is the Fiddle.
Here is an alternative approach using MAX and GROUP BY that I personally prefer:
SELECT p.product_id,
MAX(Case WHEN a.name = 'height' THEN a.value END) height,
MAX(Case WHEN a.name = 'width' THEN a.value END) width
FROM Product p
JOIN Product_Attribute pa ON p.product_id = pa.product_id
JOIN Attribute a ON pa.attribute_id = a.attribute_id
GROUP BY p.product_id
Good luck.

One approach is to use correlated subqueries in the SELECT list, although this can be less than optimum for performance on large sets. For retrieving just a few rows rows from the product table, it won't be bad. (You'll definitely want appropriate indexes.)
SELECT p.product_id
, ( SELECT a1.value
FROM attribute a1
JOIN product_attribute q1
ON q1.attribute_id = a1.attribute_id
WHERE q1.product_id = p.product_id
AND a1.attribute_name = 'height'
ORDER BY a1.id
LIMIT 0,1
) AS height_1
, ( SELECT a2.value
FROM attribute a2
JOIN product_attribute q2
ON q2.attribute_id = a2.attribute_id
WHERE q2.product_id = p.product_id
AND a2.attribute_name = 'width'
ORDER BY a2.id
LIMIT 0,1
) AS width_1
FROM product p
WHERE p.product_id = 1
This query will return the row from product, along with the values of the attributes, if they exist. If the attribute values are not found, the query will return a NULL in place of the attribute value. (This differs from the behavior of a query that uses INNER JOIN in place of correlated subquery... where a "missing" row from the attribute or product_attribute table would filter out the row from product being returned.)
The purpose of the LIMIT clauses is to guarantee that the subqueries will return not return more than one row. (If a subquery in the SELECT list were return more than one row, MySQL would return an error.) The purpose of the ORDER BY is to make the query deterministic, again, in the event there is more than one row that satisfies the subquery. (Absent the ORDER BY clause, when there is more than one row, MySQL is free to arbitrarily return whichever row it chooses to.)
For "multi-valued" attributes, the same approach works. We just add more subqueries, but specify LIMIT 1,1 to return the second attribute value, LIMIT 2,1 to return the third value, etc.
(Oh, the joy of an EAV model implemented in a relational database.)
Followup:
Q: "... more general case as it happen in eav db that we dont know before which attributes names we have."
A: The relational model is based on the principle that a tuple contains a specified number of columns, of a specified type.
What you are (apparently) trying to do is return a variable number of columns when you run a query. A SELECT statement includes a specific list of expressions to be returned; this cannot vary and the datatypes of the values returned by each expression does not vary from row to row.
The query above returns one instance of a "height" attribute value, and one instance of a "width" attribute value, for each product.
For a more "more general case", we would really expect that each attribute value would be returned on its own separate row.
The more general query, if you don't know "ahead of time" what attributes are associated with a product would be:
SELECT p.product_id
, a.attribute_id
, a.name AS attribute_name
, a.value AS attribute_value
FROM product p
LEFT
JOIN product_attribute q
ON q.product_id = p.product_id
LEFT
JOIN attribute a
ON a.attribute_id = q.attribute_id
WHERE p.product_id = 1
ORDER
BY p.product_id
, a.name
, a.attribute_id
That will return a resultset which can easily be processed:
product_id attribute_id attribute_name attribute_value
---------- ------------ -------------- ---------------
1 6 height 380
1 2 width 320
Q: "looks like should be done in 2 stages: 1. get all attributes names for product 2.then your code with server side code of attributes names in for loop"
A: No, it looks like a single query will return all the attribute name and value pairs for a product. Each attribute name/value will be on a separate row.
There's no need to use a "for loop" to generate additional queries against the database. Yes, it's possible to do that, but totally unnecessary.
If you have some bizarre requirement to compose another query to run against the database to return the resultset in the format you specified, whatever processing you would do to process the resultset from the "more general case" statement, it would probably be more efficient to just process the resultset, without running any more queries against the database.
If you need to return a resultset that looks like this:
product_id height width
---------- ------ -----
1 380 320
(as bizarre a requirement as that is, to compose another query) it's entirely possible to use that resultset from the "more general query" to generate a query that looks like this:
SELECT 1 AS product_id, '380' AS height, '320' AS width
Though such an exercise is rather pointless, given that you aren't returning any new information you didn't return previously, and now you have another resultset that you need to process, which just seems to me to be a boatload of unnecessary overhead.

Let me first say this is a really poor design. Under your current approach, you will need to run multiple subqueries or joins with table aliases to achieve the result you want.
SELECT
product_id,
(
SELECT product_attribute.value
FROM product_attribute, attribute
WHERE product_attribute.product_id=product.product_id
AND product_attribute.attribute_id=attribute.attribute_id
AND product_attribute.name = 'width'
) AS 'width',
(
SELECT product_attribute.value
FROM product_attribute, attribute
WHERE product_attribute.product_id=product.product_id
AND product_attribute.attribute_id=attribute.attribute_id
AND product_attribute.name = 'height'
) AS 'height'
FROM
product
ORDER BY
...
Let me suggest:
attribute
attribute_sid (eg, string id)
product
product_id
name
...
product_attribute
product_id (foreign key to product table)
attribute_sid (foreign key to attribute table)
value
This way, you have a definitive list of attributes, and a single attribute value per product.
SELECT attribute_sid, value FROM product_attribute WHERE product_id = 1
... will retrieve all the attributes and values, which can conveniently be placed in a dict, array, or map.

Related

MySQL query conditional or subquery

I'm finding that SQL related questions are very difficult to express conversationally so please forgive me if this makes no sense.
I'm migrating data out of a CMS database to MySQL and I would like to simplify the data structure wherever possible. The current category scheme has 7 categories and has an item to category relationship setup as a many-to-many so a junction table is required.
It would be better setup as two one-to-many relationships so that an extra table is not required. The actual category setup that I need is as follows:
Category 1
Option 1
Option 2
Category 2
Option 3
Option 4
Option 5
Option 6
Option 7
Each item belongs to one value for Category 1, and one value for Category 2. This is the general form of the query I'm creating:
SELECT items.entry_id, categories.cat_id
FROM items
INNER JOIN categories
ON items.entry_id = categories.entry_id
WHERE items.item_type = 6
How would I add a conditional or sub query so that I got the results of the SELECT clause like:
SELECT items.entry_id, categories.cat_id1, categories.cat_id2
where the value of cat_id1 and cat_id2 are the values described above?
****Update****
I have made some progress getting the query I need (the tables are too complicated to post here for examples but here is a sample query):
SELECT exp_weblog_data.entry_id,
exp_weblog_data.field_id_27 AS Title,
exp_weblog_data.field_id_29,
exp_weblog_data.field_id_32,
exp_weblog_data.field_id_33,
exp_weblog_data.field_id_28,
exp_weblog_data.field_id_84,
exp_relationships.rel_child_id,
CASE WHEN exp_category_posts.cat_id = '15' OR exp_category_posts.cat_id = '16' THEN exp_category_posts.cat_id END as cat1,
CASE WHEN exp_category_posts.cat_id = '17' OR exp_category_posts.cat_id = '20' THEN exp_category_posts.cat_id END as cat2
FROM exp_weblog_data
INNER JOIN exp_relationships
ON exp_weblog_data.entry_id = exp_relationships.rel_parent_id
INNER JOIN exp_category_posts
ON exp_weblog_data.entry_id = exp_category_posts.entry_id
WHERE exp_weblog_data.weblog_id = 6
This gets me the two columns I want for Cat1 and Cat 2 but there are still two problems here - the inner join on exp_category_posts is resulting in 2 rows for each record where I only want one row for each value of entry_id. Secondly, in the case statements, I want to set a value of A if cat_id = 15 and B if cat_id = 16 but I can't seem to find the right syntax for this without getting errors.
I hope this clears things up a bit!
There is no reasonable way to avoid an extra table when setting up a many-to-many relationship.
Do not have multiple columns for multiple categories (cat_id1, cat_id2, ...).
Do not put multiple ids in a single column ("123,234,345").
Both of those lead to problems that are worse than having the mapping table.

MYSQL - Should I use multiple subtables or one subtable for data descriptors?

I am creating a table to store data from various government reports. An example row in the table would be:
Values_table
Date Location Report Attribute Value
'2014-09-29' 'U.S.' 'LM_HG201' 'Price' 210
Obviously I do not want to save strings for all of these columns, so I will be making subtables with ids to use instead. In order to balance table speed and ease of use, would it be better to make multiple subtables like:
Location_table
ID Location
1 'U.S'
2 'World'
Report_table
ID Report
1 'LM_HG201'
Attribute_table
ID Attribute
1 'Price'
Where my query of the values table is something like:
SELECT value FROM Values_table
WHERE location = (SELECT id FROM locations_table WHERE location = 'U.S')
AND attribute = (SELECT id FROM attributes_table WHERE attribute = 'price')
Or would it be better to use a single descriptors subtable, like this:
Descriptors_table
ID Location Report Attribute
1 'U.S.' 'LM_HG201' 'Price'
Where my query of the values table would be:
SELECT value FROM values_table
WHERE descriptor_id IN (SELECT id FROM descriptors_table
WHERE location = 'U.S.' AND report = 'LM_HG201' AND attribute = 'Price')
In my mind the second approach seems better but I've never seen it done this way. Any thoughts on which is worse/better?
Normalization rules and business requirements ought to drive your design.
If Location is repeated for several reports, it might make sense to normalize it.
I would not split out Price, because that would appear to be unique to a report. Repetitions down to the penny would be serendipity.
You should learn how to design relational models and what normalization rules are.
SELECT V.Value
FROM Values_table V
INNER JOIN locations_table L ON L.id = V.location
INNER JOIN attributes_table A ON A.Id = V.attribute
INNER JOIN descriptors_table D ON D.id = V.descriptor_id
WHERE L.location = 'U.S'
AND A.attribute = 'price'
AND D.report = 'LM_HG201'
Your schema should be
Id Location Report Price
and 210 should be a value under 'Price' in the corresponding row.
If you have different attributes for different types of reports, use subtables and/or subtypes. Most systems implement these (but not MySQL).

stuck on how to do a specific query

I have three tables, items, values, and properties, their structure....
Items
id
title
desc
timestamp
Values
id
value
itemID
propID
Properties
id
name
desc
timestamp
Essentially, with this structure I can create an item, and then assign it any property or value. But I'm running into an issue of how I can search for existing items based on properties and values.
Such that, if I had an item, with a composite key dumped into this, if it were a normal table, I could so "SELECT * FROM items WHERE key1 = 'val1' AND key2 = 'val2'", but I can't figure out how to emulate a search like that, with this kind of structure. Does anyone else have any ideas?
This is for a system I was thrown into, so changing the layout of the tables may not be something I'll be given permission to do.
EDIT: I could break this into multiple steps, but the solutions I'm currently looking at will be slow and could becoming somewhat memory intensive, given the size of these data collections.
I would use an INNER JOIN query
SELECT *
FROM Items a
INNER JOIN Values b
ON a.id = b.itemID
INNER JOIN Propeties c
ON b.propID = c.id
WHERE c.title = 'height' AND b.value = '55'
Now in where you can place any of your column table, they are all joine now, just remember to preoend table alias (a,b,c) to your column name according
If you want to avoid joins you can create another table which will have three columns Item_Id, Property_Id and Value_Id. In this table you can search any thing on the basis of Id.
OR
You can do this:
SELECT * FROM Items WHERE IN (SELECT itemID FROM Values WHERE value=SOMEVALUE)

Mysql JOIN multiple tables and select mulitple values

First of all I want to apologize for my bad English. I have a problem with multiple tables.
I'm not exactly a newby in the mySQL world but I cannot figure out a solution for this problem.
For this problem I use 4 tables.
Categories
Products
Specifications
Specificationvalues
Every category has specifications and products and every specificationvalues has products and specification id's.
Now the user can make a selection with different values.
This is where my problem is.
When the user selects value "green" and legs "4" I want all green products with 4 legs.
So I used a JOIN (all kinds i think) to select the right product (example below)
SELECT DISTINCT products.id
FROM products
LEFT JOIN specificationvalues ON specificationvalues.products_id = products.id
LEFT JOIN specifications ON specificationvalues.specifications_id = specifications.id
WHERE specifications.name='materiaal'
AND specifications.name='kleur'
AND specificationvalues.id='77'
AND specificationvalues.id='78'
The problem is that all the values are in separate rows. That's why the WHERE doesn't work. I do not get MySQL error's. Only that it returns 0 rows.
I hope someone can help me! I got a lot of good things from this forum so I hope it will help me again!
I don't know why my changes yesterday where not saved. But here is my data again:
SPECIFICATIONS Table
ID CATEGORIES_ID NAME
38 297 Material
39 297 Measures
40 297 Color
SPECIFICATIONVALUES Table
ID SPECIFICATIONS_ID PRODUCTS_ID VALUE
1 38 988979 Masive wood
2 39 988979 24x57x98
3 40 988979 Yellow
4 40 988980 Black
5 39 388980 24x57x98
PRODUCTS Table
ID NAME
988979 Table
988980 Chair
So now I want all black prducts with measure 24x57x98. I hope you can help me!
I've seen many instances of tables that have rows to simulate representing "columns" where the name is a descriptor and the "id" or "value" is the associated want value.
What you need to consider is what does ONE ROW look like. Your join from specifications name to the specifications values. Does the '77' value only correspond to the 'material' spec, 'kleur', or both... likewise with 78. You could have a variety of combinations, such as
where ( specifications.name = 'material' and specificationValues.id = '77' )
OR ( specifications.name = 'kleur' and specificationValues.id = '78' )
Or, if the specification values ID is regardless of the spec name, you could use
where specifications.name in ('material', 'kleur' )
AND speciificationValues.ID in ( '77', '78' )
PER YOUR REVISED SAMPLE DATA...
In this type of criteria you want, I would do by applying a double-join to represent each criteria you want, such as:
select p.*,
sv1.Value as NameOfColor,
sv2.Value as ProductMeasurement
from
Products p
JOIN SpecificationValues sv1
on p.ID = sv1.products_id
AND sv1.Value = 'Black'
JOIN Specifications s1
on sv1.Specifications_ID = s1.ID
AND s1.Name = 'Color'
JOIN SpecificationValues sv2
on p.ID = sv2.products_id
AND sv2.Value = '24x57x98'
JOIN Specifications s2
on sv2.Specifications_ID = s2.ID
AND s2.Name = 'Measures
Now, may look complex, but look at the simplicity (by explicit spacing between join segments). However, if you ever wanted to add more "crieria" requirements, just duplicate by creating a similar sv3, sv4, sv5 set... Now, that said, if you are building this out dynamically where a user can pick more things, and you are providing some sort of "selection" of the Readable description (Color, Measure, Material), then just preserve the IDs so you don't need the extra join, just know the actual ID, and it would be simplified to...
select p.*,
sv1.Value as NameOfColor,
sv2.Value as ProductMeasurement
from
Products p
JOIN SpecificationValues sv1
on p.ID = sv1.products_id
AND sv1.Specifications_ID = 40
AND sv1.Value = 'Black'
JOIN SpecificationValues sv2
on p.ID = sv2.products_id
AND sv2.SpecificationsID = 39
AND sv2.Value = '24x57x98'
Now, back to original answer, you could get the same results (provided you never have a color of "24x57x98", or a measurement of "Black" per your example data. You can apply the IN (list of codes) AND IN (list of values) and using a HAVING clause to ensure proper count of matched elements IS found. My FINAL query would not use "Products" as the first table, but the second because you could have 10,000 products, but only 142 that are of a given size... start by the table/criteria of a smaller set and join to the products to get the name.
select
p.id,
p.name
from
specificationValues sv
join products p
on sv.products_id = p.id
where
sv.specifications_ID IN ( 39, 40 )
AND sv.value in ( 'Black', '24x57x98' )
group by
p.id
having
count(*) = 2
Ensure your specificationValues table has an index on (specifications_ID, value). This way, the index can match HOW you are looking for data. Some may even suggest having all 3 parts in the index for (specifications_ID, value, products_id)
That's not because all the values are in separate rows, that's because your WHERE clause is always false :
WHERE specifications.name='materiaal' AND specifications.name='kleur'
AND specificationvalues.id='77' AND specificationvalues.id='78'
You can't have and id equals to 77 AND 78.
You must use this kind of code :
WHERE (specifications.name='materiaal' OR specifications.name='kleur')
AND (specificationvalues.id='77' OR specificationvalues.id='78')
EDIT : my last AND will probably also be an OR, depending of your tables.

Mysql Query - Joins causing confusion for me in this query

I have a query that returns results related to items that match a specific category...
There are 3 mysql tables that results to this, items, categories and item_categories.
These i assume are self explanatory, but the latter, is a linking table that links any specific item to any specific category, using a match of id's.
The items table contains one row, with an id value of 1.
The categories table is filled with 15 rows, with id values of 1-15.
the item_categories table contains one row, the item_id value is 1 and the category_id value is 5.
This is the mysql query in its php form:
$catResultQuery = "
SELECT i.id, name, price
FROM items i
INNER JOIN item_categories
ON i.id = item_id
INNER JOIN categories c
ON category_id = c.id
WHERE MATCH (c.id)
AGAINST ('{$_SESSION['input']}' IN BOOLEAN MODE)
ORDER BY name
";
The session variable has a value of 5, but for some reason, this query displays a 0 result set.
Even when i run the query in php myadmin, it returns 0 rows.
And i am confused, because in my head, the logic behind all of this seems fairly simple, but for some reason i get 0? Does anyone have any idea where i have gone wrong with this?
Any advice and input would be greatly appreciated, thank you!!
Ok, I see now that you're building the SQL dynamically. If that's the case, then this should work:
SELECT i.id, name, price
FROM items i
INNER JOIN item_categories
ON i.id = item_id
INNER JOIN categories c
ON category_id = c.id
WHERE c.id
IN ('{$_SESSION['input']}')
ORDER BY name
Just make sure '{$_SESSION['input']}' is comma delimited and be aware that this carries the risk of SQL injection because you're constructing the SQL on the fly.