Compute the average of count result - sql

I am trying to find the average number of products/store. I want something like this:
Store Avg.Products
Store 1 100
Store 2 20
Store table: StoreId
Products table: Productid, Pname, StoreId
If there are 100 total products and by using count i got 9 products for store 1 i want 9/100 for store 1 and similarly for the other stores
I tried:
Select avg(counts) from(Select count(*) AS counts
FROM Store Join Product ON Store.Id = product.StoreId Group By Store.id)table1
But this just gives me a single value. Any ideas? Thanks

You can try to group the outer query by store name and cast AVG parameter to float to allow it to return non-integer values:
SELECT storeName, avg(CAST(counts AS float)) FROM
(Select count(*) AS counts, S.Name as storeName
FROM Store S JOIN Product P ON S.Id = P.StoreId Group By S.id, S.Name) table1
GROUP BY storeName
It may be not the best way to do this, but it should return the expected results.
UPDATE
I've verified the answer and updated the code with a fixed version. Let me know if it works for Your case.
UPDATE II
According to Your comment, You would like to calculate the number of products for each shop name and then divide it by total number of products in the database.
If this has to be one query, You can do it like this:
SELECT S.Name as storeName, CAST(count(*) AS float) / MIN(T.TotalCount) AS AVGToTotalProductsCount
FROM Store S JOIN Product P ON S.Id = P.StoreId
JOIN (SELECT COUNT(1) AS TotalCount FROM Product) T ON 1=1
GROUP BY S.Name
However, if the query can be split into multiple queries, You may be able to calculate it like this:
DECLARE #totalNumOfProducts INT
SELECT #totalNumOfProducts = COUNT(1) FROM Product
SELECT S.Name as storeName, CAST(COUNT(*) AS float) / #totalNumOfProducts AS AVGToTotalProductsCount
FROM Store S JOIN Product P ON S.Id = P.StoreId
GROUP BY S.Name

You're on the right track. In your derived table, get a COUNT(ProductID) and GROUP BY StoreID. Then JOIN that back to your main query on StoreID, and do the AVG aggregation:
SELECT S.Name, AVG(ProductCount)
FROM Stores S
INNER JOIN (SELECT COUNT(ProductID) as ProductCount, StoreID
FROM Stores S
GROUP BY StoreID) P on S.ID = P.StoreID
GROUP BY S.Name

You are making it too hard. Just count the products that result from the join.
select s.StoreID, s.Name, count(p.ProductID) ProductCount
from store s
join Product p on p.StoreID=s.StoreID
group by s.StoreID, s.Name
order by count(p.ProductID);

Related

Invalid in the select list because it is not contained in either an aggregate function (with union and rollup)

i have query like below
select COALESCE(Funding_Type, 'Total') as Funding, nama as nama1, sum(total) as Revenue
from (select ('NDIS') as Funding_Type, business_name as nama, sum(total_amount) as total
from invoices a inner join
businesses b
on a.business_id=b.id and invoice_to like '%NDIS%'
union ALL
select ('CHSP') as Funding_Type, business_name as nama, sum(total_amount) as total
from invoices a inner join
businesses b
on a.business_id=b.id and invoice_to like '%CHSP%') x
GROUP by ROLLUP (Funding_Type, nama);
And get results like this
Column 'businesses.business_name' is invalid in the select list because it is not contained in either
an aggregate function or the GROUP BY clause.
Does anyone know how to fix it?
Let me assume that your groups are non-overlapping. I assume this is the case; otherwise, you are double counting revenue which seems like a bad thing.
With this assumption, you can use a single case expression for the aggregation:
select coalesce(Funding_Type, 'Total') as Funding,
nama as nama1, sum(total) as Revenue
from (select (case when invoice_to like '%NDIS%' then 'NDIS'
when invoice_to like '%CHSP%' then 'CHSP'
end) as Funding_Type,
business_name as nama, total_amount as total
from invoices i inner join
businesses b
on i.business_id = b.id
) ib
where funding_type is not null
group by rollup (Funding_Type, nama);
You are not grouping by business_name in the inner queries so you cannot select it without some kind of aggregation. Incidentally, your inner group by is unnecessary, so we can just remove it:
select
COALESCE(Funding_Type, 'Total') as Funding,
nama as nama1,
sum(total) as Revenue
from (
select 'NDIS' as Funding_Type, business_name as nama, total_amount as total
from invoices a
inner join businesses b on a.business_id=b.id
and invoice_to like '%NDIS%'
union ALL
select 'CHSP', business_name, total_amount
from invoices a
inner join businesses b on a.business_id=b.id
and invoice_to like '%CHSP%'
) x
GROUP by ROLLUP (Funding_Type, nama);
You probably want a COALESCE on nama also, for the subtotal.
EDIT
If you want to have multiple checks on invoice_to, you can simplify the whole query like this:
select
COALESCE(Funding_Type, 'Total') as Funding,
business_name as nama1,
sum(total) as Revenue
from invoices a
inner join businesses b on a.business_id=b.id
join (values -- you could also use a table variable or TVP here
('NDIS'),
('CHSP')
) v (Funding_Type) on b.invoice_to like '%' + v.Funding_Type + '%'
GROUP by ROLLUP (v.Funding_Type, business_name);
Note that you may get doubled up rows if a row matches more than one Funding_Type.

SQL: How to group by with two tables?

I have the tables products and history and I need to group by name:
products = (id_product, name)
history = (id_history, id_product, amount)
I tried this SQL query but it isn't grouped by name:
SELECT
products.name,
sum(history.amount)
FROM history
INNER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name,
history.amount,
history.id_history;
This is the result:
You should only be grouping by the attributes you need to be aggregated. In this case, you need only products.name.
SELECT
products.name,
sum(history.amount) AS [Amount]
FROM history
INNER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name;
If you need to include products without history (assuming sum should be 0 instead of null in this case), then you can use an OUTER JOIN instead of INNER JOIN to include all products:
SELECT
products.name,
COALESCE(sum(history.amount), 0) AS [Amount]
FROM history
RIGHT OUTER JOIN products ON history.id_product = products.id_product
GROUP BY
products.name;
This is no answer, but too long for a comment.
For readability's sake the product table should be first. After all it is products that we select from, plus a history sum that we can access via [left] join history ... followed by an aggregation, or [left] join (<history aggregation query>), or a subselect in the select clause.
Another step to enhance readability is the use of alias names.
Join the table, then aggregate
select p.name, coalesce(sum(h.amount), 0) as total
from products p
left join history h on h.id_product = p.id_product
group by p.name
order by p.name;
Aggregate, then join
select p.name, coalesce(h.sum_amount, 0) as total
from products p
left join
(
select sum(h.amount) as sum_amount
from history
group by id_product
) h on h.id_product = p.id_product
order by p.name;
Get the sum in the select clause
select
name,
(select sum(amount) from history h where h.id_product = p.id_product) as total
from products p
order by p.name;
And as you were confused on how to use GROUP BY, here is an explanation: GROUP BY ___ means you want one result row per ___. In your original query you had GROUP BY products.name, history.amount, history.id_history saying you wanted one result row per name, amount, and id, while you actually wanted one row per name only, i.e. GROUP BY products.name.

Sql query to display records that appear more than once in a table

I have two tables, Customer with columns CustomerID, FirstName, Address and Purchases with columns PurchaseID, Qty, CustomersID.
I want to create a query that will display FirstName(s) that have bought more than two products, product quantity is represented by Qty.
I can't seem to figure this out - I've just started with T-SQL
You could sum the purchases and use a having clause to filter those you're interested in. You can then use the in operator to query only the customer names that fit these IDs:
SELECT FirstName
FROM Customer
WHERE CustomerID IN (SELECT CustomerID
FROM Purchases
GROUP BY CustomerID
HAVING SUM(Qty) > 2)
Please try this, it should work for you, according to your question.
Select MIN(C.FirstName) FirstName from Customer C INNER JOIN Purchases P ON C.CustomerID=P.CustomersID Group by P.CustomersID Having SUM(P.Qty) >2
Please try this:
select c.FirstName,p.Qty
from Customer as c
join Purchase as p
on c.CustomerID = p.CustomerID
where CustomerID in (select CustomerID from Purchases group by CustomerID having count(CustomerID)>2);
SELECT
c.FirstName
FROM
Customer c
INNER JOIN Purchases p
ON c.CustomerId = p.CustomerId
GROUP BY
c.FirstName
HAVING
SUM(p.Qty) > 2
While the IN suggestions would work they are kind of overkill and more than likely less performant than a straight up join with aggregation. The trick is the HAVING Clause by using it you can limit your result to the names you want. Here is a link to learn more about IN vs. Exists vs JOIN (NOT IN vs NOT EXISTS)
There are dozens of ways of doing this and to introduce you to Window Functions and common table expressions which are way over kill for this simplified example but are invaluable in your toolset as your queries continue to get more complex:
;WITH cte AS (
SELECT DISTINCT
c.FirstName
,SUM(p.Qty) OVER (PARTITION BY c.CustomerId) as SumOfQty
FROM
Customer c
INNER JOIN Purchases p
ON c.CustomerId = p.CustomerId
)
SELECT *
FROM
cte
WHERE
SumOfQty > 2

SQL nested aggregate grouping error

I'm trying to find name of product which has sold maximum units, I've two tables, purchases and products, products has pname and pid, purchases has pid, qty(units sold).
I've managed this
select p.pname, sum(q.qty) from purchases q
inner join products p on p.pid=q.pid
where p.pid=q.pid
group by p.pname
order by sum(q.qty) desc
I'm getting the result in descending order but I need only the top most selling units, multiple products can have top most selling units. When I use
max(sum(q.qty))
I get grouping error.
One approach is to derive the values first using a common table expression.
Simply put you can't wrap aggregates in other aggregates. You may be able to wrap an aggregate around an analytic however.
with cte as (select p.pname, sum(q.qty) from purchases q
inner join products p on p.pid=q.pid
where p.pid=q.pid
group by p.pname
order by sum(q.qty) desc)
Select pname, max(purchases)
from cte
group by pname
You can use ctes to do this.
1)First get the total quantity of each product
2)Then get the maximum of all those totals
3)Join it with your original query
with totals as (select pid, sum(qty) totalqty from purchases group by pid)
, t1 as (select p.pid, p.pname, sum(q.qty) totqty
from purchases q
inner join products p on p.pid=q.pid
group by p.pname)
, t2 as (select max(totalqty) maxtotal from totals)
select pname, totqty
from t1
join t2 on t1.totqty = t2.maxtotal
Analytics can simplify this for you. If you have more than one product with the same sum(qty) and that happens to be the max(sum(qty)), then this should get you them:
select pname, quantity
FROM (
select p.pname
, sum(q.qty) quantity
,rank() over (order by sum(q.qty desc) ranking
from purchases q
inner join products p on p.pid=q.pid
group by p.pname
)
where ranking = 1

Join two tables but only get most recent associated record

I am having a hard time constructing an sql query that gets all the associated data with respect to another (associated) table and loops over into that set of data on which are considered as latest (or most recent).
The image below describes my two tables (Inventory and Sales), the Inventory table contains all the item and the Sales table contains all the transaction records. The Inventory.Id is related to Sales.Inventory_Id. And the Wanted result is the output that I am trying to work on to.
My objective is to associate all the sales record with respect to inventory but only get the most recent transaction for each item.
Using a plain join (left, right or inner) doesn't produce the result that I am looking into for I don't know how to add another category in which you can filter the most recent data to join to. Is this doable or should I change my table schema?
Thanks.
You can use APPLY
Select Item,Sales.Price
From Inventory I
Cross Apply(Select top 1 Price
From Sales S
Where I.id = S.Inventory_Id
Order By Date Desc) as Sales
WITH Sales_Latest AS (
SELECT *,
MAX(Date) OVER(PARTITION BY Inventory_Id) Latest_Date
FROM Sales
)
SELECT i.Item, s.Price
FROM Inventory i
INNER JOIN Sales_Latest s ON (i.Id = s.Inventory_Id)
WHERE s.Date = s.Latest_Date
Think carefully about what results you expect if there are two prices in Sales for the same date.
I would just use a correlated subquery:
select Item, Price
from Inventory i
inner join Sales s
on i.id = s.Inventory_Id
and s.Date = (select max(Date) from Sales where Inventory_Id = i.id)
select * from
(
select i.name,
row_number() over (partition by i.id order by s.date desc) as rownum,
s.price,
s.date
from inventory i
left join sales s on i.id = s.inventory_id
) tmp
where rownum = 1
SQLFiddle demo