Sql query to get products associated with flags - mysql

I have a flags_products table like below (many to many relationship between flags and products),
flags_products :
flag_id -- product_id
1 -- 1
2 -- 1
3 -- 1
1 -- 2
2 -- 2
4 -- 2
What is the SQL query to get rows that have both the flags (flag_id's 1 and 2) associated with a product (product_id)? Obviously:
SELECT *
FROM flags_products
WHERE flag_id = 1
AND flag_id = 2
GROUP BY product_id;
...doesn't work, and gives an empty set. So what would be the correct query?

The exact approach that you should take will depend on your larger requirements. Here's one possible solution:
SELECT
product_id
FROM
Some_Table
WHERE
flag_id IN (1, 2)
GROUP BY
product_id
HAVING
COUNT(*) = 2
This works as long as you can't have duplicates. If a product can be flagged twice with the same flag_id then you would need:
SELECT
product_id
FROM
Some_Table
WHERE
flag_id IN (1, 2)
GROUP BY
product_id
HAVING
COUNT(DISTINCT flag_id) = 2
In both of these cases you'll need the GROUP BY to match your column list. MySQL doesn't require that in order for the query to run (a flaw IMO, but I'll save that argument for another time ), but the results won't be determinable for the columns not in the GROUP BY. You can also use the above to queries as subqueries which you can then join to another table or tables.
If you know that it will always be exactly two flags for which you're looking then you can use EXISTS:
SELECT
T1.product_id
FROM
Some_Table T1
WHERE
EXISTS (
SELECT *
FROM
Some_Table T2
WHERE
T2.product_id = T1.product_id AND
T2.flag_id = 1
) AND
EXISTS (
SELECT *
FROM
Some_Table T2
WHERE
T2.product_id = T1.product_id AND
T2.flag_id = 2
)
Performance may not be very good though.

You can use INTERSECT operator.
SELECT product_id
FROM t
WHERE flag_id = 1
INTERSECT
SELECT product_id
FROM t
WHERE flag_id = 2
This query will return all product_id which have both flag_id = 1 or 2

Related

How to fetch Rows have not this column value in another table column value

I have a table like this:
Tb_Product_Options:
Product_Id Option_Id
1 5
1 7
2 3
3 9
3 6
Now I want to get all Product_Ids have not this values : '5,9'.
at this example my result must be: 2 (product_id)
You can aggregation :
select Product_Id
from table t
group by Product_Id
having sum ( Option_Id in (5,9) ) = 0;
If you want all columns then you can use NOT EXISTS :
select t.*
from table t
where not exists (select 1
from table t1
where t1.Product_Id = t.Product_Id and t1.Option_Id in (5,9)
);
In the old days, we would use an exclusion join:
SELECT DISTINCT x.*
FROM my_table x
LEFT
JOIN my_table y
ON y.stuff = x.stuff
AND y.other_stuff IN(some,values)
WHERE y.id IS NULL
This surely works:
select product_id
from Tb_Product_Options
where product_id not in (
select product_id
from Tb_Product_Options
where option_id in (5,9)
)

MYSQL select count relational division

I have this table in mysql
1. Is it possible to select a count of - ALL same entity_id where field_tags_tid=2 and field_tags_tid=7
in this example the result would be 1 because only entity_id=6 matches field_tags_tid=2 and field_tags_tid=7
This problem is often called Relational Division
SELECT entity_ID
FROM tableName
WHERE field_tags_ID IN (2,7)
GROUP BY entity_ID
HAVING COUNT(*) = 2
if uniqueness was not enforce on field_tags_ID for every entity_ID then a DISTINCT keyword is needed. otherwise, leave it as is,
SELECT entity_ID
FROM tableName
WHERE field_tags_ID IN (2,7)
GROUP BY entity_ID
HAVING COUNT(DISTINCT field_tags_ID) = 2
SQLFiddle Demo
SQL of Relational Division
UPDATE 1
SELECT COUNT(*) totalCOunt
FROM
(
SELECT entity_ID
FROM tableName
WHERE field_tags_tid IN (2,7)
GROUP BY entity_ID
HAVING COUNT(DISTINCT field_tags_tid) = 2
) s
SQLFiddle Demo

Adding one extra row to the result of MySQL select query

I have a MySQL table like this
id Name count
1 ABC 1
2 CDF 3
3 FGH 4
using simply select query I get the values as
1 ABC 1
2 CDF 3
3 FGH 4
How I can get the result like this
1 ABC 1
2 CDF 3
3 FGH 4
4 NULL 0
You can see Last row. When Records are finished an extra row in this format
last_id+1, Null ,0 should be added. You can see above. Even I have no such row in my original table. There may be N rows not fixed 3,4
The answer is very simple
select (select max(id) from mytable)+1 as id, NULL as Name, 0 as count union all select id,Name,count from mytable;
This looks a little messy but it should work.
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM
(
SELECT 1 as ID
UNION
SELECT 2 as ID
UNION
SELECT 3 as ID
UNION
SELECT 4 as ID
) a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID IN (1,2,3,4)
UPDATE 1
You could simply generate a table that have 1 column preferably with name (ID) that has records maybe up 10,000 or more. Then you could simply join it with your table that has the original record. For Example, assuming that you have a table named DummyRecord with 1 column and has 10,000 rows on it
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM DummyRecord a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID >= 1 AND
a.ID <= 4
that's it. Or if you want to have from 10 to 100, then you could use this condition
...
WHERE a.ID >= 10 AND
a.ID <= 100
To clarify this is how one can append an extra row to the result set
select * from table union select 123 as id,'abc' as name
results
id | name
------------
*** | ***
*** | ***
123 | abc
Simply use mysql ROLLUP.
SELECT * FROM your_table
GROUP BY Name WITH ROLLUP;
select
x.id,
t.name,
ifnull(t.count, 0) as count
from
(SELECT 1 AS id
-- Part of the query below, you will need to generate dynamically,
-- just as you would otherwise need to generate 'in (1,2,3,4)'
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) x
LEFT JOIN YourTable t
ON t.id = x.id
If the id does not exist in the table you're selecting from, you'll need to LEFT JOIN against a list of every id you want returned - this way, it will return the null values for ones that don't exist and the true values for those that do.
I would suggest creating a numbers table that is a single-columned table filled with numbers:
CREATE TABLE `numbers` (
id int(11) unsigned NOT NULL
);
And then inserting a large amount of numbers, starting at 1 and going up to what you think the highest id you'll ever see plus a thousand or so. Maybe go from 1 to 1000000 to be on the safe side. Regardless, you just need to make sure it's more-than-high enough to cover any possible id you'll run into.
After that, your query can look like:
SELECT n.id, a.*
FROM
`numbers` n
LEFT JOIN table t
ON t.id = n.id
WHERE n.id IN (1,2,3,4);
This solution will allow for a dynamically growing list of ids without the need for a sub-query with a list of unions; though, the other solutions provided will equally work for a small known list too (and could also be dynamically generated).

very difficult mysql query - random order on two tables

Consider this classical setup:
entry table:
id (int, PK)
title (varchar 255)
entry_category table:
entry_id (int)
category_id (int)
category table:
id (int, PK)
title (varchar 255)
Which basically means entries can be in one or more categories (the entry_category table is used as MM/join table)
Now I need to query 6 unique categorys along with 1 unique entries from these categories by RANDOM!
EDIT: To clarify: the purpose of this is to display 6 random categories with 1 random entry per category.
A correct result set would look like this:
category_id entry_id
10 200
20 300
30 400
40 500
50 600
60 700
This would be incorrect as there are duplicates in the category_id column:
category_id entry_id
10 300
20 300
...
And this is incorrect as there are duplicates in the member_id column:
category_id entry_id
20 300
20 400
...
How can I query this?
If I use this simple query with order by rand, the result contains duplicated rows:
select c.id, e.id
from category c
inner join entry_category ec on ec.category_id = c.id
inner join entry e on e.id = ec.entry_id
group by c.id
order by rand()
Performance is at the moment not the most important factor, but I would need a reliably working query for this, and the above is pretty much useless and does not do what I want at all.
EDIT: as an aside, the above query is no better when using select distinct ... and leaving out the group by. This includes duplicate rows as distinct only makes sure that the combinations of c.id and e.id are unique.
EDIT: one solution I found, but probably slow as hell on larger datasets:
select t1.e_id, t2.c_id
from (select e.id as e_id from entry e order by rand()) t1
inner join (select ec.entry_id as e_id, ec.category_id as c_id from entry_category ec group by e_id order by rand()) t2 on t2.e_id = t1.e_id
group by t2.c_id
order by rand()
SELECT category_id, entity_id
FROM (
SELECT category_id,
#ce :=
(
SELECT entity_id
FROM category_entity cei
WHERE cei.category_id = ced.category_id
AND NOT FIND_IN_SET(entity_id, #r)
ORDER BY
RAND()
LIMIT 1
) AS entity_id,
(
SELECT #r := CAST(CONCAT_WS(',', #r, #ce) AS CHAR)
)
FROM (
SELECT #r := ''
) vars,
(
SELECT DISTINCT category_id
FROM category_entity
ORDER BY
RAND()
LIMIT 15
) ced
) q
WHERE entity_id IS NOT NULL
LIMIT 6
This solution is not a piece of code I'd be proud of, since it relies on black magic of session variables in MySQL to keep the recursion stack. However, it works.
Also it's not perfectly random and can in fact yield less than 6 values (if entity_id's duplicate across the categories too often). In this case, you can increase the value of 15 in the innermost query.
Create a unique index or a PRIMARY KEY on category_entity (category_id, entity_id) for this to work fast.
Seems to me that the good way to do this is to pick 6 distinct values from each set, shuffle each list of values (each list individually), and then glue the lists together into a two-column result.
To randomize which six you get, shuffle the entire list of each type of value, and grab the first six.

MySQL - Matching 2 rows (or more!) within a sub-query/table

I have this schema which I need to match 2 rows from
user_data : user_id, field_id, value
A sample output would be:
user_id field_id value
-----------------------------
1 1 Gandalf
1 2 Glamdring
How do I write a query which basically say "Find the user id of the user whose field_id 1 is Gandalf, and field_id 2 is Glamdring?"
SELECT FROM looks at one row at a time. I am stumped on this. I will also need to find a solution that scale gracefully (such as looking at three rows etc.)
You could run a query to get the users that match each of the conditions and intersect the results. Since MySQL doesn't support intersect you can do it with an n-way join:
SELECT T1.user_id
FROM Table1 T1
JOIN Table1 T2 ON T1.user_id = T2.user_id
WHERE T1.field_id = 1 AND T1.value = 'Gandalf'
AND T2.field_id = 2 AND T2.value = 'Glamdring'
I would try the following:
SELECT user_id
FROM user_data
WHERE ( field_id = 1 AND value= 'Gandalf' )
OR ( field_id = 3 AND value = 'Glamdring' )
GROUP BY user_id
HAVING COUNT( field_id ) = 2
It will search for all the rows that match one of your criteria, and use GROUP BY and HAVING afterwards to find the user_id that has the expected count of matches.
select * from user_date where ( field_id= 1 AND value='Gandalf' ) OR ( field_id =2 AND value ='Glamdring' ) ;
The HAVING clause is the key. It turns the query from an "OR" statement into an "AND" statement