1

I have a select query to build and I can't get it work propery. I was hopeing for a few sudggestions from more advanced MySQL developers. So my tables are:

 CREATE TABLE IF NOT EXISTS `gv` ( 
`id` int(11) NOT NULL AUTO_INCREMENT,
`option_id` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

INSERT INTO `gv` (`id`, `option_id`, `group_id`) VALUES
(1, 1, 1),
(2, 2, 2),
(3, 3, 2),
(4, 4, 3),
(5, 5, 4),
(6, 6, 4); 

CREATE TABLE IF NOT EXISTS `igv` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `item_id` int(11) NOT NULL,
 `gv_id` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;

INSERT INTO `igv` (`id`, `item_id`, `gv_id`) VALUES
(1, 1, 1),
(2, 1, 3),
(3, 2, 1),
(4, 2, 2),
(6, 3, 5),
(7, 4, 2),
(8, 2, 6); 

CREATE TABLE IF NOT EXISTS `items` (
  `item_id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;

INSERT INTO `items` (`item_id`) VALUES
(1),
(2),
(3),
(4),
(5); 

Now I will explain what these tables are doing:

  1. gv comes from group_values. A group is like a property for an item. Ex: Color.

    • each row holds values for a group. Ex: Red ( option_id ) for group Color ( group_id ).
  2. igv comes from item group values. An item is the main entity in my project, like a product.

    • each row holds relations between items and group_values. Ex: An item with Red Color.
    • it's linked to the gv table by gv_id ( igv.gv_id = gv.id ).
  3. items comes from items :) I have simplified for this example to only one column, item_id.

What I need to query:

Well, in my app I have a selected item ( my_item ) ( with 0 or more group values attached ). I need all the items that have exactly the same group_values defined, or no values specified for my_item groups. In php I can extract all the attached groups as an array with ids, also all the group_values. But I found no way to select what I need to from this database. I will appreciate very much any input provided into this massive time-consumming select.

Thanks!

A short sketched-up example. On the left we have the item that is matching all the right items ( separated with comma ). enter image description here

  • Can you give a worked example? – Neil Sep 10 '12 at 11:38
  • `SELECT i.item_id, igv.id, gv.option_id, gv.group_id FROM items AS i LEFT JOIN igv ON i.item_id = igv.item_id LEFT JOIN gv ON igv.gv_id = gv.id AND gv.group_id IN (1, 2) WHERE IF(gv.group_id IS NOT NULL, gv.option_id IN (1,2), 1) GROUP BY i.item_id` but for this I need to process all provided items first and extract the groups attached and their values. Also this is not fully tested into my app, only on this test example. – Serbu Florin-Adrian Sep 10 '12 at 12:32
  • Sorry, but that doesn't really help me. I reread your question and I assume you want to match item 2 with item 4 because they both have an option_id of 2 for the group_id of 2, and you don't want to match item 2 with item 3 because they have a different option_id for the group_id of 4, but I can't tell whether you want to match item 2 with either item 1 or item 5 or both. – Neil Sep 11 '12 at 11:37
  • I do not want to match item 2 with item 1 because we have the same situation as with item 3, they have different option for group 2. Also if no option was attached for any of the CURRENT_ITEM's groups then it matches. So item 5 will be matched with any other item. I will post a short sketch. – Serbu Florin-Adrian Sep 11 '12 at 13:28
  • This does absolutely no sense to me. Why does item_id 3 match, when you search for item_id 1? option_id and group_id, 5 and 4, are used by no other item. I'm afraid I simply don't understand what you're after. – fancyPants Sep 11 '12 at 14:49
  • Item 3 matches item 1 because it has nothing in common with it. Item 3 has the group 4 with option 5. Item 1 has group 1 and 2 with option 1 and 3. So if the items are sharing the same groups then those groups must have exactly the same options to match, or have no groups in common; in this last case the options are not important. – Serbu Florin-Adrian Sep 11 '12 at 15:13
  • Ah, now I get it (I think :)). Unfortunately I will have no time today to help you. Tomorrow again... :) – fancyPants Sep 11 '12 at 15:14
  • No problem, I appreciate your help a lot! It's a website were the user can insert his own products, some users may fill all the properities ( defined for a certain product category ) and others may skip these. I just want to show the users the similar products with his, even not all properties are filled. The idea is that the found product may have the same properties but aren't specified. Hope you got it. Thanks a lot! – Serbu Florin-Adrian Sep 11 '12 at 15:28

2 Answers2

2

OK, so to start with we need to find all items with a group matching another item:

SELECT igv.item_id, sigv.item_id FROM igv
  INNER JOIN gv ON igv.gv_id = gv.id
  INNER JOIN gv sgv ON gv.group_id = sgv.group_id
  INNER JOIN igv sigv ON sigv.gv_id = sgv.id
  WHERE igv.item_id = items.item_id
    AND sigv.item_id = similar.item_id

We then are only interested when the option does not match:

SELECT igv.item_id, sigv.item_id dissimilar FROM igv
  INNER JOIN gv ON igv.gv_id = gv.id
  INNER JOIN gv sgv ON gv.group_id = sgv.group_id
  INNER JOIN igv sigv ON sigv.gv_id = sgv.id
  WHERE igv.item_id = items.item_id
    AND sigv.item_id = similar.item_id
    AND gv.option_id != sgv.option_id

Then we are only interested in the items that are not selected above. This can be done using either NOT EXISTS or a LEFT JOIN. Here's how to do it with NOT EXISTS:

SELECT items.item_id, similar.item_id similar_id
  FROM items similar
  INNER JOIN items
  WHERE items.item_id != similar.item_id
    AND NOT EXISTS (
    SELECT igv.item_id, sigv.item_id dissimilar FROM igv
      INNER JOIN gv ON igv.gv_id = gv.id
      INNER JOIN gv sgv ON gv.group_id = sgv.group_id
      INNER JOIN igv sigv ON sigv.gv_id = sgv.id
      WHERE sigv.item_id = similar.item_id
        AND igv.item_id = items.item_id
        AND gv.option_id != sgv.option_id
    )

Result:

ITEM_ID SIMILAR_ID
1       3
1       5
2       4
2       5
3       1
3       4
3       5
4       2
4       3
4       5
5       1
5       2
5       3
5       4
Neil
  • 54,642
  • 8
  • 60
  • 72
  • Thank you a lot Neil, this solved my issue. But it seems that my problem is extending to a new undesider level, I forgot to mention but I also can have multiple options attached to the same group for an item. But is my fault that I have not documented it on the first time. Anyway...very good answers, I appreciate it very much! – Serbu Florin-Adrian Sep 12 '12 at 15:38
  • Ah, well in that case it would probably be possible to write a query which identified an item as similar if all the matching groups had a matching option, although with your example I think I would only be able to get it to match items 2 and 4. – Neil Sep 12 '12 at 23:53
1

Update:

Based on the logic from your comment, if I understood it correctly, and your test data, the following table shows which result you want for which item_id you provide for the query.

id_provided | result
--------------------
1             2 & 5
2             1 & 4 & 5
3             5
4             2 & 5
5             -

If this is correct, the following query gives the desired result:

SELECT 
COALESCE(i2.item_id, i.item_id)
FROM
items i
LEFT JOIN igv ON igv.item_id = i.item_id
LEFT JOIN gv ON igv.gv_id = gv.id
LEFT JOIN gv gv2 ON gv.group_id = gv2.group_id AND gv.option_id = gv2.option_id
LEFT JOIN igv igv2 ON gv2.id = igv2.gv_id
LEFT JOIN items i2 ON igv2.item_id = i2.item_id
WHERE i.item_id = $yourItemId
AND i2.item_id != i.item_id OR i2.item_id IS NULL



I'm not sure if I understood what you want and I'm not sure if the following query gets what you want, cause you didn't say what the expected output would be and your test data is not sufficient. Anyway, here's my shot:

SELECT DISTINCT
i.item_id
FROM
items i
LEFT JOIN igv ON igv.item_id = i.item_id
LEFT JOIN gv ON igv.gv_id = gv.id
GROUP BY i.item_id
HAVING 
GROUP_CONCAT(CONCAT(COALESCE(gv.option_id, 0), COALESCE(gv.group_id, 0)) ORDER BY gv.option_id, gv.group_id SEPARATOR ':')
=
(
SELECT
GROUP_CONCAT(CONCAT(COALESCE(sq_gv.option_id, 0), COALESCE(sq_gv.group_id, 0)) ORDER BY sq_gv.option_id, sq_gv.group_id SEPARATOR ':')
FROM
items sq_i
LEFT JOIN igv sq_igv ON sq_igv.item_id = sq_i.item_id
LEFT JOIN gv sq_gv ON sq_igv.gv_id = sq_gv.id
WHERE sq_i.item_id = $yourItemId /*insert the item_id here*/
GROUP BY sq_i.item_id
)

fancyPants
  • 50,732
  • 33
  • 89
  • 96
  • Thank you for your input. On my test example, if I replace $yourItemId with any item id from 1 to 5, it ouputs only the $yourItemId provided. So I need to provide it an id of an item, and the query to select all the rest of the items witch: - if have the same group attached, then the item must also have the same option . - none of the groups from the provided items are attached to this item. So if my provided item has the Red Color, then the item selected must have: - Red color if the Color group is attached by having a row on igv - whatever color if no color group is attached – Serbu Florin-Adrian Sep 10 '12 at 12:00
  • I appreciate very much your interest, also now I think now I explained the desired result as others can understand it properly. So please check my updated question- the image attached. – Serbu Florin-Adrian Sep 11 '12 at 13:33