I'm having difficulty with the current model for my MySQL Table, namely that I can't seem to properly query all the child nodes of a specific parent. As the title states, I'm using the adjacency model.
The problem is that most methods I have found online either query all leaf nodes, or select more than just what I'm attempting to grab.
The first tutorial I was following was on MikeHillyer.com, and his solution was to the effect of:
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;
The problem with this, is it queries all of the leaf nodes, and not just the ones related to the parents. He also suggested using the Nested Set Model, which I REALLY don't want to have to use due to it being a little more difficult to insert new nodes (I do realize it's significance though, I'd just rather not have to resort to it).
The next solution I found was on a shared slideshow on slide 53 (found from another answer here on StackOverflow). This solution is supposed to query a node's immediate children, only... The solution does not seem to be working for me.
Here's their solution:
SELECT * FROM Comments cl
LEFT JOIN Comments c2
ON(c2.parent_id = cl.comment_id);
Now, my table is a little different, and so I adjusted some of the code for it. A brief excerpt of my table is the following:
Table: category
id | parent | section | title | ...
----+--------+----------+----------+-----
1 | NULL | home | Home | ...
2 | NULL | software | Software | ...
3 | 2 | software | Desktop | ...
4 | 2 | software | Mobile | ...
5 | NULL | about | About | ...
6 | 5 | about | Legal | ...
... | ... | ... | ... | ...
When I modified the above query, I did the following:
SELECT * FROM category cat1
LEFT JOIN category cat2
ON(category.parent = cl.id);
This resulted in EVERYTHING being queried and tied in a table twice as long as the unaltered table (obviously not what I'm looking for)
I'm pretty certain I'm just doing something wrong with my query, and so I'm just hoping someone here can correct whatever my mistake is and point me in the right direction.
I know it's supposed to be easier to use a Nested Set Model, but I just dislike that option for the difficulty of adding new options.
Looks like you're very close. Your left join is guaranteeing that all records from the table will be returned.
See the below query.
SELECT c1.*, c2.id FROM category c1 INNER JOIN category c2 ON (c1.parent = c2.id);
Related
products
+----+--------+
| id | title |
+----+--------+
| 1 | Apple |
| 2 | Pear |
| 3 | Banana |
| 4 | Tomato |
+----+--------+
product_variants
+----+------------+------------+
| id | product_id | is_default |
+----+------------+------------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 4 | 1 |
+----+------------+------------+
properties
+----+-----------------+-----------+
| id | property_key_id | value |
+----+-----------------+-----------+
| 1 | 1 | Yellow |
| 2 | 1 | Green |
| 3 | 1 | Red |
| 4 | 2 | Fruit |
| 5 | 2 | Vegetable |
| 6 | 1 | Blue |
+----+-----------------+-----------+
property_keys
+----+-------+
| id | value |
+----+-------+
| 1 | Color |
| 2 | Type |
+----+-------+
product_has_properties
+----+------------+-------------+
| id | product_id | property_id |
+----+------------+-------------+
| 1 | 1 | 4 |
| 2 | 1 | 3 |
| 3 | 2 | 4 |
| 4 | 3 | 4 |
| 5 | 3 | 4 |
| 6 | 4 | 4 |
| 7 | 4 | 5 |
+----+------------+-------------+
product_variant_has_properties
+----+------------+-------------+
| id | variant_id | property_id |
+----+------------+-------------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 6 |
| 4 | 3 | 4 |
| 5 | 4 | 1 |
| 6 | 5 | 1 |
+----+------------+-------------+
I need to query my DB so it selects products which have certain properties attached to the product itself OR have those properties attached to one of its related product_variants. Also should properties with the same properties.property_key_id be grouped like this: (pkey1='red' OR pkey1='blue') AND (pkey2='fruit' OR pkey2='vegetable')
Example cases:
Select all products with (color='red' AND type='vegetable'). This should return only Tomato.
Select all products with ((color='red' OR color='yellow') AND type='fruit') should return Apple and Banana
Please note that in the example cases above I don't really need to query by properties.value, I can query by properties.id.
I played around a lot with MySQL query's but the biggest problem I'm struggling with is the properties being loaded through two pivot tables. Loading them is no problem but loading them and combining them with the correct WHERE, AND and OR statements is.
The following code should give you what you're looking for, however you should note that your table currently has a Tomato listed as yellow and a vegetable. Obviously you want the Tomato as red and a Tomato is actually a fruit not a vegetable:
Select distinct title
from products p
inner join
product_variants pv on pv.product_id = p.id
inner join
product_variant_has_properties pvp on pvp.variant_id = pv.id
inner join
product_has_properties php on php.product_id = p.id
inner join
properties ps1 on ps1.id = pvp.property_id --Color
inner join
properties ps2 on ps2.id = php.property_id --Type
inner join
property_keys pk on pk.id = ps1.property_key_id or pk.id = ps2.property_key_id
where ps1.value = 'Red' and ps2.value = 'Vegetable'
Here is the SQL Fiddle: http://www.sqlfiddle.com/#!9/309ad/3/0
This is a convoluted answer, and it may be possible to do it in a far simpler way. However given that you seem to want to be able to query by color = xx and type = xx, we clearly need to have columns with those names, which as you've intimated, means we need to pivot the data.
Furthermore, since we want to get all the combinations of colours and types for each product, we need to perform a sort of cross join, to combine them.
This leads us to the query - first we get all the types for a product and its variants, then we join that to all the colours for a product and its variant. We use union to combine the product and variant properties in order to keep them all in the same column, rather than having multiple columns to check.
Of course all products may not have this information specified, so we use left joins all the way through. If it is guaranteed that a product will always have at least one colour, and at least one type - they can all be changed to inner joins.
Also, in your example you say tomato should have a colour of red, yet in the sample data you provide i'm sure the tomato has a colour of yellow.
Anyway, here's the query:
select distinct title from
(select q1.title, q1.value as color, q2.value as type from
(
select products.id, products.title, properties.value, properties.property_key_id
from products
left join product_has_properties
on products.id = product_has_properties.product_id
left join properties
on properties.id = product_has_properties.property_id and properties.property_key_id = 1
union
select product_variants.product_id, products.title, properties.value, properties.property_key_id
from product_variants
inner join products
on product_variants.product_id = products.id
left join product_variant_has_properties
on product_variants.id = product_variant_has_properties.variant_id
left join properties
on properties.id = product_variant_has_properties.property_id and properties.property_key_id = 1
) q1
left join
(
select products.id, products.title, properties.value, properties.property_key_id
from products
left join product_has_properties
on products.id = product_has_properties.product_id
left join properties
on properties.id = product_has_properties.property_id and properties.property_key_id = 2
union
select product_variants.product_id, products.title, properties.value, properties.property_key_id
from product_variants
inner join products
on product_variants.product_id = products.id
left join product_variant_has_properties
on product_variants.id = product_variant_has_properties.variant_id
left join properties
on properties.id = product_variant_has_properties.property_id and properties.property_key_id = 2
) q2
on q1.id = q2.id
where q1.value is not null or q2.value is not null
) main
where ((color = 'red' or color = 'yellow') and type = 'fruit')
And here's a demo: http://sqlfiddle.com/#!9/d3ded/76
If you were to get more types of property, in addition to colour and type, the query would need to be modified - sorry but that's pretty much what you're stuck with, trying to pivot in mysql
I think that you make unnecessary complications for your data model, your code and your queries.
Those eventually will be a performance killer for your application.
Your best solution is to consider an easier approach.
Try to flatten your data structure so you will not have such dependencies.
I don't know what exactly product_variants mean so I can't tell exactly how to do the change.
But the main idea is to save the properties always for each variant.
When you have only 1 variant - define it as a variant too.
And I suggest you to make the properties table to reference the exact variant instead of having global numbering with referencing tables in the structure of:
+----+-----------------+-------------+-----------+
| id | property_key_id | variant_id| value |
+----+-----------------+-------------+-----------+
| 1 | 1 | 1 | Yellow |
| 2 | 1 | 1 | Green |
| 3 | 1 | 1 | Red |
| 4 | 2 | 1 | Fruit |
| 5 | 2 | 2 | Vegetable |
| 6 | 1 | 2 | Blue |
| 7 | 1 | 2 | Yellow |
+----+-----------------+-------------+-----------+
If this approach - you will have duplicate values, but all your queries will be simpler and you will have the freedom to save the values that you want for each specific product variant.
UPDATE
If you have no option to change the structure of the data, "LEFT OUTER JOIN" is your only hope.
Check the below query that selects the ones with color 'Yellow'
select p.* from products p
left outer join product_has_properties pp
on p.id=pp.product_id
left outer join product_variants v
on p.id=v.product_id
left outer join product_variant_has_properties vp
on v.id = vp.variant_id
where vp.property_id=1 or pp.property_id=1;
Considering products and not variants, you can simulate this (at least to some extent) with joins so that you
substitute each OR in your query with an equivalent condition in the WHERE clause. E.g. to have (color='red' OR color='yellow'),
SELECT product_id FROM product_has_properties
WHERE property_id IN (1, 3)
substitute each AND in your query with a self-join and a condition in the WHERE clause. This should yield rows that correspond to products that have the pair of properties in question. E.g.to have (color='red' AND type='vegetable'),
SELECT p1.product_id
FROM product_has_properties p1
INNER JOIN product_has_properties p2 ON (p1.product_id = p2.product_id)
WHERE p1.property_id = 3 AND p2.property_id = 5
Obviously this gets complicated as the number of conditions grows. To get ((color='red' OR color='yellow') AND type='fruit'), you would need to do
SELECT p1.product_id
FROM product_has_properties p1
INNER JOIN product_has_properties p2 ON (p1.product_id = p2.product_id)
WHERE (p1.property_id = 1 OR p1.property_id = 3) AND p2.property_id = 4
Assuming that some fruit could be both blue and red, to get pkey1='red' AND pkey1='blue' AND pkey2='fruit', you'd have to do
SELECT p1.product_id
FROM product_has_properties p1
INNER JOIN product_has_properties p2 ON (p1.product_id = p2.product_id)
INNER JOIN product_has_properties p3 ON (p1.product_id = p3.product_id)
WHERE p1.property_id = 3 AND p2.property_id = 6 AND p3.property_id = 4
There might be some case which isn't covered by this approach, though.
Short answer
I'm going to throw out a bit of a different answer to the ones you've been getting. While it is very possible to have a purely SQL answer to this, the question I would pose to you is: Why?
That answer will determine your next step.
If your answer is to try to learn the pure SQL way to do it, there are some great answers here which get you most if not all of the way there.
If your answer is to create scalable dynamic queries for an end application, then you may find your job eased by leaning on your programming language.
A little personal background
I had a requirement to pivot data with more tables. I was determined I'd try to do this the best possible way, and I spent a lot of time working out what was best for my application. Knowing full well this may not be the same experience you have, I will share my experience here in case it helps you.
I tried to create pure SQL solutions, which did work for specific use cases but required extensive tweaking for each additional use case. When I tried to scale the queries up I first attempted to create Stored Procedures. That was a nightmare and pretty early on in my development I realized it would be a headache to maintain.
I went on to use PHP and create my own query generating. While some of this code has morphed into something that is quite useful for me today, I learned that much of it was going to be challenging to maintain unless I created service libraries. At that point, I realized I was basically going to be creating an Object-relational Mapper (ORM). Unless my application was SO special and SO unique that no ORM on the market could come close to doing what I wanted, then I needed to take that opportunity to explore employing an ORM for my application. Despite my initial reservations which caused me to do everything BUT look at an ORM, I have started using one and it helped my development speed increase significantly.
Reaching your desired end result
Select all products with (color='red' AND type='vegetable'). This should return only Tomato.
Select all products with ((color='red' OR color='yellow') AND type='fruit') should return Apple and Banana
This is possible in an ORM. What you're describing is only loosely defined in your SQL but is in fact perfectly summarized in OOP. This is what it would look like in PHP, just as an example.
<?
Abtract class AbstractProductType {
public function __construct() {
}
}
class Color extends AbstractProductType {
}
class Yellow extends Color {
}
class Red extends Color {
}
class Type extends AbstractProductType {
}
class Vegetable extends Type {
}
class Fruit extends Type {
}
class Product {
public function setColor(Color $color) {
//
}
public function setType(Type $type) {
//
}
}
$product = new Product();
$product->setColor(new Red());
$product->setType(new Fruit());
$result = $product->find();
?>
The idea behind this is that you can make full use of SQL in object oriented programming.
A slightly lower-key version of this would be to create a class which generates SQL snippets. My personal experience was that that's a lot of work for a limited payback. If your project is going to remain relatively small, it may work out just fine. However, if you antiicpate that your project will grow, then an ORM may well be worth exploring.
Conclusion
Although I am not sure what language you will be utilizing to query and manipulate your data, there are great ORMs out there which should not be discounted. Despite their many cons (you can find a lot of debate about this all over the internet), I am a reluctant believer that, although certainly not ideal for all situations, they should be considered for some. If this is not one of those situations for you, be prepared to write lots of JOINs yourself. When referencing a table n times and requiring a reference back to the table, the only method I am aware of to add a reference is to create n JOINs.
I'll be very interested to see if there is a better way, of course!
Conditional Aggregation
You can use conditional aggregation in your having clause to see if a product has specific properties. For example, to query all products that have both the "type vegetable" and "color red" properties.
You have to group by both the product id and the product variant id in order to make sure that all the properties you're searching for exist on the same variant or the product itself.
select p.id, pv.id from products p
left join product_has_properties php on php.product_id = p.id
left join properties pr on pr.id = php.property_id
left join property_keys pk on pk.id = pr.property_key_id
left join product_variants pv on pv.product_id = p.id
left join product_variant_has_properties pvhp on pvhp.variant_id = pv.id
left join properties pr2 on pr2.id = pvhp.property_id
left join property_keys pk2 on pk2.id = pr2.property_id
group by p.id, pv.id
having (
count(case when pk.value = 'Color' and pr.value = 'Red' then 1 end) > 0
and count(case when pk.value = 'Type' and pr.value = 'Vegetable' then 1 end) > 0
) or (
count(case when pk2.value = 'Color' and pr2.value = 'Red' then 1 end) > 0
and count(case when pk2.value = 'Type' and pr2.value = 'Vegetable' then 1 end) > 0
)
What was the question? (I read through the post several times, and I'm still failing to see any actual question that is being asked.) A lot of the answers here seem to be answering the question "What SQL statement would return a result from these tables?" My answer doesn't provide an example or a "how to" guide to writing SQL. My answer addresses a fundamentally different question.
The difficulty that OP is experiencing writing SQL against the tables shown in the "question" is due to (what I refer to as) the "impedance mismatch" between the "Relational" model and the "Entity-Attribute-Value" (EAV) model.
SQL is designed to work with the "Relational" model. Each instance of an entity is represented as a tuple, stored a row in table. The attributes of an entity are stored as values in columns of the entity row.
The EAV model differs significantly from the Relational model. It moves attribute values off of the entity row, and moves them into multiple, separate rows in other tables. And that makes writing queries more complicated, if the queries are attempting to emulate queries against a "Relational" model by transforming the data from the "EAV" representation back into a "Relational" representation.
There's a couple of approaches to writing SQL queries against the EAV model that emulate the results returned from a Relational model (as demonstrated by the example SQL provided in other answers to this "question".
One approach is to use subqueries in the SELECT list to return values of attributes as columns in the entity row.
Another approach is to perform joins between the row in the entity table to the rows in the attribute table(s), and use a GROUP BY to collapse the rows, and in the SELECT list, use conditional expressions "pick out" the value to be returned for a column.
There's lots of examples of both of those approaches. And neither is really better than the other, the suitability of each approach really depends on the particular use case.
While it is possible to write SQL queries against the EAV-style tables shown, those queries are an order of magnitude more complicated than equivalent queries against data stored in a "relational" model.
A result returned by a trivial query in the relational model, e.g.
SELECT p.id
FROM product p
WHERE p.color = 'red'
To return that same set from data in the EAV model requires a much more complex SQL query, involving joins of several tables and/or subqueries.
And once we move beyond the trivial query, to a query where we want to return attributes from multiple related entities... as a simple example, return information about orders in the past 30 days for products that were 'red'
SELECT c.customer_name
, c.address
, o.order_date
, p.product_name
, l.qty
FROM customer c
JOIN order o ON ...
JOIN line_item l ON ...
JOIN product p ON ...
WHERE p.color = 'red'
AND o.order_date >= DATE(NOW()) + INTERVAL 30
getting that same result, using SQL, from the EAV model is way more convoluted and confusing, and can be an excruciating exercise in frustration.
Certainly, it's possible to write the SQL. And once we do manage to get SQL statements that work to return a "correct" resultset, when the number of rows in the tables scale up beyond the trivial demonstration, up to the kind of volumes we expect databases to handle... the performance of those queries is horrendous (as compared to queries returning the same results from a traditional Relational model).
(And we've not even touched on the additional complexity for just adding and updating the attributes of entities, enforcing referential integrity between entities, etc.)
But why would we want to do that? Why do we need (or want) to write SQL statements against the EAV model tables that emulate the results returned from queries against Relational model tables?.
Bottom line, if we are going to use an EAV model, we are much better off not attempting to use a single SQL statement to return results like we'd get back from a query of a "Relational" model.
The problem of retrieving information from the EAV model is much more suited to a programming language that is object-oriented, and provides a framework. Something that is entirely lacing in SQL.
I'm passing through the following situation and have not found a good solution to this problem. I am going through a optimization of a API so am looking for fastest possible solution.
The following description is not exactly what I am doing, but I think it represents the problem well.
Let's say I have a table of products:
+----+----------+
| id | name |
+----+----------+
| 1 | product1 |
| 2 | product2 |
+----+----------+
And I have a table of attachments to each product, separate by language:
+----+----------+------------+-----------------------+
| id | language | product_id | attachment_url |
+----+----------+------------+-----------------------+
| 1 | bb | 1 | image1_bb.jpg |
| 1 | en | 1 | image1_en.jpg |
| 1 | pt | 1 | image1_pt.jpg |
| 2 | bb | 1 | image2_bb.jpg |
| 2 | pt | 1 | image2_pt.jpg |
+----+----------+------------+-----------------------+
What I intend to do is to get the correct attachment according to the language selected on the request. As you can see above, I can have several attachments to each product. We use Babel (bb) as a generic language, so every time I don't have a attachment to the right language, I should get the babel version. Is also important to consider that the Primary Key of the attachments table is a composite of id + language.
So, supposing I try to get all the data in pt, my first option to create a SQL query was:
SELECT p.id, p.name,
GROUP_CONCAT( '{',a.id,',',a.attachment_url, '}' ) as attachments_list
FROM products p
LEFT JOIN attachments a
ON (a.product_id=p.id AND (a.language='pt' OR a.language='bb'))
The problem is that, with this query I always get the bb data and I only want to get it when there is no attachment on the right language.
I already tried to do a subquery changing attachments for:
(SELECT * FROM attachments GROUP BY id ORDER BY id ASC, language DESC)
but it doubles the time of the request.
I also tried using DISTINCT inside the GROUP_CONCAT, but it only works if the whole result of each row is equal, so it does not work for me.
Does anyone knows any other solution that I can apply directly into the query?
EDIT:
Combining the answers of #Vulcronos and #Barmar made the final solution at least 2x faster than the one I first suggested.
Just to add some context, for anybody else who is looking for it. I am using Phalcon. Because of it, I had a lot of trouble putting the pieces together, as Phalcon PHQL does not support subqueries, nor a lot of the other stuff I had to use.
For my scenario, where I had to deliver approximatelly 1.2MB of JSON content, with more than 2100 objects, using custom queries made the total request time up to 3x faster than Phalcon native relations management methods (hasMany(), hasManyToMany(), etc.) and 10x faster than my original solution (which used a lot the find() method).
Try doing two joins instead of one:
SELECT p.id, p.name,
GROUP_CONCAT( '{',COALESCE(a.id, b.id),',',COALESCE(a.attachment_url, b.attachment_url), '}' ) as attachments_list
FROM products p
LEFT JOIN attachments a
ON (a.product_id=p.id AND a.language='pt')
LEFT JOIN attachments b
ON (a.product_id=p.id AND a.language='bb')
and then using COALESCE to return b instead of a if a doesn't exist. You can also do it with a subselect if the above doesn't work.
OR conditions tend to make queries slow, because it's hard to optimize them with indexes. Try joining separately using the two different languages.
SELECT p.id, p.name,
IFNULL(apt.attachment_url, abb.attachment_url) AS attachment_url
FROM products AS p
JOIN attachments AS abb ON abb.product_id = p.id
LEFT JOIN attachments AS apt ON alang.product_id = p.id AND apt.language = 'pt'
WHERE abb.language = 'bb'
This assumes that all products have a bb attachment, while pt is optional.
I left out the join of Product, because it's not relevant for this problem. It's only needed to include the product name in the resultset.
SELECT a.product_id, a.id, a.attachment_url FROM attachments a
WHERE a.language = ?
OR (a.language = 'bb'
AND NOT EXISTS
(SELECT * FROM attachments
WHERE language = ?
AND id = a.id
AND product_id = a.product_id));
Notes: problems like this usually have many possible solutions. This is not necessarily the most efficient one.
Let's say I have the following scenario.
A database of LocalLibrary with two tables Books and Readers
| BookID| Title | Author |
-----------------------------
| 1 | "Title1" | "John" |
| 2 | "Title2" | "Adam" |
| 3 | "Title3" | "Adil" |
------------------------------
And the readers table looks like this.
| UserID| Name |
-----------------
| 1 | xy L
| 2 | yz |
| 3 | xz |
----------------
Now, lets say that user can create a list of books that they read (a bookshelf, that strictly contains books from above authors only i.e authors in our Db). So, what is the best way to represent that bookshelf in Database.
My initial thought was a comma separated list of BookIDin Readers table. But it clearly sounds awkward for a relational Db and I'll also have to split it every time I display the list of users' books. Also, when a user adds a new book to shelf, there is no way of checking if it already exists in their shelves except to split the comma-separated list and and compare the IDs of two. Deleting is also not easy.
So, in one line, the question is how does one appropriately models situations like these.
I have not done anything beyond simple SELECTs and INSERTs in MySQL. It would be much helpful if you could describe in simpler terms and provide links for further reading.
Please comment If u need some more explanation.
Absolutely forget the idea about a comma separated list of books to add to the Readers table. It will be unsearchable and very clumsy. You need a third table that join the Books table and the Readers table. Each record in this table represent a reader reading a book.
Table ReaderList
--------------------
UserID | BookID |
--------------------
You get a list of books read by a particular user with
select l.UserID, r.Name, l.BookID, b.Title, b.Author
from ReaderList l left join Books b on l.BookID = b.BookID
left join Readers r on l.UserID = r.UserID
where l.UserID = 1
As you can see this pattern requires the use of the keyword JOIN that bring togheter data from two or more table. You can read more about JOIN in this article
If you want, you could enhance this model adding another field to the ReaderList like the ReadingDate
I'm working on a restaurant CMS app. I have a many-to-many relationship between 2 tables, menu_sections and menu_items. The relationship is maintained with a table in between called menu_relationships.
As an example let's say the menu section called Snacks (menu_section_id = 1) contains a menu item called Pretzels (menu_item_id = 1) and the menu section called Desserts (menu_section_id = 2) contains a menu item called Ice Cream (menu_item_id = 2), but Ice Cream is also contained within another menu section called Kids Food (menu_section_id = 3). So there would be 3 rows in the menu_relationships table to map out these 3 relationships. The relationship table would look like this:
---------------------------------------
| menu_section_id | menu_item_id |
|=====================================|
| 1 | 1 |
|-------------------------------------|
| 2 | 2 |
|-------------------------------------|
| 3 | 2 |
---------------------------------------
So far so good.
I want to generate a result set that will return the names of all menu items except for menu items with a given menu_section_id. So to return the menu item names, I have a join on the menu_items table. Here's the SQL:
SELECT menu_section_id, menu_items.menu_item_id, menu_item_name
FROM menu_relationships
JOIN menu_items
ON menu_items.menu_item_id = menu_relationships.menu_item_id
WHERE menu_section_id != 2
The result set which will give me a row for each relationship that doesn't contain a given menu_section_id. With the example data I would be getting 2 rows back from the relationship table:
-----------------------------------------------------------
| menu_section_id | menu_item_id | menu_item_name |
|======================================|==================|
| 1 | 1 | Pretzels |
|--------------------------------------|------------------|
| 3 | 2 | Ice Cream |
-----------------------------------------------------------
But what I want is to exclude the menu item altogether from the result set, if it has ANY relationship to the specified menu_section_id. In other words, in the case of this example , I only want to return rows for menu items that have no relationship mappings at all to a menu_section_id of 2, I only want to return the Pretzels row.
I've tried various things with GROUP BY and HAVING using the bit_xor() aggregate function, but so far no luck at all in getting what I want.
I probably could have taken less time to explain that but I wanted it to be a clear as I can make it. I hope it is. Can anyone help?
This is a wonderful case for the use of LEFT OUTER JOIN because it includes all rows from your left-hand table and matches where it can, returning NULL for any non-match.
Building on Mark Breyer's sample query from above, see this example:
SELECT R.menu_section_id, I.menu_item_id, I.menu_item_name
FROM menu_items AS I
LEFT OUTER JOIN menu_relationships R on (R.menu_item_id=I.menu_item_id) AND (R.menu_section_id = 2)
The mysql optimizer may actually rewrite this as a subquery - i'm not an optimization expert by any means - I'd take a look at the way your indexes are built and see if this type of join makes sense for your schema. I'd also test to see if it's actually faster because it's actually less semantic.
There are many ways to do this. Here is one example using WHERE ... NOT IN (...):
SELECT
R.menu_section_id,
I.menu_item_id,
I.menu_item_name
FROM menu_items AS I
JOIN menu_relationships AS R
ON R.menu_item_id = I.menu_item_id
WHERE I.menu_item_id NOT IN
(
SELECT menu_item_id
FROM menu_relationships
WHERE menu_section_id = 2
)
I would use a subquery for this, getting me every menu_item_id which has the menu_section_id 2 and then using NOT IN. Here you go:
SELECT menu_section_id, menu_items.menu_item_id, menu_item_name
FROM menu_relationships
JOIN menu_items
ON menu_items.menu_item_id = menu_relationships.menu_item_id
WHERE menu_relationships.menu_item_id NOT IN (
SELECT menu_item_id
FROM menu_relationships
WHERE menu_section_id = 2
);
I was going to suggest a subquery a well, except that I wanted to mention that subqueries can dramatically affect performance on your site. You may want to consider options for caching to avoid serious load time hangups due to things like this.
In most cases you'll be ok, but if you're only showing us part of the issue and just not mentioning the irrelevant details then you could very well be building a site where you run 100 of these queries on a page, for example, because someone mentioned it here without mentioning the compounded overhead things like this can result in...
Like I said though, you'll probably be fine. Just don't do a subquery within a subquery unless you want to restart your server.
How are URLs (fragments) stored in a relational database?
In the following URL fragment:
~/house/room/table
it lists all the information on a table, and perhaps some information about the table.
This fragment:
~/house
outputs: Street 13 and Room, Garage, Garden
~/house/room
outputs: My room and Chair, Table, Window
What does the Database schema looks like? What if I rename house to flat?
Possible solution
I was thinking that I could create a hash for the URL and store it along with parentID and information. If I rename some upper-level segment I would then need to update all the rows which contain the given segment.
Then I thought would store each segment along with information and its level:
SELECT FROM items WHERE key=house AND level=1 AND key=room AND level=2
How do I solve this problem if the URL can be arbitrarily deep?
check The Adjacency List Model and The Nested Set Model described in Joe Celko's Trees and Hierarchies in SQL for Smarties
you should find plenty information to this topic. one article is here
Update
The Nested Set Model is very good if you are looking for a task like 'Retrieving a Single Path'. What you have is 'Find the Immediate Subordinates of a Node'. Here the Adjacency List Model is better.
| id | p_id | name |
| 1 | null | root |
| 2 | 1 | nd1.1 |
| 3 | 2 | nd1.2 |
| 4 | 1 | nd2.1 |
SQL to get a row with name of a fragment and it's direct sub items.
SELECT
p.name,
GROUP_CONCAT(
c.name
SEPARATOR '/'
) AS subList
FROM _table p
INNER JOIN _table c
ON p.id = c.p_id
WHERE p.name = 'root'
P.S. prefer WHERE p.id = 1. Id is unique where as name can be ambiguous.
see MySQL GROUP CONCAT function for more syntax details.