SQL - select within select in query - sql

In a very standard question of selecting all item names and their prices for those prices greater than the average price.
The query using having has the form:
'''SELECT Name, Price FROM items
GROUP BY Name
HAVING Price >
(SELECT AVG(Price) FROM items )
;'''
It seems that only when AVG is used in a SELECT statement then it is returning the column average. Else if I use HAVING Price > AVG(Price) nothing is returned. Why is an additional SELECT statement required, instead of just using AVG on Price?

That's just how GROUP BY works. In the SELECT part of the query, you can only have the columns that are used in the GROUP BY clause, or the aggregates, like MIN, MAX, AVG, SUM, etc. In this case, what's your aggregate of the column Price that you want to output? you should specify that.
Alternatively, you could use a windowed function for calculating the average price for each name unit, and then add the subquery to filter on that:
SELECT *
FROM (SELECT Name, Price, AVG(Price) OVER (PARTITION BY Name) AS Average_Price
FROM items) AS X
WHERE X.Price > X.Average_Price

I think your best option is a simple subquery in WHERE condition
select i.*
from items i
where Price > (
select avg(Price) avgPrice
from items ii
--where ii.Name = i.Name /* add this line if you need average to be calculated per Name */
)

Related

It is possible to do extra aggregation in filter clause? (PostgreSQL)

I want to write something like this:
select
"id",
"plant",
"product",
"uom",
count(*) as transactions_count,
avg(("invoice_price" / "quantity")) filter (where ("date" == max(date)) as "LAST_GUIDANCE", -- I want group by date only for this
from "transactions"
group by
"id",
"plant",
"product",
"uom"
Where I want to count last guidance but only for rows with last date.
Problem is that I cannot add "date" to group by, because I want this grouping only for last_guidance..
It is possible to write it somehow easily in Postgres (use "over" for ex.) or I have to create too separated queries and then join them?
Thanks for answers
This is not allowed. The error message is pretty clear:
ERROR: aggregate functions are not allowed in FILTER
LINE 1: select count(*) filter (where x = max(x))
Nor are window functions allowed.
You can use a subquery to calculate the maximum date:
select id, plan, product, uom,
count(*) as transactions_count,
avg(invoice_price / quantity) filter (where date = max_date) as LAST_GUIDANCE
from (select t.*,
max(date) over (partition by id, plan, product, uom) as max_date
from transactions t
) t
group by id, plan, product, uom;
Note that I removed the double quotes. Don't escape column names. That just makes queries harder to write and read.

MIN and DISTINCT for ORACLE DB

I have this query
SELECT table_article.articleID, table_article_to_date.sellDate, MIN(table_article.price) AS minPrice
FROM table_article table_article
LEFT JOIN table_article_to_date table_article_to_date ON (table_article.ord_no=table_article_to_date.ord_no)
WHERE table_article.price > 0 AND table_article_to_date.sellDate BETWEEN_TWO_DATES
GROUP BY table_article.articleID, table_article_to_date.sellDate, table_article.price
For the sell_date I use a time range. My problem is, that i get more than one entrie each articleID.
I wish to have the lowest price of each articleID in a specified time range. DISTINCT is not woking with MIN
Givs a change to make this with a query?
The problem here is that you are adding the sell date to the GROUP BY clause. TO solve the issue, you need to take it out and make use of subqueries to get it back. To achieve this, you can do an inner join of the query and a query with the id, sell date and prize on the id and prize.
Also, no need for the prize in the group by, since it's already in the MIN.
SELECT articleData.articleId, articleData.sellDate, articleData.price FROM
(
SELECT articleId, MIN(price)
FROM table
[...]
GROUP BY articleId
) AS minPrice
INNER JOIN
(
SELECT articleId, sellDate, price
FROM table
) AS articleData
ON minPrice.price = articleData.price AND minPrice.articleId = articleData.articleId

I'm trying to find the grouped category's that have a average greater than x

The problem is that when I run my query I receive the incorrect Average math back from what I get on a calculator when using the greater than statement.
What's happening is when the average is occurring it is only using values above the where clause value. What I want to happen is for it to GROUP the category's get the average of all the prices in that category and then only show me the ones above the value.
This version works because it is looking directly at the category without the greater than question.
SELECT CategoryID, AVG(Price)
FROM Products
WHERE CategoryID="3"
GROUP BY CategoryID
Returned Data
CategoryID AVG(Price)
3 25.16
This is the version I'm having trouble with.
SELECT CategoryID, AVG(Price)
FROM Products
WHERE Price > 30
GROUP BY CategoryID;
Returned Data
CategoryID AVG(Price)
1 154.75
2 41.95
3 51.3575
4 38.300000000000004
5 35.625
6 73.14750000000001
7 49.3
8 46.75
use having
SELECT CategoryID, AVG(Price)
FROM Products
GROUP BY CategoryID
having AVG(Price)>x
having is a group filter , so here you need having instead of where
You want having - using where is just giving you the averages of prices that are over 30.
So:
SELECT CategoryID, AVG(Price)
FROM Products
GROUP BY CategoryID
having AVG(Price) > 30
Try this-
SELECT CategoryID, AVG(Price)
FROM Products
--WHERE Price > 30
GROUP BY CategoryID;
HAVING AVG(Price) > 30
Usually you use the HAVING keyword to select from groups formed by GROUP BY. In this HAVINGclause you can use all the different criteria to select from the groups.
SELECT CategoryID, AVG(Price)
FROM Products
GROUP BY CategoryID
HAVING AVG(Price) > x
You need to use HAVING in this case. HAVING allows you to filter on aggregated (like an average) fields, while WHERE limits you to only the actual values in the data at rest. Try this:
SELECT CategoryID, AVG(Price)
FROM Products
GROUP BY CategoryID
HAVING AVG(Price) > 30
Here is a good tutorial on HAVING vs WHERE

SQL Group by with min and corresponding values in a single query

Can I write a single query returning group by with min values and corresponding values in an optimized manner? Currently we are using multiple queries for the same.
eg:
A table has product category, product name, product id and price. I want to return the product type with min(price) but also product name and product id as well. This is in SQL 2008. Product name and product id can be duplicate
Edit 1: Added input and output for clarity
The first note is that SQL is a declarative language; it declares a functional requirement, not how that requirement will be solved (the SQL is 'compiled' in to an explain/execution plan, based on stats, indexes, hints, etc, etc, and then that's what is executed.).
That said, some SQL expressions are more costly than others. Your case has a simple solution using analytic functions (aka windowed functions) that has a low cost due to scanning the table (or appropriate index) only once.
SELECT
*
FROM
(
SELECT
*,
RANK() OVER (PARTITION BY category ORDER BY price) AS category_price_rank
FROM
your_table
)
ranked_your_table
WHERE
category_price_rank = 1
SELECT * FROM (
SELECT ProductCatg, ProductName, ProductID, MIN(PRICE), DateOrdered
FROM YOUR_TABLE
GROUP BY ProductCatg, ProductName, ProductID, DateOrdered ) A
WHERE A.PRICE = (SELECT MIN(PRICE)
FROM YOUR_TABLE
WHERE ProductCatg = A.ProductCatg AND ProductName = A.ProductName )

SQL COUNT function to find commonality

The question I'm facing is as follows: list the first letters of the product names and their totals. Only display the letter and count if there are 3 or more products beginning with that letter of the alphabet.
Clearly the query requires the use of the COUNT aggregate, but I am spinning my wheels looking at this. How do I write this query?
expected output:
ProductName Total
C 9
G 11
ETC...
I'm assuming I need a SUBSTRING in my select statement
SELECT SUBSTRING(ProductName,1,1) AS ProductName,
COUNT(ProductName) AS Total
FROM Products
But using WHERE with a count function will give me an aggregate error is SQLServer2012?
You cannot use aggregate function's in where clause to filter the result
To filter the group's you have to use having clause instead of where clause. Try this
SELECT SUBSTRING(ProductName,1,1) AS ProductName,
COUNT(ProductName) AS Total
FROM Products
group by SUBSTRING(ProductName,1,1)
having COUNT(ProductName) >=3
You just need a proper group and having clause:
SELECT
LEFT(ProductName, 1) AS ProductName,
COUNT(*) AS [Total]
FROM Products
GROUP BY
LEFT(ProductName, 1)
HAVING COUNT(*) >= 3