SQL MIN() with GROUP BY select additional columns - sql

I am trying to query a sql database table for the minimum price for products. I also want to grab an additional column with the value of the row with the minimum price. My data looks something like this.
ProductId | Price | Location
1 | 50 | florida
1 | 55 | texas
1 | 53 | california
2 | 65 | florida
2 | 64 | texas
2 | 60 | new york
I can query the minimum price for a product with this query
select ProductId, Min(Price)
from Table
group by ProductId
What I want to do is also include the Location where the Min price is being queried from in the above query. Is there a standard way to achieve this?

One method uses a correlated subquery:
select t.*
from t
where t.price = (select min(t2.price) from t t2 where t2.productid = t.productid);
In most databases, this has very good performance with an index on (productid, price).

Related

How do I find the number of occurrences of a substring in a SQL column, as grouped by another column?

I'm pretty new to SQL, and searched everywhere for an answer to my question but couldn't find one that exactly works.
I have a table with one column of Order IDs, and another with Order notes. I'm trying to find the number of times a word in the Order notes column appears for every ID. So my if my table is something like
ORDERID | ORDER NOTE
------------------------------------------------
357 | gardening shoes
213 | gardening hose
213 | garden rake
178 | rake shoes hose
I want the below result for searching for instances of "garden" in the database
ORDERID | COUNT
------------------------------------------------
357 | 1
213 | 2
178 | 0
I can figure out each portion individually, how to group by the ORDERID, and how to get the # of substrings in a table, but can't seem to be able to combine them correctly.
Thanks in advance for the help
You can do conditional aggregation:
select orderid, sum(case when order_note like '%garden%' then 1 else 0 end) cnt
from mytable
group by orderid
Demo on DB Fiddle:
orderid | cnt
------: | --:
178 | 0
213 | 2
357 | 1
Note: if you are running MySQL, the conditional sum can be simplified as sum(order_note like '%garden%').

Why when using MIN function and selecting another column, we require GROUP BY clause? Doesn't MIN return single record?

I have two tables vehicles and dealership_vehicles. The dealership_vehicles table has a price column. The vehicles table has a column dealership_vehicle_id which relates to the dealership_vehicles id column in the dealership_vehicles table.
I wanted to return just the vehicle make of the cheapest car.
Why is it that the following query:
select
vehicles.make,
MIN(dealership_vehicles.price)
from
vehicles inner join dealership_vehicles
on vehicles.dealership_vehicle_id=dealership_vehicles.id;
Returns the error:
column "vehicles.make" must appear in the GROUP BY clause or be used in an aggregate function
Since MIN function returns a single value it is plausible that SQL query can be constructed that will return a single value without needing GROUP BY.
You say you want to know the make of the cheapest car. The easiest way to do this is
SELECT DISTINCT v.MAKE
FROM VEHICLE v
INNER JOIN DEALERSHIP_VEHICLES dv
ON v.DEALERSHIP_VEHICLE_ID = dv.ID
WHERE dv.PRICE = (SELECT MIN(PRICE) FROM DEALERSHIP_VEHICLES);
Note that because multiple vehicles might have the "cheapest" price it's entirely possible you'll get multiple returns from the above query.
Best of luck.
EDIT
Another way to do it is to take the minimum price, by make, then sort by the minimum price, and then just take the first row. Something like
SELECT *
FROM (SELECT v.MAKE, MIN(dv.PRICE)
FROM VEHICLE v
INNER JOIN DEALERSHIP_VEHICLES dv
ON v.DEALERSHIP_VEHICLE_ID = dv.ID
GROUP BY v.MAKE
ORDER BY MIN(dv.PRICE) ASC)
WHERE ROWNUM = 1;
Think of the term "GROUP BY" as "for each." It's saying "Give me the MIN of dealership_vehicles.price for each vehicles.make"
So you will need to change your query to:
select
vehicles.make,
MIN(dealership_vehicles.price)
from
vehicles inner join dealership_vehicles
on vehicles.dealership_vehicle_id=dealership_vehicles.id
Group by vehicles.make;
If you want the make of the cheapest car, then no aggregation is needed:
select v.make, dv.price
from vehicles v inner join
dealership_vehicles dv
on v.dealership_vehicle_id = dv.id
order by dv.price asc
fetch first one row only;
This gets a little more complicated if you want all rows in the case of ties:
select v.*
from (select v.make, dv.price, rank() over (order by price asc) as seqnum
from vehicles v inner join
dealership_vehicles dv
on v.dealership_vehicle_id = dv.id
) v
where seqnum = 1
So let's say after we join in the price we have the following table (i.e. stored in a #temp table):
#temp Vehicles table:
| Make | Model | Price |
|--------|-------|----------|
| Toyota | Yaris | 5000.00 |
| Toyota | Camry | 10000.00 |
| Ford | Focus | 7500.00 |
If you query it for minimum price without specifying what you're grouping by, then only one minimum function is applied across all of the rows. Example:
select min(Price) from #temp
will return you a single value of 5000.00
If you want to know the make of the cheapest car, you need to filter your results by the cheapest price - it's a two step process. First you find out the cheapest price using min, then in a separate query, you find out which cars are at that price. Once you construct your query correctly, you will notice that this reveals what you might not have though of - you can actually have more than one cheapest make.
Example table:
#temp Vehicles table v2:
| Make | Model | Price |
|--------|--------|----------|
| Toyota | Yaris | 5000.00 |
| Toyota | Camry | 10000.00 |
| Ford | Focus | 7500.00 |
| Ford | Escort | 5000.00 |
query:
select * from #temp
where Price = (select min(Price) from #temp)
result:
| Make | Model | Price |
|--------|--------|----------|
| Toyota | Yaris | 5000.00 |
| Ford | Escort | 5000.00 |

SQL: Values in column listed twice after pivot

When querying a specific table, I need to change the structure of the result, making it so that all values from a given year are on the same row, in separate columns that identify the category that the value belongs to.
The table looks like this (example data):
year | category | amount
1991 | A of s | 56
1992 | A of s | 55
1993 | A of s | 40
1994 | A of s | 51
1995 | A of s | 45
1991 | Total | 89
1992 | Total | 80
1993 | Total | 80
1994 | Total | 81
1995 | Total | 82
The result I need is this:
year | a_of_s | total
1991 | 56 | 89
1992 | 55 | 80
1993 | 40 | 80
1994 | 51 | 81
1995 | 45 | 82
From what I can understand I need to use pivot. However, my problem seems to be that I don't understand pivot. I've attempted to adapt the queries of solutions in similar questions where pivot seems to be part of the answer, and so far what I've come up with is this:
SELECT year, [A of s], [Total] FROM table
pivot (
max(amount)
FOR category in ([A of s], [Total])
) pvt
ORDER BY year
This returns the correct table structure, but all cells in the columns a_of_s and total are NULL, and every year is listed twice. What am I missing to get the result I need?
EDIT: After fixing the errors pointed out in the comments, the only real issue that remains is that years in the year column are listed twice.
Possibly related: Is the aggregate function I use in pivot (max, sum, min, etc) arbitrary?
I assumed you really don't need to pivot your table and with the result you require you can create an alternative approach to achieve it.
This is the query i made that returns according to your requirement.
;With cte as
(
select year, Amount from tbl
where category = 'A of s'
)
select
tbl1.year, tbl2.Amount as A_of_S, tbl1.Amount as Total
from tbl as tbl1
inner join cte as tbl2 on tbl1.year = tbl2.year
where tbl1.category = 'Total'
and this is the SQL fiddle i created for you with your test day. -> SQL fiddle
Much simpler answer:
WITH VTE AS(
SELECT *
FROM (VALUES (1991,'A of s',56),
(1992,'A of s',55),
(1993,'A of s',40),
(1994,'A of s',51),
(1995,'A of s',45),
(1991,'Total',89),
(1992,'Total',80),
(1993,'Total',80),
(1994,'Total',81),
(1995,'Total',82)) V([year],category, amount))
SELECT [year],
MAX(CASE category WHEN 'A of s' THEN amount END) AS [A of s],
MAX(CASE category WHEN 'Total' THEN amount END) AS Total
FROM VTE
GROUP BY [year];

Access SQL query update calculation for duplicates

I have a query that filters results for products which have had orders sent after an user-input date, and calculates what the quantity becomes if the order was sent after that date.
SELECT *, [OnHand]+[OrderJoin.Quantity] AS Qty After
FROM Query3
WHERE (((Query3.ShippedDate)>[Enter End Date] And (Query3.ShippedDate) Is Not Null));
However, I need a way for it to recognise duplicates and update it based on those.
e.g. I have this
ID | Product Name | Qty Before | Qty Shipped | Qty After
11 | Chocolate | 80 | 20 | 100
11 | Chocolate | 80 | 10 | 90
And I'd need a way for it to show Qty After as 110 (after the 10 and 20 shipped)
If I understand correctly, you want an aggregation query. This would be something like this:
SELECT id, ProductName,
OnHand]+ SUM([OrderJoin.Quantity]) AS Qty After
FROM Query3
WHERE Query3.ShippedDate > [Enter End Date] And
Query3.ShippedDate) Is Not Null
GROUP BY id, ProductName, OnHand;
I note that OrderJoin is not defined, but that is the structure of your original query.

Select the difference of two consecutive columns

I have a table car that looks like this:
| mileage | carid |
------------------
| 30 | 1 |
| 50 | 1 |
| 100 | 1 |
| 0 | 2 |
| 70 | 2 |
I would like to get the average difference for each car. So for example for car 1 I would like to get ((50-30)+(100-50))/2 = 35. So I created the following query
SELECT AVG(diff),carid FROM (
SELECT (mileage-
(SELECT Max(mileage) FROM car Where mileage<mileage AND carid=carid GROUP BY carid))
AS diff,carid
FROM car GROUP BY carid)
But this doesn't work as I'm not able to use current row for the other column. And I'm quite clueless on how to actually solve this in a different way.
So how would I be able to obtain the value of the next row somehow?
The average difference is the maximum minus he minimum divided by one less than the count (you can do the arithmetic to convince yourself this is true).
Hence:
select carid,
( (max(mileage) - min(mileage)) / nullif(count(*) - 1, 0)) as avg_diff
from cars
group by carid;