Fetch the data based on min(sort_order) - sql

I have a table Product which have many Images.
Product
id name
1 Sofa
2 Bed
Images
id product_id image_url sort_order
1 1 "url5" 521
2 1 "url1" 200
3 1 "url1" 100
4 2 "url1" 1
5 2 "url2" 2
I want to fetch images where sort_order have minimum value for 100 products like below:
id product_id image_url sort_order
3 1 "url1" 100
4 2 "url1" 1
I know I need to use min(sort_order) for images but don't find the correct syntax.
I am trying the below. but no luck
select i.*
from images i
join product p on p.id = i.product_id
where p.id in (1, 2, ....)
and i.sort_order = min(sort_order)
Any help to build the correct query?

Number your images per product and keep the rows numbered 1.
select id, product_id, image_url, sort_order
from
(
select
i.*,
row_number() over (partition by product_id order by sort_order) as rn
from images i
) numbered
where rn = 1;
An alternative is to query the table twice:
select *
from images
where (product_id, sort_order) in
(
select product_id, min(sort_order)
from images
group by product_id
);

why you need to use min ?? correct me if i am wrong but you said you need images where sort_order = 1, so you just need to change your where and add limit.
WHERE i.sort_order = 1 LIMIT 100

This is pretty bad code, but maybe it'll give you an idea of how you can do this.
select p.id,p.name,*
From product p
inner join (
select i.product_id, min(sort_order) as minSortOrder
from images i
group by i.product_id
) i on i.product_id = p.id
inner join images ii on ii.product_id = p.id and ii.sort_order = i.minSortOrder
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ccae44d199f9c9ac0edf4b75dd1b973b
edit: another way to do this is to create a row_number ordered by Sort_Order, so the Row_number = 1 will always be the lowest Sort_order, then we wrap that query in an outer query to apply the where clause
SELECT *
FROM (
SELECT p.id
,p.name
,ii.image_url
,ii.sort_order
,row_number() OVER (
PARTITION BY ii.product_id ORDER BY sort_order
) AS RowNumber
FROM product p
INNER JOIN images ii ON ii.product_id = p.id
) s
WHERE s.rownumber = 1
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ca31c7650bc87174110bf9aa09b230d9

Related

How to get all rows from one table which have all relations?

I have 3 tables:
companies (id, name)
union_products (id, name)
products (id, company_id, union_product_id, price_per_one_product)
I need to get all companies which have products with union_product_id in (1,2) and total price of products (per company) is less than 100.
What I am trying to do now:
select * from "companies" where exists
(
select id from "products"
where "companies"."id" = "products"."company_id"
and "union_product_id" in (1, 2)
group by id
having COUNT(distinct union_product_id) = 2 AND SUM(price_per_one_product) < 100
)
The problem I stuck with is that I'm getting 0 rows from the query above, but it works if I'll change COUNT(distinct union_product_id) = 2 to 1.
DB fiddle: https://www.db-fiddle.com/f/iRjfzJe2MTmnwEcDXuJoxn/0
Try to join the three tables as the following:
SELECT C.id, C.name FROM
products P JOIN union_products U
ON P.union_product_id=U.id
JOIN companies C
ON P.company_id=C.id
WHERE P.union_product_id IN (1, 2)
GROUP BY C.id, C.name
HAVING COUNT(DISTINCT P.union_product_id) = 2 AND
SUM(P.price_for_one_product) < 100
ORDER BY C.id
See a demo.
SELECT c.name FROM "companies" c
JOIN "products" p ON c.id = p.company_id
WHERE union_product_id IN (1, 2) AND price_for_one_product < 100
GROUP BY c.name
HAVING COUNT(DISTINCT p.name) =2
This would provide you all the company(s) name(s) which has provides both union_product_id 1 and 2 and the price_for_one_product/ price_per_one_product is less than 100.
Note: You might need to change price_for_one_product with price_per_one_product, as in question you have used price_per_one_product but db-fiddle link table defination has used price_for_one_product.

How can I show all "article" which have more than 3 "bids"?

I wanna show the "ArticleName" of all "offers" that have more than 3 "bids". The number of the "Bids" should be output.
I don't know how I can write it down. But I think I know the Logic. It should count the same number of the Table "bid" and the column "OID" and in the end it should paste the number which is more than 3.
Picture:
Well that's easy enough:
Select ArticleName
, count(*) NumberOfBids
from Offer o
join Bid b
on b.oid = o.oid
group by ARticleName
having count(*) >= 3
SELECT * FROM (
SELECT o.ArticleName, count(b.BID) as numberOfBids
FROM Offer as o INNER JOIN bid as b ON o.oid = b.oid
GROUP BY o.ArticleName
) as c
WHERE c.numberOfBids > 3

Updating Product Order is not working

I have Products table with (Name, ParentID, Order) columns. I have a insert statement which keep the inserted child products. After the insert I need to update the order,
I have the following SQL,
UPDATE Products
SET [Order] = (SELECT ISNULL(MAX([Order]), 0) + 1 FROM Products WHERE ParentID = CP.ParentID)
FROM Products P
INNER JOIN #InsertedChildProduct CP ON (CP.ID = P.ID)
The problem is that I am updating the order of products that are just inserted, but [Order] is not working. If I have,
Products
--------
ParentID Order
----------------
1 1
1 2
and let say I have inserted 2 child products then the table should be,
Products
--------
ParentID Order
----------------
1 1
1 2
1 3
1 4
But I am seeing,
Products
--------
ParentID Order
----------------
1 1
1 2
1 3
1 3
You can try this, taken from the answers on here:
declare #MaxNumber int
set #MaxNumber = 0
UPDATE Products
SET [Order] = #MaxNumber, #MaxNumber = (SELECT ISNULL(MAX([Order]), 0)
FROM Products
WHERE ParentID = CP.ParentID) + 1
FROM Products P
INNER JOIN #InsertedChildProduct CP ON (CP.ID = P.ID)
Try like this instead, you need to gt the MAX() first in inner query
UPDATE P
SET [Order] = X.newval
FROM Products P
JOIN
(
SELECT ID, (ISNULL(MAX([Order]), 0) + 1) as newval
FROM Products P
JOIN #InsertedChildProduct ip
on P.ParentID = ip.ParentID
group by ID
) X
ON X.ID = P.ID
Thought about this a bunch of different ways and can't see how this would work without imposing order on both datasets, even if arbitrary. Here's one way to do it (Fiddle - make sure to build the schema first, then run the code): http://www.sqlfiddle.com/#!3/d34df/3)
WITH cteRN_c
AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID) AS RN_c,
ID
FROM #InsertedChildProduct
),
cteRN_p
AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY ParentID ORDER BY [Order]) AS RN_p,
ParentID,
[Order]
FROM Products
WHERE [Order] IS NULL
)
UPDATE p
SET [Order] = (SELECT ISNULL(MAX([ORDER]), 0) FROM Products WHERE ParentID = p.ParentID) + c.RN_c
FROM cteRN_p p INNER JOIN cteRN_c c
ON p.ParentID = c.ID AND
p.RN_p = c.RN_c;
We impose order by adding arbitrary row numbers to both the temp table set and the parent set, via ROW_NUMBER in CTEs. From that point, it's just a matter of joining the CTEs on the correct datapoints, and running the updates against the parent CTE. Granted, it's arbitrary which child will get numbered in which order, but at least it will happen.
Edit: Forgot the ISNULL in the MAX portion of the query - in case no children yet. Fiddle updated as well.

How to ensure outer join with filter still returns all desired rows?

Imagine I have two tables in a DB like so:
products:
product_id name
----------------
1 Hat
2 Gloves
3 Shoes
sales:
product_id store_id sales
----------------------------
1 1 20
2 2 10
Now I want to do a query to list ALL products, and their sales, for store_id = 1. My first crack at it would be to use a left join, and filter to the store_id I want, or a null store_id, in case the product didn't get any sales at store_id = 1, since I want all the products listed:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
WHERE store_id = 1 or store_id is null;
Of course, this doesn't work as intended, instead I get:
name sales
---------------
Hat 20
Shoes 0
No Gloves! This is because Gloves did get sales, just not at store_id = 1, so the WHERE clause has filtered them out.
How then can I get a list of ALL products and their sales for a specific store?
Here are some queries to create the test tables:
create temp table test_products as
select 1 as product_id, 'Hat' as name;
insert into test_products values (2, 'Gloves');
insert into test_products values (3, 'Shoes');
create temp table test_sales as
select 1 as product_id, 1 as store_id, 20 as sales;
insert into test_sales values (2, 2, 10);
UPDATE: I should note that I am aware of this solution:
SELECT name, case when store_id = 1 then sales else 0 end as sales
FROM test_products p
LEFT JOIN test_sales s ON p.product_id = s.product_id;
however, it is not ideal... in reality I need to create this query for a BI tool in such a way that the tool can simply add a where clause to the query and get the desired results. Inserting the required store_id into the correct place in this query is not supported by this tool. So I'm looking for other options, if there are any.
Add the WHERE condition to the LEFT JOIN clause to prevent that rows go missing.
SELECT p.name, coalesce(s.sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
AND s.store_id = 1;
Edit for additional request:
I assume you can manipulate the SELECT items? Then this should do the job:
SELECT p.name
,CASE WHEN s.store_id = 1 THEN coalesce(s.sales, 0) ELSE NULL END AS sales
FROM products p
LEFT JOIN sales s USING (product_id)
Also simplified the join syntax in this case.
I'm not near SQL, but give this a shot:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id AND store_id = 1
You don't want a where on the whole query, just on your join

mysql group_concat

I have two tables.
products
id title image_ids
---------------------
1 myproduct 1,2,3
images
id title file_name
-------------------------
1 myimage myimage.jpg
2 myimage2 myimage2.jpg
3 myimage3 myimage3.jpg
I want to query so that the names of the images are concatenated into a single field for each product reference.
This query doesn't work
SELECT products.title,
products.image_ids,
GROUP_CONCAT(images.file_name)
FROM products
LEFT JOIN images ON images.id IN (products.image_ids)
WHERE products.id = 1
GROUP BY products.id
This one does:
SELECT products.title,
products.image_ids,
GROUP_CONCAT(images.file_name)
FROM products
LEFT JOIN images ON images.id IN (1,2,3)
WHERE products.id = 1
GROUP BY products.id
And produces the desired results
title image_ids file_names
--------------------------------------------------------------
myproduct 1,2,3 myimage.jpg,myimage2.jpg,myimage3.jpg
Why doesn't the first query work when it is asking the same thing as the second query and how can I make it work.
IN does not work for comma-separated lists of values.
Basically, you are not comparing integers, you are comparing strings:
SELECT 1 IN (1, 2, 3) -- True
SELECT 1 IN ('1, 2, 3') -- False ('1' <> '1, 2, 3')
Use FIND_IN_SET instead:
SELECT products.title,products.image_ids, GROUP_CONCAT(images.file_name)
FROM products
LEFT JOIN
images
ON FIND_IN_SET(images.id, products.image_ids)
WHERE products.id = 1
GROUP BY
products.id
This, however, is not the best solution performance-wise since FIND_IN_SET is non-sargable. It will require a full table scan on images.
If you have some reasonable limit on the number of values in products.image_ids (say, no more than 5 images per product), you can use this query instead:
SELECT products.title,products.image_ids, GROUP_CONCAT(images.file_name)
FROM (
SELECT 1 AS n
UNION ALL
SELECT 2 AS n
UNION ALL
SELECT 3 AS n
UNION ALL
SELECT 4 AS n
UNION ALL
SELECT 5 AS n
) q
CROSS JOIN
products
LEFT JOIN
images
ON SUBSTRING_INDEX(SUBSTRING_INDEX(image_ids, ',', n), ',', 1)
WHERE products.id = 1
AND SUBSTRING_INDEX(image_ids, ',', n) <> SUBSTRING_INDEX(image_ids, ',', n - 1)
GROUP BY
products.id