I have a little problem with my SQL sentence. I have a table with a product_id and a flag_id, now I want to get the product_id which matches all the flags specified. I know you have to inner join it self, to match more than one, but I don't know the exact SQL for it.
Table for flags
product_id | flag_id
1 1
1 51
1 23
2 1
2 51
3 1
I would like to get all products which have flag_id 1, 51 and 23.
get the product_id which matches all the flags specified
This problem is called Relational Division. One way to solve it, is to do this:
GROUP BY product_id .
Use the IN predicate to specify which flags to match.
Use the HAVING clause to ensure the flags each product have,
like this:
SELECT product_id
FROM flags
WHERE flag_id IN(1, 51, 23)
GROUP BY product_id
HAVING COUNT(DISTINCT flag_id) = 3
The HAVING clause will ensure that the selected product_id must have both the three flags, if it has only one or two of them it will be eliminated.
See it in action here:
SQL Fiddle Demo
This will give you only:
| PRODUCT_ID |
--------------
| 1 |
try this:
SELECT *
FROM your_table
WHERE flag_id IN(1,2,..);
Firstly it would help if you can specify what you have tried before, but as I understood you need to get products with certain flags, so you can just use WHERE:
SELECT product_id FROM Product WHERE flag_id IN (1,2,3,4,5)
Try:
SELECT *
FROM TABLE_NAME A INNER JOIN TABLE_NAME B ON A.product_id = B.product_id
Related
I have related products table like this:
product_id | related_product_id
1 | 2
1 | 3
1 | 4
2 | 1
3 | 1
4 | 1
But instead I would like to insert new related product ids so they all match. I.E. If product 1 has 2,3,4 I wan't that products 2,3,4 also have the same related ids which are missing.
Not sure how it's called but is this possible? Many thanks.
You can use a SELECT query as the source of data in an INSERT
INSERT INTO related_products (product_id, related_product_id)
SELECT r1.product_id, r2.related_product_id
FROM related_products AS r1
CROSS JOIN related_products AS r2
WHERE r1.product_id != 1
AND r2.product_id = 1
This join will get all of product 1's related products and combine them with all the other product IDs.
You can give this query a try (untested, make a backup first!):
insert into related_products (product_id,related_product_id) (select related_product_id, product_id from related products);
I would suggest to user bidirectional condition to get the inter related products. For example if you apply condition on single column product_id, you will not get visa-versa result. But, if you check that condition on both column, you will get the result.
For example:
select related_product_id, product_id from products where related_product_id=1 OR product_id=1
This will give your related product id in either related_product_id or product_id.
Same you can get it for product 2 i.e.
select related_product_id, product_id from products where related_product_id=2 OR product_id=2
This will give all your related product id in either related_product_id or product_id.
I have a table like this:
userid | trackid | path
123 70000 ad
123 NULL abc.com
123 NULL Apply
345 70001 Apply
345 70001 Apply
345 NULL Direct
345 NULL abc.com
345 NULL cdf.com
And I want a query like this. When path='abc.com', num_website +1; when path='Apply', num_apply +1
userid | num_website | num_Apply | num_website/num_Apply
123 1 1 1
345 1 2 0.5
My syntax looks like this:
select * from
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num from
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
My question is
1. how to have the field num_website/num_apply in term of my syntax above?
2. is there any other easier way to get the result I want?
Any spots shared will appreciate.
The simplest way to do it would be to change the select line:
SELECT a1.userid, a1.is_CWS, a2.Apply_num, a1.is_CWS/a2.Apply_num FROM
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num
from TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
and then continue with the rest of your query as you have it. The star means "select everything." If you wanted to select only a few things, you would just list those things in place of the star, and if you wanted to select some other values based on those things, you would put those in the stars as well. In this case a1.is_CWS/a2.Apply_num is an expression, and MySql knows how to evaluate it based on the values of a1.is_CWS and a2.Apply_num.
In the same vein, you can do a lot of what those subqueries are doing in a single expression instead of a subquery. objectNotFound has the right idea. Instead of doing a subquery to retrieve the number of rows with a certain attribute, you can select SUM(path="abc.com") as Apply_num and you don't have to join anymore. Making that change gives us:
SELECT a1.userid,
SUM(path="abc.com") as is_CWS,
a2.Apply_num,
is_CWS/a2.Apply_num FROM
TABLE
JOIN
(select userid,count(userid) as Apply_num
FROM TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
GROUP BY userid
Notice I moved the GROUP BY to the end of the query. Also notice instead of referencing a1.is_CWS I now reference just is_CWS (it's no longer inside the a1 subtable so we can just reference it)
You can do the same thing to the other subquery then they can share the GROUP BY clause and you won't need the join anymore.
to get you started ... you can build on top of this :
select
userid,
SUM(CASE WHEN path='abc.com'then 1 else 0 end ) as num_website,
SUM(CASE WHEN path='Apply' and trackid is not NULL then 1 else 0 end ) as Apply_Num
from TABLE
WHERE path='abc.com' or path='Apply' -- may not need this ... play with it
group by userid
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.
So, I have a particular query that I'm trying to tweak a bit. The needs for the project changed a bit and I'm not sure how to approach this.
I have 3 tables - a main table, a "tags" table and then a linking table to tie the tags to the main entries. The wrinkle is that there is a weight given to that linkage and that is used to sum the total weight of the tags linked to a particular Name entry in the main table. In short, a main entry might have multiple tags on it, each with a different weight. The current query sums all of the tag weights and orders them by the total sum of all tags.
UID | Name
-----------------
123 | Robert
UID | Tag_Name
-----------------
1 | Name_One
2 | Name_Two
Tag_ID | Name_ID | Weight
-----------------------------
2 | Name_One | 2
1 | Name_Two | 3
1 | Name_One | 5
Currently, I've built this that accomplishes this fine where 2,1 is a string of the tag id's that I'm looking to match:
SELECT person.id,
SUM(linkage.weight) AS total_weight
FROM (person)
INNER JOIN linked_tags AS linkage
ON linkage.track_id = person.id AND linkage.tag_id IN (2,1)
GROUP BY person.id
HAVING COUNT(DISTINCT linkage.tag_id)=1
ORDER BY total_weight DESC
I want to extend this for another use. Right now, the tag id's that are passed in as a string are subtractive. It only finds matches where both tag id's exist for a certain person id. If I wanted to pass another string of id's in, where if ANY person id's match ANY of the tag id's out of that string, followed by the current subtractive string of id's, plus sum the weight of those tags, how might I go about it?
I believe the correct having clause for your query is:
HAVING COUNT(DISTINCT linkage.tag_id)=2
Your version finds exactly 1 tag.
The following version of the query has tags 3 and 4 being optional:
SELECT person.id, SUM(linkage.weight) AS total_weight
FROM person INNER JOIN
linked_tags AS linkage
ON linkage.track_id = person.id AND
linkage.tag_id IN (2, 1, 3, 4)
GROUP BY person.id
HAVING COUNT(DISTINCT case when linkage.tag_id in (1, 2) then linkage.tag_id end) = 2
ORDER BY total_weight DESC ;
The big difference is the use of the case statement in the count(distinct) clause.
I'm facing a problem while trying to retrieve all productIDs from a table if they match all items in an array, in this case, return products only if they contain every ingredient the user searched for.
Table looks like this
ID produktID ingredientID
----------------------------
1 418 1
2 418 2
3 418 3
4 416 4
5 411 1
6 411 5
7 411 6
I join this table from a products table where the main information is stored. The aim of the query should be to retreive a productID only when all ingredientIDs match with the given array. I've tried using WHERE ingredientID IN(1,5,6) but it always turns out to be an OR statement, returning every ID where any of the ingredients are matched.
So for example, if I pass (1,5,6) or (5,6) the product ID 411 should be returned, but if I pass (2,5,6) it should not.
The query I tried looks like this (simplified, it's part of a 5 way join to other relations like brands and catgories)
SELECT productID FROM products_ingredients_mm WHERE ingredientID IN (1,5,6) GROUP BY productID
but the result contains 418 aswell. How do I get it to match?
I hope I was able to describe the problem in an understandable way, it's really hard for me to wrap my head around it to ask a question.
This is called Relational Division.
SELECT produktID
FROM tableName
WHERE ingredientID IN (1,5,6)
GROUP BY produktID
HAVING COUNT(*) = 3
SQLFiddle Demo
If a unique constraint was not enforce on ingredientID for every produktID, then you need to use DISTINCT
SELECT produktID
FROM tableName
WHERE ingredientID IN (1,5,6)
GROUP BY produktID
HAVING COUNT(DISTINCT ingredientID) = 3
SQLFiddle Demo
Other Source
Relational Division
Try this:
SELECT pi.productID, p.productName
FROM products_ingredients_mm pim
INNER JOIN products p ON pim.productID = p.productID
WHERE ingredientID IN (1,5,6)
GROUP BY productID
HAVING COUNT(DISTINCT ingredientID) = 3