I'm trying to solve this for quite a moment now and I don't seem to be able to do it by myself.
I'd like to store OPTIONS linked to IDs, and when needed, get the results that match all wanted OPTIONS. I thought about doing it this way:
ID | OPTION
aaa | 1
aaa | 2
aaa | 3
bbb | 1
bbb | 2
ccc | 1
ccc | 2
ccc | 5
ccc | 7
Where ID and OPTION are FOREIGN KEYS.
The final request would look like
options_wanted(1,2,5,7)
SELECT * FROM main_table
WHERE crit1=...
AND crit2=...
AND (ALL OPTIONS ARE FOUND IN options TABLE)
Can I make it work or should I change the implementation?
What do you suggest me?
EDIT:
Thanks to https://stackoverflow.com/a/7505147/2512108, I almost found what I want.
His query works but the last column only gives the 1st option alone. Is there a way to make it return ALL the options AVAILABLE (not only the wanted ones) ?
Answer:
select item_id, group_concat(option_id order by option_id asc) options
from options
where option_id in (1, 2, 3)
group by item_id
having count(option_id) = 3
Fiddle: http://sqlfiddle.com/#!9/04f69/3
I'll leave the joining to your other table up to you, as well as the other criteria since the table schema isn't really explicitly mentioned.
EDIT
No I won't, I hate half an answer.
select item_id, group_concat(option_id order by option_id asc) options
from main_table m
inner join options o
on m.id = o.item_id
where option_id in (1, 2, 3)
AND crit1 = 2
AND crit2 = 3
group by item_id
having count(option_id) = 3
Updated fiddle: http://sqlfiddle.com/#!9/45bee/1
And if you want it to return ALL options available to an item that has at minimum all of the REQUIRED options, your query is this:
select o.item_id, group_concat(o.option_id) options
from options o
inner join (
select item_id
from main_table m
inner join options o
on m.id = o.item_id
where option_id in (1, 2, 3)
AND crit1 = 2
AND crit2 = 3
group by item_id
having count(option_id) = 3
With a final fiddle here: http://sqlfiddle.com/#!9/d60b3/1
As you state:
'Criterias and options are different'
and
'All options are found in options table'
I assume you have two tables; Namely main_table and options_table
SELECT id, option
FROM main_table
WHERE crit1='value1'
and crit2 = 'value2'
and id IN(
SELECT id
FROM main_table
WHERE option IN (select options from options_table)
GROUP BY id
HAVING COUNT(*) = (select count(options) from options_table)
Find similar question over here:
SQL selecting rows where one column's value is common across another criteria column
MADE IT ! Finally, thanks to you
SELECT main_table.*, (SELECT GROUP_CONCAT(feature_id) FROM featuring WHERE main_id = main_table.id) options FROM main_table
RIGHT JOIN featuring
ON main_table.id = featuring.main_id
WHERE featuring.feature_id IN (WANTEDOPTIONS)
GROUP BY main_table.id
HAVING COUNT(DISTINCT featuring.feature_id) = NUMBEROFWANTEDOPTIONS
It gives me all the informations on the main and ALL the AVAILABLE options in featuring.
Thanks again.
Related
I'm joining 2 tables, for example
PRODUCTS (PRODUCT_ID, NAME)
and
PRICE_LEVELS (PRODUCT_ID, PRICE, PRICE_LVL_NAME )
(ofc. it's simplified, there is several joins ).
in the PRICE_LEVELS table, I have some possibilities of price level names, let's say "DEFAULT" and LEVEL1, so we ended up with something like:
PRODUCT_ID | PRICE | PRICE_LVL_NAME
1 | 100 | _DEFAULT_
1 | 50 | LEVEL1
2 | 130 | _DEFAULT_
Both tables are joined in the view.
What I need is to get price, but only once - I mean if there is LEVEL1 defined, pick that one, otherwise pick DEFAULT.
Meantime, I have used GROUP BY and thing seems to work, but I have no idea why (ofc. I've used a lot of test data and it's simply always works, but not sure, how it's reliable).
Let's say our view (combining both tables) has name V_PRODUCTS, so I'm running query:
SELECT *
FROM `V_PRODUCTS`
WHERE (PRICE_LVL_NAME = '_DEFAULT_' OR PRICE_LVL_NAME = 'LEVEL1')
GROUP BY `PRODUCT_ID `;
So the questions are:
Why the query above works ? GROUP BY is always choosing LEVEL1, if is available and DEFAULT if not. It's exactly what I need, but need to understand why it's working this way.
Is there any way how to do this more explicit in the SQL ?
UPDATE: there is unlimited number of possible levels
Q1: It's not reliable, it's probably based on internal storage and might change at any time.
Q2: Join the table twice using an Outer Join and return the best match using COALESCE:
SELECT ..., COALESCE(pl1.PRICE, pl2.PRICE)
FROM `V_PRODUCTS` as p
LEFT JOIN PRICE_LEVELS as pl1
ON p.PRODUCT_ID = pl1.PRODUCT_ID
and PRICE_LVL_NAME = 'LEVEL1')
LEFT JOIN PRICE_LEVELS as ply
ON p.PRODUCT_ID = pl2.PRODUCT_ID
and PRICE_LVL_NAME = '_DEFAULT_'
I don't have a MySQL to play with right now but you should only have to replace the IS NULL part of this MSSQL to make it work I think.
If you don't need AlphaNum in the levels or can add a level field that is INT where 0 is default and then 1 and so on, you can join the levels to itself to get the max available level to join to the product table.
SELECT PRODUCTS.*, PriceLvl.*
FROM [PRODUCTS] LEFT JOIN
(SELECT p1.*
FROM PRICE_LEVELS p1
LEFT JOIN PRICE_LEVELS p2 ON
(p1.PRODUCT_ID = p2.PRODUCT_ID
AND p2.PRICE_LVL > p1.PRICE_LVL)
WHERE p2.PRICE_LVL IS NULL) PriceLvl
ON PRODUCTS.PRODUCT_ID = PriceLvl.PRODUCT_ID
That takes
PRODUCT_ID NAME
1 Tshirts
2 Pants
and
PRODUCT_ID PRICE PRICE_LVL
1 10.00 0
1 15.00 1
2 40.00 0
1 20.00 2
and gives
PRODUCT_ID NAME PRODUCT_ID PRICE PRICE_LVL
1 Tshirts 1 20.00 2
2 Pants 2 40.00 0
I'd assign a numeric value for each level with zero for default, and store it same table.
select t1.price from price_levels t1
where t1.product_id = #X and price_level = (select max(price_level)
from price_levels t2 where t1.product_id = t2.product_id)
WHERE price_level IN ('_DEFAULT_', 'mylevel')
ORDER BY
price_level = '_DEFAULT_'
LIMIT 1
The WHERE will grab 1 or 2 rows.
price_level = '_DEFAULT_' will be 1 for default, otherwise 0. 0 sorts before 1. So, if there is a 'mylevel', it will come first.
LIMIT 1 says to pick the first (or only) row.
UPDATED
id | id_list
1 | 2,3,5,7
2 | 1,4,5,6
3 | 1,4,6,7
4 | 2,3,5,8
5 | 1,2,4,8
6 | 2,3,7,8
7 | 1,3,6,9
8 | 4,5,6,9
9 | 7,8
let's say I'm up to the content of id=1
I wanted to select all the rows where id is in id_list of id=1 PLUS the row where id=1
so the result would be
rows with id = 1,2,3,5,7
How to do this query guys?
You can also use a self join
Using IN()
select * from atable a
join atable b on (a.id = b.id )
where 1 IN (a.id_list) or b.id =1
Fiddle with IN()
Using FIND_IN_SET()
select * from atable a
join atable b on (a.id = b.id )
where FIND_IN_SET('1', a.id_list) or b.id =1
Fiddle with FIND_IN_SET()
Using UNION ALL
select * from atable
where id =1
UNION ALL
select * from atable
where 1 IN (id_list)
Fiddle with UNION ALL
Your database design is broken; id_list should be represented as a join table instead of as a string. However, to solve your immediate problem:
select * from table where id=1
or id_list like '1%'
or id_list like '%,1,%'
or id_list like '%,1'
Adjust as needed for PreparedStatement. You have to provide all three cases because if you just did
or id_list like '%1%'
and id_list contained the value 11, you'd get an incorrect match
Try this (see SQL-Fiddle):
SELECT * FROM tbl
WHERE id = 1
OR INSTR((SELECT id_list FROM tbl WHERE id = '1'), id) > 0;
Tested with MySQL 5.5.30
try this one
select *
from tbl
where id=1
or id_list like '%1%'
This appears to call for a union of two sets. The one set would be the single row whose id matches the specified value:
SELECT
id
FROM
atable
WHERE
id = #id
The other set would be the result of this self-join:
SELECT
item.id
FROM
atable AS item
INNER JOIN
atable AS list
ON
FIND_IN_SET(item.id, list.id_list)
WHERE
list.id = #id
That is, the row with the specified id is matched against every row in the table on the condition that the other row's id is found in the specified row's id_list.
You can try the complete query at SQL Fiddle.
Please note that lists aren't a very good design feature. In your situation, it might be better to use a many-to-many table as suggested by #Othman. Only I would probably use a slightly different query to get the desired output, because his doesn't include the specified row itself:
SELECT
id
FROM
manytomany
WHERE
id = #id
UNION
SELECT
linked_id
FROM
manytomany
WHERE
id = #id
;
While the entries in manytomany are assumed to be unique, the query uses the UNION DISTINCT operator because of the potential duplicates returned by the first subquery, although it is possible to move the application of DISTINCT to the first subquery only like this:
SELECT DISTINCT
id
FROM
manytomany
WHERE
id = #id
UNION ALL
SELECT
linked_id
FROM
manytomany
WHERE
id = #id
;
That first subquery could actually be rewritten simply as SELECT #id AS id, but the rewrite would only make sense if the passed value was guaranteed to be valid, i.e. that it would definitely be found in manytomany.id.
Here's a demo for the other approach too (all three variations, including the SELECT #id AS id one).
I don't have an answer for your question but I encourage you to redesign your table like this I think this called many to many relation
id | friend
1 | 2
1 | 3
1 | 5
1 | 7
2 | 1
2 | 4
2 | 5
2 | 6
3 | 1
3 | 4
3 | 6
3 | 7
And then your query will be like this
SELECT DISTINCT(friend) FROM `new` WHERE id = 1
I am assuming you are using php..
My suggestion is to grab the id_list for id 1.
Explode that id_list on the comma, and then do another mysql query to grab the remaining results for 5|3|6|8
ex) $idarray = explode(",", $result);
select * from your_table where id in ('5','3','6','8')
OPTION 2:
SELECT * FROM your_table
WHERE id = '1'
OR id IN ('\''+(SELECT REPLACE(id_list,',','\',\'') FROM your_table WHERE id = '1')+'\'')
EDIT: Oops, sorry, that should be an OR instead.
The issue:
I want to select the products which have the option_value_id of both 1 and 3. But, as you can see, it will also show the products which have only have 1 of the option_value_ids.
I tried adding AND instead of IN but that will obviously show no results.
The answer might be simple, but I just can't seem to figure it out at the moment.
Could someone help me out? Even a small hint can be appreciated.
This is called Relation Division, and here is one way to do so:
SELECT *
FROM TABLEName
WHERE Product_ID IN(SELECT Product_ID
FROM Tablename
WHERE option_value_id IN(1, 3)
GROUP BY Product_ID
HAVING COUNT(option_value_id) = 2);
SQL Fiddle Demo
This will give you:
| ID | PRODUCT_ID | OPTION_VALUE_ID |
-------------------------------------
| 1 | 1 | 1 |
| 3 | 1 | 3 |
| 13 | 2 | 3 |
| 14 | 2 | 1 |
This is example of looking at things as a set. I think the best approach is to use SQL's aggregation, particularly the having clause. In MySQL syntax, this looks like:
select pa.product_id
from Product_Attributes pa
group by pa.product_id
having max(pa.option_value_id = 1) = 1 and
max(pa.option_value_id = 3) = 1
Try this:
SELECT *
FROM products_attributes
WHERE option_value_id IN (1, 3)
GROUP BY product_id
HAVING COUNT(*) = 2
This is a common problem, called Relational Division, there is even a tag in SO for it: sql-match-all
Usually, there is a unique constraint on the (product_id, option_value_id), so one solution is to use 2 joins (N joins if you want to check for N attributes):
SELECT p.* -- whatever columns you need
FROM product AS p -- from the `product` table
JOIN products_attributes AS pa1
ON pa1.option_value_id = 1
AND pa1.product_id = p.product_id
JOIN products_attributes AS pa2
ON pa2.option_value_id = 3
AND pa2.product_id = p.product_id ;
There is a similar question, with more than 10 different ways to achieve the same result (and benchmarks for Postgres): How to filter SQL results in a has-many-through relation
I'm listing product properties in a MySQL table where each row contains a product ID prod and a property ID prop. If a product has three properties, this results in three rows for that product. Example table:
prod | prop
-----+-----
1 | 1
2 | 1
2 | 2
2 | 3
3 | 2
3 | 4
How can I find which products have both properties #1 and #2 (product #2)?
The only way that I can think of is one select and inner join per property, but I think that would be very inefficient. It's a search function for a website and has to work for 10k lines in the table and 10 requested properties.
SELECT prod
FROM tbl
WHERE prop IN (1, 2)
GROUP BY prod
HAVING COUNT(*) = 2
And if there will be always 2 properties to find - then INNER JOIN would be a bit more efficient:
SELECT t1.p
FROM tbl t1
INNER JOIN tbl.t2 ON t2.prod = t1.prod
AND t2.prop = 2
WHERE t1.prop = 1
The recommended index for this query to be efficient as much as possible is a compound one (prop, prod)
I have a join table named languages_services that basically joins the table services and languages.
I need to find a service that is able to serve both ENGLISH (language_id=1) and ESPANOL (language_id=2).
table languages_services
------------------------
service_id | language_id
------------------------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 3
With the data provided above, I want to test for language_id=1 AND language_id=2 where the result would look like this
QUERY RESULT
------------
service_id
------------
1
Obviously it doesn't return the one with service_id=2 because it doesn't service Espanol.
Any tips on this is greatly appreciated!
SELECT
service_id
FROM
language_services
WHERE
language_id = 1
OR language_id = 2
GROUP BY
service_id
HAVING
COUNT(*) = 2
Or...
WHERE
lanaguage_id IN (1,2)
GROUP BY
service_id
HAVING
COUNT(*) = 2
If you're always looking at 2 languages you could do it with joins, but the aggregate version is easier to adapt to differing numbers of language_ids. (Add an OR, or add an item to the IN list, and change the COUNT(*) = 2 to COUNT(*) = 3, etc, etc).
Be aware, however, that this scales very poorly. And with this table structure there isn't much you can do about that.
EDIT Example using a join for 2 languages
SELECT
lang1.service_id
FROM
language_services AS lang1
INNER JOIN
language_services AS lang2
ON lang1.service_id = lang2.service_id
WHERE
lang1.language_id = 1
AND lang2.language_id = 2