Iterate through one table to update another table on sql - sql

I'm using Postgresql and it is costly to use count. What I'm trying to do is counting one table ids in another one and update count number.
Let's say first table is categories
id
name
num_count
Other table is Restaurants
id
name
category_id
I want to update categories.num_count based on the count value of Restaurants.category_id.
I can do this with
Update Categories set Categories.num_count = (select count(*) from Restaurants where Restaurants.category_id = Categories.id)
yet it tooks too much time. I tried to do something like iterate Restaurants 1 time and update Categories.num_count ++ but i couldn't do it right because it makes every categories num_count 1. It stops working after 1 execution.
Is there a better way to do this with sql?

update categories c
set num_count = r.num_count
from (
select category_id, count(*) as num_count
from restaurants
group by category_id
) r
where c.id = r.category_id

How about:
with countPerCategory (categoryId, numCount) as
{
select category_id, count(*) from Restaurants
group by category_id
}
update Categories set num_count = countPerCategory.numCount
from countPerCategory
where Categories.id = countPerCategory.categoryId;
PS: I find it an unnecessary thing to do. You need to update the num_count whenever a Restaurant is added or deleted.

Related

SQL many-to-many, how to check criteria on multiple rows

In many-to-many table, how to find ID where all criteria are matched, but maybe one row matches one criterion and another row matches another criterion?
For example, let's say I have a table that maps shopping carts to products, and another table where the products are defined.
How can I find a shopping cart that has at least one one match for every criterion?
Criteria could be, for example, product.category like '%fruit%', product.category like '%vegetable%', etc.
Ultimately I want to get back a shopping cart ID (could be all of them, but in my specific case I am happy to get any matching ID) that has at least one of each match in it.
I am assuming a table named cart_per_product with fields cart,product, and a table named product with fields product,category.
select cart from cart_per_product c
where exists
(
select 1 from product p1 where p1.product=c.product and p1.category like N'%fruit%'
)
and exists
(
select 1 from product p2 where p2.product=c.product and p2.category like N'%vegetable%'
)
You can use ANY and ALL operators combined with outer joins. A simple sample on a M:N relation:
select p.name
from products p
where id_product = ALL -- all operator
( select pc.id_product
from categories c
left outer join product_category pc on pc.id_product = p.id_product and
pc.id_category = c.id_category
)
I think you can figure out the column names
select c.id
from cart c
join product p
on c.pID = p.ID
group by c.id
having count(distinct p.catID) = (select count(distinct p.catID) from product)
Generic approach that possibly isn't the most efficient:
with data as (
select *,
count(case when <match condition> then 1 end)
over (partition by cartid) as matches
from <cart inner join products ...>
)
select * from data
where matches > 0;

SQL update query with subquery search condition

I have a homework question where we need to write an UPDATE statement to change category name of Categories in Ass7 by removing its last two characters if the category's products in dbo schema has an average unit price above $30.
Ass7 is a Schema we made of a database that has the relevant tables Categories and Products. the Categories table has the Category Name and the Products table has unit prices, with each product having a category ID. I was trying something like this
UPDATE Ass7.Categories
SET CategoryName = LEFT(CategoryName, (LEN(CategoryName) - 2))
WHERE EXISTS (
SELECT CategoryID
,AVG(UnitPrice) AS average
FROM Ass7.Categories
INNER JOIN dbo.Products ON Ass7.Categories.CategoryID = dbo.Products.CategoryID
WHERE average > 30
GROUP BY Ass7.Categories.CategoryID
);
But I'm a but confused as to where to go.
I think you are over-complicating the query. you just need a sub-query which return category_id of products having avg(unit_price) > 30 as below.
UPDATE Ass7.Categories
SET CategoryName = LEFT(CategoryName, (LEN(CategoryName) - 2))
WHERE CategoryID IN(
SELECT p.CategoryID
FROM dbo.Products p
GROUP BY p.CategoryID
having AVG(p.UnitPrice) > 30
);

Optimizing a PostgreSQL SELECT query (sqlfiddle)

I have 2 tables named player and team, which are tied together in a many-to-many relationship via a third table named player_team.
The table structure & current query can be found: http://sqlfiddle.com/#!2/e6db4/18
I need a query, that returns the player's data, along with team.id, which includes the player's maximum rating. Also, if the rating is <= X, then that player should be excluded.
The example query returns the correct results, but is quite inefficient.
Obviously, the same results can be achieved by accessing each table's rows only once, but the question is how to achieve this? (I'd prefer replies that are in PostgreSQL dialect)
Here is a query that you could try:
with all_comb as (
select p.id as PLID,
t.id as TID,
t.rating as RATING
from player p,
team t,
player_team pt
where p.id = pt.pid
and t.id = pt.tid
and t.rating >0)
select distinct
a.PLID,
a.TID,
a.RATING
from all_comb a
where a.RATING = (
select max(b.RATING)
from all_comb b
where a.PLID = b.PLID)
order by PLID;
The sqlfiddle is here.

Complex SQL query on many to many

I have three tables in PostgreSQL:
1. product: id, name
2. param: id, name
3. param_product: id, product_id, param_id, value - (!) it's Many to Many
It's a problem with select product with, for example, "(param_id=1 and value=2000) and (param_id=2 and value=1000)"
What's the way to solve this?
Thanks.
The approach to solving this also depends on what columns you want to return in your select. If all you want is the columns from product then it is simple.
SELECT *
FROM product
WHERE EXISTS (SELECT 1 FROM product_id = product.id AND param_id=1 and value=2000)
AND EXISTS (SELECT 1 FROM product_id = product.id AND param_id=2 and value=1000)
Also, as far as SQL is concerned this is still a 1-to-many relationship.
I could be getting the wrong end of the stick, but don't you just need something like
SELECT pr.*
FROM product pr
INNER JOIN param_product pp ON pr.id = pp.product_id
AND pa.id = pp.param_id
WHERE
(
pa.id = 1
AND pp.value = 2000
)
OR
(
pa.id = 2
AND pp.value = 1000
)
You said:
(param_id=1 and value=2000) and (param_id=2 and value=1000)
Notice param_id can't be 1 AND 2 at the same time :) Try this instead:
(param_id=1 and value=2000) OR (param_id=2 and value=1000)
This will give you both param_id 1 where value equals 2000 and param_id 2 where value equals 1000.
If your tables are designed for N:M relationships, of course you're going to have trouble selecting unique values. The easiest approach here would be using MIN() or MAX() functions
SELECT DISTINCT MAX(product.id)
FROM product
JOIN param_product ON param_product.product_id = product.id
WHERE param_id IN ('1000', '2000')
I think this should help you get on your way.
Also if product is unique to param, you dont need "id" column in your "param_product" table, you can assign primary key ON (product_id, param_id) columns since they are probably bound to be unique.

MS SQL - Problem selecting a subset of records

I'm having a SQL brainfart moment. I am trying to get a set of records when any of the attribute IDs for that product is a certain value.
Problem is, I need to get all other attributes for that same product along with it.
Here's an illustration for what I mean:
Is there a way to do that? Currently I am doing this
select product_id
from mytable
where product_attribute_id = 154
But I obviously only get the single record:
Any help would be greatly appreciated. My SQL skills are a bit basic.
EDIT
There's one condition I forgot to mention. There are times where I need to be able to filter on two attribute IDs. For example, in the first image above, the lower set (product ID 31039) has attribute id 395. I would need to filter on 154, 395. The result would not include the top set (31046) which does not have an attribute id 395.
I think is what you're looking for:
SELECT * myTable where Product_Id IN (SELECT Product_Id FROM MyTable WHERE Product_AttributeID = #parameterValue)
In English: Get me all the records such that their product id is in the set of all product ids such that their attribute id is equal to #parameterValue.
EDIT:
SELECT * myTable where Product_Id IN (SELECT Product_Id FROM MyTable WHERE Product_AttributeID = #parameterValue1) AND Product_Id IN (SELECT Product_Id FROM MyTable WHERE Product_AttributeID = #parameterValue2)
That should do it.
Using proper joins, you can link back to the same table
select B.*
from mytable A
-- retrieve B records from A record link
inner join mytable B on B.product_id = A.product_id
where A.product_attribute_id = 154 -- all the A records
EDIT: to get products that have 2 attributes, you can join another time
select C.*
from mytable A
-- retrieve B records from A record link
inner join mytable B on B.product_id = A.product_id
inner join mytable C on C.product_id = A.product_id
where A.product_attribute_id = 154 -- has attrib 1
AND B.product_attribute_id = 313 -- has attrib 2