SQL Select Group By Min() - but select other - sql

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker

You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;

SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

Related

How can I do order by without using it in group by

I have a table called products and I would like to get all my products with same asin inside one row and concat their skus in one column I came up with something like this but I have problem with order by sku and other columns that are not inside group by. Is there any solution?
my sql:
SELECT prod.asin, string_agg(p.sku,', ') AS skus,
SUM(p.amazon_inv_available) AS amazon_available_inv,
SUM(p.amazon_inv_total) AS amazon_total_inv
FROM (
SELECT id, asin, sku, amazon_inv_available, amazon_inv_total
FROM products
WHERE store_id IN (12, 10, 11)
ORDER BY sku
) AS prod
LEFT JOIN products AS p ON prod.asin = p.asin
GROUP by prod.asin
dbfiddle
Desired result when order by prod.sku
order by prod.sku DESC
You can order by inside string_agg(sku,', ' order by sku) remove order by from your subquery
You can add ORDER BY to STRING_AGG() to specify the ordering of the concatenated values.
For example:
SELECT
asin,
string_agg(sku,', ' order by id) AS skus,
SUM(amazon_inv_available) AS amazon_available_inv,
SUM(amazon_inv_total) AS amazon_total_inv
FROM products
WHERE store_id IN (12, 10, 11)
GROUP by asin
ORDER BY amazon_available_inv
Result:
asin skus amazon_available_inv amazon_total_inv
----- ----------- --------------------- ----------------
B HH, QW 200 400
A AC, TT, DD 300 600
See db<>fiddle.
I think I found the solution. I used my query as subquery:
SELECT * FROM
(SELECT prod.asin, string_agg(p.sku,', ' ORDER BY p.sku) AS skus,
SUM(p.amazon_inv_available) AS amazon_available_inv,
SUM(p.amazon_inv_total) AS amazon_total_inv
FROM (SELECT id, asin, sku, amazon_inv_available, amazon_inv_total
FROM products WHERE store_id IN(
10,11,12)) AS prod
LEFT JOIN
products AS p
ON prod.asin = p.asin
GROUP by prod.asin) AS res
ORDER BY skus

SQL: How to select the highest priced used item for each day

I need to produce a query that would give me the highest priced used product for each day where the total price of products sold that day exceeds 200.
SELECT *, max(price)
FROM products
WHERE products.`condition` = 'used' and products.price > 200
GROUP BY date_sold
Here is my products table http://prntscr.com/of3hjd
You could try using a join with sum for price > 200 group by date_sol
select m.date_sold, max(m.price)
from my_table m
inner join (
select date_sold, sum(price)
from my_table
group by date_sold
having sum(price)>200
) t on t.date_sold = m.date_sold
group by m.date_sold
You can use window functions for this:
select p.*
from (select p.*,
sum(price) over (partition by date_sold) as sum_price,
row_number() over (partition by date_sold, condition order by price desc) as seqnum
from products p
) p
where sum_price > 200 and
condition = 'used' and
seqnum = 1;
SELECT *, max(price) FROM products
where products.`condition` = 'used' and sum(products.price) > 200
GROUP BY day(date_sold)

Getting the lastest entry grouped by ID

I have a table with stock for products. The problem is that every time there is a stock change, the new value is stored, together with the new Quantity. Example:
ProductID | Quantity | LastUpdate
1 123 2019.01.01
2 234 2019.01.01
1 444 2019.01.02
2 222 2019.01.02
I therefore need to get the latest stock update for every Product and return this:
ProductID | Quantity
1 444
2 222
The following SQL works, but is slow.
SELECT ProductID, Quantity
FROM (
SELECT ProductID, Quantity
FROM Stock
WHERE LastUpdate
IN (SELECT MAX(LastUpdate) FROM Stock GROUP BY ProductID)
)
Since the query is slow and supposed to be left joined into another query, I really would like some input on how to do this better.
Is there another way?
Use analytic functions. row_number can be used in this case.
SELECT ProductID, Quantity
FROM (SELECT ProductID, Quantity, row_number() over(partition by ProductID order by LstUpdte desc) as rnum
FROM Stock
) s
WHERE RNUM = 1
Or with first_value.
SELECT DISTINCT ProductID, FIRST_VALUE(Quantity) OVER(partition by ProductID order by LstUpdte desc) as quantuity
FROM Stock
Just another option is using WITH TIES in concert with Row_Number()
Full Disclosure: Vamsi's answer will be a nudge more performant.
Example
Select Top 1 with ties *
From YourTable
Order by Row_Number() over (Partition By ProductID Order by LastUpdate Desc)
Returns
ProductID Quantity LastUpdate
1 444 2019-01-02
2 222 2019-01-02
So you Could use a CTE(Common Table Expression)
Base Data:
SELECT 1 AS ProductID
,123 AS Quantity
,'2019-01-01' as LastUpdate
INTO #table
UNION
SELECT 2 AS ProductID
,234 AS Quantity
,'2019-01-01' as LastUpdate
UNION
SELECT 1 AS ProductID
,444 AS Quantity
,'2019-01-02' as LastUpdate
UNION
SELECT 2 AS ProductID
,222 AS Quantity
,'2019-01-02' as LastUpdate
Here is the code using a Common Table Expression.
WITH CTE (ProductID, Quantity, LastUpdate, Rnk)
AS
(
SELECT ProductID
,Quantity
,LastUpdate
,ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY LastUpdate DESC) AS Rnk
FROM #table
)
SELECT ProductID, Quantity, LastUpdate
FROM CTE
WHERE rnk = 1
Returns
You could then Join the CTE to whatever table you need.
row_number() function might be the most efficient, but the big slow down in your query is the use of the IN statement when used on a subquery, it's a little bit of a tricky one but a join is faster. This query should get what you want and be much faster.
SELECT
a.ProductID
,a.Quantity
FROM stock as a
INNER JOIN (
SELECT
ProductID
,MAX(LastUpdate) as LastUpdate
FROM stock
GROUP BY ProductID
) b
ON a.ProductID = b.ProductId AND
a.LastUpdate = b.LastUpdate

SQL select x number of rows from table based on column value

I'm looking for a way to select top 3 rows from 4 vendors from a table of products, following this criteria:
Must select 4 vendors.
Must select top 3 products for each vendor ordered by product rating.
I tried doing something like:
select top 12 * product, vendor
from products
order by productrating
but obvisously that goesn't give me 3 products for each vendor.
The product table has:
productid (int), productname (nvarchar(500)), productrating (float),
vendor (id), price (float).
These are the relevant columns.
You can use the ANSI standard row_number() function to get 3 products for each vendor:
select p.*
from (select p.*,
row_number() over (partition by vendor order by rating desc) as seqnum
from products p
) p
where p.seqnum <= 3
If you want 4 vendors:
select top 12 p.*
from (select p.*,
row_number() over (partition by vendor order by rating desc) as seqnum
from products p
) p
where p.seqnum <= 3
order by vendor;
This will give you top 3 Products per vendor. You didn't specify how you're selecting the 4 vendors. That logic could easily be included using the WHERE clause or using a different ORDER BY depending on how you select the 4 vendors.
SELECT TOP 12 vnd.Vendor, apl.ProductName
FROM Vendors vnd
CROSS APPLY (
SELECT TOP 3 ProductID
FROM Products prd
WHERE vnd.ProductID = prd.ProductID
ORDER BY prd.ProductRating DESC
) apl
ORDER BY vnd.VendorName
If you have fixed list of vendors you would like to query you can use the following approach:
SELECT TOP 3
p.ProductID
FROM Products p
WHERE p.ProductID IN ( SELECT v.ProductID
FROM Vendors v
WHERE v.VendorID IN (Vendor1ID, Vendor2ID, Vendor3ID, Vendor4ID)
ORDER BY p.ProductRating DESC
You will need to look for a work-around if you have vendor names to select them filtering out by its name but keeping the join coindition trhough its IDs.

Oracle Complex Sort - Multiple Children

I have a table as follows:
BRAND_ID PRODUCT_ID PRODUCT_DESC PRODUCT_TYPE
100 1000 Tools A
100 1500 Tools A
200 2000 Burgers B
300 3000 Clothing C
300 4000 Makeup D
300 5000 Clothing C
So a Brand can have multiple products, all of the same type or mixed types. If a brands products are all of the same type I need them first in the result, sorted by product type, followed by brands that have different product types. I can do this programatically but I wanted to see if there is a way to do it in the query.
I don't have access to Oracle, but I believe something along these lines should work...
WITH
ranked_data
AS
(
SELECT
COUNT(DISTINCT product_type) OVER (PARTITION BY brand_id) AS brand_rank,
MIN(product_type) OVER (PARTITION BY brand_id) AS first_product_type,
*
FROM
yourTable
)
SELECT
*
FROM
ranked_data
ORDER BY
brand_rank,
first_product_type,
brand_id,
product_type,
product_description
An alternative is to JOIN on to a sub-query to calculate the two sorting fields.
SELECT
yourTable.*
FROM
yourTable
INNER JOIN
(
SELECT
brand_id,
COUNT(DISTINCT product_type) AS brand_rank,
MIN(product_type) AS first_product_type,
FROM
yourTable
GROUP BY
brand_id
)
AS brand_summary
ON yourTable.brand_id = brand_summary.brand_id
ORDER BY
brand_summary.brand_rank,
brand_summary.first_product_type,
yourTable.brand_id,
yourTable.product_type,
yourTable.product_description
How about selecting from a sub-select that figures out number of distinct brands and then sorting by the count?
select t.BRAND_ID,
t.PRODUCT_ID,
t.PRODUCT_DESC,
t.PRODUCT_TYPE
from (select t2.BRAND_ID,
t2.PRODUCT_ID,
count(distinct t2.PRODUCT_TYPE) cnt
from YOURTABLE t2
group by t2.BRAND_ID, t2.PRODUCT_ID) data
join YOURTABLE t on t.BRAND_ID = data.BRAND_ID and t.PRODUCT_ID = data.PRODUCT_ID
order by data.cnt, BRAND_ID, PRODUCT_ID, PRODUCT_TYPE