I have a table item_category with two columns: item_id, cat_id. Item to category is a many-to-many relationship.
If my table looks like this...
item_id | cat_id
1 | 1
1 | 2
2 | 3
2 | 4
3 | 5
3 | 6
4 | 7
4 | 8
5 | 9
5 | 10
... how can I select a distinct list of item_ids that do not have any rows where category_id is 2 or 7 (yielding item_ids of 2, 3, 5)?
I would do this using aggregation and a having clause:
select item_id
from item_category ic
group by item_id
having max(cat_id = 2) = 0 and
max(cat_id = 7) = 0
This is an example of a "set-within-sets" query. Using group by with having is the most generalizable form for such a query. For instance, if you wanted to be sure that category 3 were included, you would change the having clause to:
having max(cat_id = 2) = 0 and
max(cat_id = 7) = 0 and
max(cat_id = 3) = 1
Try something like this :
SELECT DISTINCT item_id
FROM table_category
WHERE item_id NOT IN
( select distinct item_id
from item_category
where cat_id in (2,7)
)
I'd use a nested SELECT, though there are probably ways to do this with a self join.
select item_id
from item_category t
where not exists (
select 1
from item_category
where item_id = t.item_id
and cat_id in (2,7)
)
group by item_id;
Example
You could also use a NOT IN clause instead:
SELECT DISTINCT item_id
FROM item_category
WHERE item_id NOT IN (
select distinct item_id
from item_category
where cat_id in (2,7));
Example
Both queries are probably similar in performance, but you can test if your data set is large.
Something like:
SELECT DISTINCT item_category.item_id
FROM item_category
INNER JOIN (
SELECT item_id ,SUM(CASE cat_id WHEN 2 THEN 1 WHEN 7 THEN 1 ELSE 0 END) AS catcount
FROM item_category
GROUP BY item_id
) AS exclude
ON item_category .item_id = exclude.item_id
WHERE exclude.catcount=0
Updated the answer I think this is what you meant.
One way would be
SELECT DISTINCT ITEM_ID
FROM ITEM_CATEGORY
WHERE ITEM_ID NOT IN (SELECT DISTINCT ITEM_ID
FROM ITEM_CATEGORY
WHERE CATEGORY_ID IN (2, 7))
which produces your desired results. If you want to have a bit more fun, you could do
SELECT DISTINCT ic1.ITEM_ID
FROM ITEM_CATEGORY ic1
LEFT OUTER JOIN (SELECT DISTINCT ITEM_ID
FROM ITEM_CATEGORY
WHERE CATEGORY_ID IN (2, 7)) ic2
ON ic2.ITEM_ID = ic1.ITEM_ID
WHERE ic2.ITEM_ID IS NULL
which also gets the results you're looking for and, if you're not familiar with how a LEFT OUTER JOIN works, might make for an interesting time puzzling through how and why it works.
SqlFiddle here.
Share and enjoy.
It can be done with a simple sub query:
SELECT DISTINCT item_id
FROM ic
WHERE item_id NOT IN (
SELECT DISTINCT item_id FROM ic WHERE cat_id IN (2,7)
);
Related
I am using mariadb and I have a table called links:
id | product_id | last_change
------------------------------
1 1 xxx
2 2 xxx
3 5 xxx
4 5 xxx
I want to find every object (3, 4 in this example) that occures more than once. Following this answer I tried:
SELECT product_id, COUNT(*) from links HAVING COUNT(*) > 1
But this results in the (adapted to this example) first row being shown and the total number of product_id occurrences:
product_id | COUNT(*)
---------------------
1 4
I wanted to achieve a list of all items occuring more than once:
id | product_id | last_change
------------------------------
3 5 xxx
4 5 xxx
An aggregation function without GROUP BY always results in only one row result as it aggregates all rows
So use a GROUP BY
SELECT product_id, COUNT(*) from links GROUP BY product_id HAVING COUNT(*) > 1
To see all entry with the count of the product_id , you can do following
SELECT l1.product_id , last_change , Count_
FROM links l1
JOIN (SELECT product_id, COUNT(*) as Count_ from links GROUP BY product_id HAVING COUNT(*) > 1) l2
ON l1.product_id = l2.product_id
Try below statement
select id, product_id, count(product_id)
from links
group by (product_id)
having count(product_id)> 1;
I have these SELECT statements in SQL:
this:
SELECT
*
FROM
products
WHERE
product_category = '12'
LIMIT
3
and this:
SELECT
*
FROM
products
WHERE
product_category = '36'
LIMIT
3
and this:
SELECT
*
FROM
products
WHERE
product_id IN ('3178','3181','7403')
LIMIT
3
As you can see they are very similar, what I want is to run these 3 statement effectively, point of that is that whole result should be 9 rows long (because 3x3), and firstly should be displayed three products from category 12, then second three products should be displayed from category 36 and the last three products should be products with IDs 3178,3181,7403.
I know that I can use UNION like this:
(SELECT
*
FROM
products
WHERE
product_category = '12'
LIMIT
3)
UNION
(SELECT
*
FROM
products
WHERE
product_category = '36'
LIMIT
3)
UNION
(SELECT
*
FROM
products
WHERE
product_id IN ('3178','3181','7403')
LIMIT
3)
LIMIT 9
But I wonder, if there is more effective way, because these statements are mostly copies.
for mysql version 8.0 or greater and you can reduce one subquery
select col1,col2,col3 from (SELECT *, row_number() over(partition by product_category order by product_category) rn
FROM
products
WHERE
product_category in( 12,36)
) a where a.rn<=3
union
SELECT
col1,col2,col3
FROM
products
WHERE
product_id IN ('3178','3181','7403') order by product_id
LIMIT
3
In MySQL 8+, you can use window functions:
SELECT p.*
FROM (SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY product_category,
product_id IN (3178, 3181, 7403)
ORDER BY product_category
) as seqnum
FROM products p
WHERE product_category IN (12, 36) OR
product_id IN (3178, 3181, 7403)
) p
WHERE seqnum <= 3
ORDER BY (product_category = 12) DESC,
(product_category = 36) DESC;
UNION ALL
UNION alone will try to merge similar records as one. With UNION ALL, will show everything.
Perhaps if you really want to make this more efficient you have to look at it a slightly different way and not limit yourself to the query, but think about updating your database structure.
For example, you could add a new column in products table to handle the group order.
| product_id | product_category | product_group |
|------------|------------------|---------------|
| 3178 | | c |
| | 12 | a |
| | 36 | b |
| 3181 | | c |
| 7403 | | c |
And get the result with a simple query :
SELECT * from products
WHERE product_group in (a, b, c)
ORDER BY product_group
Depending on why you're limiting to just 3 results per group, it probably makes more sense anyway to use code to manage which of the entries you want to return (so having some code to set the product_group field), not simply the 3 first entries from your table, in no specific order.
Ok I have siple table which contains data:
id cat_id title (with random values)
1 1 test
2 1 tstt
3 3 tewt
4 2 4324
5 3 rterter
Now, I need to create a query which selects only ONE raw per category (cat_id)
(possibly with lowest ID and ordered by cat_id)
So the result should be:
1 1 test
4 2 4324
3 3 tewt
Use GROUP BY :
SELECT MIN(id), cat_id, title FROM table GROUP BY cat_id
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT cat_id, MIN(id) id
FROM tableName
GROUP BY cat_id
) b ON a.cat_id = b.cat_id AND
a.id = b.id
ORDER BY a.cat_id
I have 3 tables: items, purchases, and collaborators. A user can own an item, purchase an item, or be a collaborator on an item. Additionally, items that are purchased can be rated up, +1, or down, -1. An owner or collaborator can't purchase their own item.
I'd like to get all items for a given user and also display the ratings on each item.
Here's my tables:
items | purchases | collaborators
i_id item_id user_id | p_id item_id user_id rating |c_id item_id user_id
1 1 11 | 1 1 13 -1 | 1 1 12
2 2 12 | 2 2 11 1 | 2 2 13
3 3 13 | 3 3 12 NULL |
| 4 1 14 -1 |
Here's my MYSQL query so far:
select *, count(p_id) as tots, sum(rating=1) as yes, sum(rating= '-1') as no
from items
left join purchases
on items.item_id=purchases.item_id
left join collaborators
on items.item_id=collaborators.item_id
where items.user_id=13 or purchases.user_id=13 or collaborators.user_id=13
group by items.item_id
Here's my expected results for user_id=11 (changing each user_id in the WHERE clause):
item_id tots yes no
1 2 0 2
2 1 1 0
// notice how user_id=11 doesn't have anything to do with item_id=3
Here's my expected results for user_id=12:
item_id tots yes no
1 2 0 2
2 1 1 0
3 1 1 0
Here's my expected results for user_id=13:
item_id tots yes no
1 2 0 2
2 1 1 0
3 1 1 0
//notice user_id=13 should have same results as user_id=12. Although, their
relation to each of the 3 items is different, they still either purchased,
own, or collaboratored on each of them.
Unfortunately, I get the first two results but not the correct one for user_id=13.
For user_id=13, item_id=1 the tots=1 and not tots=2 for some reason I can't understand.
Any thoughts, such as, "its better to separate this into 2 queries", would be greatly appreciated,
I'm still not entirly sure I understand you correct but you could try following statement and let us work from there.
Edit
Following statement returns the expected results.
You can verify this (using SQL Server) here.
The gist of this is to
select all possible user_id and item_id combinations from your three tables
select the counts/ratings for each item
combine the results
SQL Statement
SELECT u.user_id, pt.item_id, pt.cnt, pt.yes, pt.no
FROM (
SELECT user_id, item_id, title FROM items
UNION SELECT user_id, item_id, NULL FROM purchases
UNION SELECT user_id, item_id, NULL FROM collaborators
) u INNER JOIN (
SELECT COUNT(*) AS cnt
, SUM(CASE WHEN ISNULL(rating, 1) = 1 THEN 1 ELSE 0 END) AS yes
, SUM(CASE WHEN rating =-1 THEN 1 ELSE 0 END) AS no
, item_id
FROM purchases
GROUP BY
item_id
) pt ON pt.item_id = u.item_id
MYSQL statement
SELECT u.user_id, pt.item_id, pt.cnt, pt.yes, pt.no, u.title
FROM (
SELECT user_id, item_id, title FROM items where user_id=13
UNION SELECT user_id, item_id, NULL FROM purchases where user_id=13
UNION SELECT user_id, item_id, NULL FROM collaborators where user_id=13
) u INNER JOIN (
SELECT COUNT(*) AS cnt
, SUM(rating=1) AS yes
, SUM(rating =-1) AS no
, item_id
FROM purchases
GROUP BY
item_id
) pt ON pt.item_id = u.item_id
SELECT * FROM menu WHERE item_id = 1 OR item_id = 2 OR item_id = 3;
The above statement returns 3 rows.
But the statement below only returns 2 rows.
SELECT * FROM menu WHERE item_id = 1 OR item_id = 1 OR item_id = 2;
And I understand why like, but is there a way to force item_id 1 to be returned twice???
Example of what I want to be returned:
id -> 1 Chips €2.50
id -> 1 Chips €2.50
id -> 2 Coke €1.60
--------------------
Total €6.60
You would have to do something like this:
SELECT * FROM menu WHERE item_id = 1
UNION ALL
SELECT * FROM menu WHERE item_id = 1
UNION ALL
SELECT * FROM menu WHERE item_id = 2
You could join on another table, for example
SELECT * FROM menu
INNER JOIN order_items ON menu.item_id = order_items.item_id
WHERE order_id = 123;
Or just duplicate them in your application.
You shouldn't really need to do what you're asking for.
You just make a join among its aliases, e.g.
select top 5 * from item a, item b where a.itemid =1
It will print data like the following.
1 abc
1 abc
1 abc
1 abc
1 abc
Hope, you will understand.
You need a way to generate a dummy rowset to do this, and MySQL lacks it.
You can do the following:
SELECT *
FROM (
SELECT 1 AS x
UNION ALL
SELECT 1 AS x
UNION ALL
SELECT 2 AS x
) dummy
JOIN menu
ON menu.id = dummy.x
Use unions:
SELECT * FROM menu WHERE item_id = 1
UNION ALL
SELECT * FROM menu WHERE item_id = 1
UNION ALL
SELECT * FROM menu WHERE item_id = 2
why do you want to query db twice for same thing. Just query once and do the modification
(add rows or display same row twice) using programming language you are using.
Something like
id -> 1 Chips €2.50 X 2
id -> 2 Coke €1.60
--------------------
Total €6.60
The first answer looks wrong. It should be left outer join with order_items as first table...
SELECT * FROM order_items oi
left outer JOIN menu m
ON oi.item_id = m.item_id
WHERE order_id = 123;