Multiple counts with different conditions - sql

I want to retrieve and display on one row, the number of sales made by an employee followed by the total number of sales.
SELECT COUNT(SalesID) AS SalesForEmployee, COUNT(SalesID) AS TotalSales
FROM Sales
WHERE EmployeeID = 123
How do I make it so that the where clause only applies to the first column in the select?

SELECT
sum(case when EmployeeID = 123 then 1 else 0 end) AS SalesForEmployee
,COUNT(SalesID) AS TotalSales
FROM Sales

select count(SalesEmp.SalesID) AS SalesForEmployee count(Sales.salesID) As TotalSales
from Sales left outer join Sales as SalesEmp
on Sales.salesID=SalesEmp.SalesID
and SalesEmp.EmployeeID = 123

You can't have a where that only applies to one column.
In order to get both counts while only scanning the table once, you can do this:
select
sum(case when EmployeeID=123 then 1 else 0 end) as SalesForEmployee,
Count(SalesID) as TotalSales
from Sales
There's no where clause because Count(SalesID) needs to count every row to give you the total count.
Since you have to look at every row, case when EmployeeID=123 then 1 else 0 end gives you a 1 for each row that belongs to the target employee and a 0 for every row that doesn't. Therefore, summing that expression gives you the count only for that employee.
SalesID EmployeeID (case when ... )
1 123 1
2 311 0
3 333 0
4 123 1
5 300 0
count = 5 sum = 2
You could also do it like this:
select
(select count(SalesID) from Sales where EmployeeID=123) as SalesForEmployee,
(select count(SalesID) from Sales) as TotalSales
But now you are scanning the Sales table twice, which will be slower.

This is a nested subquery approach combining two select statements into one.
SELECT
(SELECT COUNT(SalesID) FROM Sales WHERE EmployeeID = 123) AS SalesForEmployee,
(SELECT COUNT(SalesID) FROM Sales) AS TotalSales
Another way to write it would be like this.
SELECT COUNT(SalesID) AS SalesForEmployee,
(SELECT COUNT(SalesID) FROM Sales) AS TotalSales
FROM Sales WHERE EmployeeID = 123
With a correlated subquery, you can link the outer query with the inner query. Say you wanted to get the Total sales not including the sales of EmployeeID 123
SELECT COUNT(SalesEmployee.SalesID) AS SalesForEmployee,
(SELECT COUNT(SalesID) FROM Sales WHERE Sales.EmplyeeID <> SalesEmployee.EmployeeID) AS TotalSales
FROM Sales As SalesEmployee WHERE SalesEmployee.EmployeeID = 123
Here the inner query is referencing the outqueries EmployeeeID in the WHERE clause to filter them out.

Related

SQL Query to get sums among multiple payments which are greater than or less than 10k

I am trying to write a query to get sums of payments from accounts for a month. I have been able to get it for the most part but I have hit a road block. My challenge is that I need a count of the amount of payments that are either < 10000 or => 10000. The business rules are that a single payment may not exceed 10000 but there can be multiple payments made that can total more than 10000. As a simple mock database it might look like
ID | AccountNo | Payment
1 | 1 | 5000
2 | 1 | 6000
3 | 2 | 5000
4 | 3 | 9000
5 | 3 | 5000
So the results I would expect would be something like
NumberOfPaymentsBelow10K | NumberOfPayments10K+
1 | 2
I would like to avoid doing a function or stored procedure and would prefer a sub query.
Any help with this query would be greatly appreciated!
I suggest avoiding sub-queries as much as possible because it hits the performance, specially if you have a huge amount of data, so, you can use something like Common Table Expression instead. You can do the same by using:
;WITH CTE
AS
(
SELECT AccountNo, SUM(Payment) AS TotalPayment
FROM Payments
GROUP BY AccountNo
)
SELECT
SUM(CASE WHEN TotalPayment < 10000 THEN 1 ELSE 0 END) AS 'NumberOfPaymentsBelow10K',
SUM(CASE WHEN TotalPayment >= 10000 THEN 1 ELSE 0 END) AS 'NumberOfPayments10K+'
FROM CTE
You can get the totals per account using SUM and GROUP BY...
SELECT AccountNo, SUM(Payment) AS TotPay
FROM payments
GROUP BY AccountNo
You can use that result to count the number over 10000
SELECT COUNT(*)
FROM (
SELECT AccountNo, SUM(Payment) AS TotPay
FROM payments
GROUP BY AccountNo
)
WHERE TotPay>10000
You can get the the number over and the number under in a single query if you want but that's a but more complicated:
SELECT
COUNT(CASE WHEN TotPay<=10000 THEN 1 END) AS Below10K,
COUNT(CASE WHEN TotPay> 10000 THEN 1 END) AS Above10K
FROM (
SELECT AccountNo, SUM(Payment) AS TotPay
FROM payments
GROUP BY AccountNo
)

SUM of multi (relate to another table) rows record in another table

I have two tables an tblOrder and tblOrderDetail Table.
tblOrderDetail contain below rows:
OrderDetailID OrderID Product Quantity UnitPrice Discount Total
1 1 ABC 10 $240.00 10 $2,160.00
2 2 CDF 100 $200.00 10 $18,000.00
3 3 GHI 200 $150.00 0 $30,000.00
4 1 XYZ 40 $100.00 5 $3,800.00
i want sql query to get Subtotal column in tblOrder, which are sum of relate OrderID Total from tblOrderDetail like this:
OrderID Sub Total
1 $5,960.00
2 $18,000.00
3 $30,000.00
i try this sql query:
SELECT
OrderID
,(
SELECT
SUM(((tblOrderDetail.UnitPrice) - (tblOrderDetail.UnitPrice * (tblOrderDetail.Discount / 100))) * (tblOrderDetail.Quantity))
FROM
tblOrderDetail
WHERE tblOrderDetail.OrderID = tblOrder.OrderID
) AS [Sub Total]
FROM
tblOrder
but it gives this
OrderID Sub Total
1 $0.00
2 $0.00
3 $0.00
Note i want Sub Total column dynamically not by Sum of Total Column in tblOrderDetail Table.
I hope somebody can make sense of what I'm saying and hopefully help me achieve this!
Use Group by Clause to with SUM aggregate function,
Select *, sum(Total) as totalforOrder from ordertbl group by OrderID;
Demo SQLFiddle
as per your question, here is a correlated query
SELECT
tblOrder.OrderID
,(
SELECT
SUM(((tblOrderDetail.UnitPrice) - (tblOrderDetail.UnitPrice * (tblOrderDetail.Discount / 100))) * (tblOrderDetail.Quantity))
FROM
tblOrderDetail
WHERE tblOrderDetail.OrderID = tblOrder.OrderID
) AS SubTotal
FROM
tblOrder;
Its working fine on SQLFiddle
you can use Group by to do this
Select OrderID, sum(Total) as [Sub Total] from tblOrderDetail group by OrderID;
You can get more details here !

Firebird Query- Return first row each group

In a firebird database with a table "Sales", I need to select the first sale of all customers. See below a sample that show the table and desired result of query.
---------------------------------------
SALES
---------------------------------------
ID CUSTOMERID DTHRSALE
1 25 01/04/16 09:32
2 30 02/04/16 11:22
3 25 05/04/16 08:10
4 31 07/03/16 10:22
5 22 01/02/16 12:30
6 22 10/01/16 08:45
Result: only first sale, based on sale date.
ID CUSTOMERID DTHRSALE
1 25 01/04/16 09:32
2 30 02/04/16 11:22
4 31 07/03/16 10:22
6 22 10/01/16 08:45
I've already tested following code "Select first row in each GROUP BY group?", but it did not work.
In Firebird 2.5 you can do this with the following query; this is a minor modification of the second part of the accepted answer of the question you linked to tailored to your schema and requirements:
select x.id,
x.customerid,
x.dthrsale
from sales x
join (select customerid,
min(dthrsale) as first_sale
from sales
group by customerid) p on p.customerid = x.customerid
and p.first_sale = x.dthrsale
order by x.id
The order by is not necessary, I just added it to make it give the order as shown in your question.
With Firebird 3 you can use the window function ROW_NUMBER which is also described in the linked answer. The linked answer incorrectly said the first solution would work on Firebird 2.1 and higher. I have now edited it.
Search for the sales with no earlier sales:
SELECT S1.*
FROM SALES S1
LEFT JOIN SALES S2 ON S2.CUSTOMERID = S1.CUSTOMERID AND S2.DTHRSALE < S1.DTHRSALE
WHERE S2.ID IS NULL
Define an index over (customerid, dthrsale) to make it fast.
in Firebird 3 , get first row foreach customer by min sales_date :
SELECT id, customer_id, total, sales_date
FROM (
SELECT id, customer_id, total, sales_date
, row_number() OVER(PARTITION BY customer_id ORDER BY sales_date ASC ) AS rn
FROM SALES
) sub
WHERE rn = 1;
İf you want to get other related columns, This is where your self-answer fails.
select customer_id , min(sales_date)
, id, total --what about other colums
from SALES
group by customer_id
So simple as:
select CUSTOMERID min(DTHRSALE) from SALES group by CUSTOMERID

Multiple GROUP BY?

I'm hoping to be able to write a SQL query that can use multiple COUNTs based upon different criteria and have the values grouped together.
Let's say I have a (canned) scenario where I change the price-points of my products and want to analyze what people have paid.
SELECT Product, COUNT(*) as Total FROM Orders WHERE Location = 'Amazon'
SELECT Product, COUNT(*) as HighPriceCount FROM Orders
WHERE Location = 'Amazon' and PRICE > 10
From here, I'd like to be able to see results like this.
--------------------------------------------
| Product | Total | HighPriceCount | Avg |
--------------------------------------------
| Game 1 | 50 | 20 | .40 |
| Prod 2 | 300 | 200 | .66 |
--------------------------------------------
Where Avg. is the "price above 10" / "total sold". My initial approach is to group by Product but I wanted to see if an "inner-select" is the only path or whether there is a more elegant way to do this. Seems like a lot of duplication? Here's my initial version of a query.
-- I don't know if this works?
SELECT Product, COUNT(*) AS Total,
(
SELECT Product, COUNT(*) FROM Orders WHERE Location = 'Amazon' and Price > 10
GROUP BY Product
) AS HighPriceCount,
(Total / HighPriceCount) AS Avg
From Orders
WHERE Location = 'Amazon'
GROUP BY Product
To get the count use a CASE for HighPriceCount as below. Aggregate functions do not count null except for COUNT(*)
Sql-Fiddle Example
SELECT Product, COUNT(*) as Total,
COUNT(case when price > 10 then 1 end) as HighPriceCount,
SUM(case when price > 10 then price end)/COUNT(*) as Avg
FROM Orders
WHERE Location = 'Amazon'
GROUP BY Product
Did you try using SUM and CASE? (can't try it right know I think it should work)
SELECT PRODUCT,
SUM(CASE WHEN PRICE>10 THEN 1 ELSE 0 END) as highpricecount,
COUNT(CASE WHEN PRICE>10 THEN 1 END) as total
FROM Orders
WHERE LOCATION='AMAZON'
GROUP BY PRODUCT;
Here's mine...
Select Product, Total, HighPriceCount, HighPriceCount/Total As Avg
From (Select Product,
Sum(Case When Location = 'Amazon' Then 1 Else 0 End) As Total,
Sum(Case When Location = 'Amazon' And Price > 10 Then 1 Else 0 End) As HighPriceCount,
From Orders
Group By Product) o
Here is mine
Select
Product, Sum(Total) as Total, Sum(HighPriceCount) as HighPriceCount,Total/HighPriceCount as AVG
from
(
select Product, 1 as Total, case WHEN PRICE>10 THEN 1 ELSE 0 END as HighPriceCount
from Orders where LOCATION='AMAZON'
)
group by Product
Your query doesn't work. Please try this one:
select A.Product, A.Total, ISNULL(B.HighPriceCount,0), B.HighPriceCount*1.0/A.Total*1.0 as [Avg]
(select Product, count(*) as Total
from Orders
where Location='Amazon'
group by Product ) A
left join
(SELECT Product, COUNT(*) as HighPriceCount
from Orders
where Location = 'Amazon' and Price > 10
group by Product) B
on A.Product = B.Product
I don't have your database, so maybe there is typo, I can't test this query, but with some change by you, it should work.

Get max of column using sum

I have one table with following data..
saleId amount date
-------------------------
1 2000 10/10/2012
2 3000 12/10/2012
3 2000 11/12/2012
2 3000 12/10/2012
1 4000 11/10/2012
4 6000 10/10/2012
From my table I want result with max of sum amount between dates 10/10/2012 and 12/10/2012 which for the data above will be:
saleId amount
---------------
1 6000
2 6000
4 6000
Here 6000 is the max of the sums (by saleId) so I want ids 1, 2 and 4.
You have to use Sub-queries like this:
SELECT saleId , SUM(amount) AS Amount
FROM Table1
GROUP BY saleId
HAVING SUM(amount) =
(
SELECT MAX(AMOUNT) FROM
(
SELECT SUM(amount) AS AMOUNT FROM Table1
WHERE date BETWEEN '10/10/2012' AND '12/10/2012'
GROUP BY saleId
) AS A
)
See this SQLFiddle
This query goes through the table only once and is fairly optimised.
select top(1) with ties saleid, amount
from (
select saleid, sum(amount) amount
from tbl
where date between '20121010' and '20121210'
group by saleid
) x
order by amount desc;
You can produce the SUM with the WHERE clause as a derived table, then SELECT TOP(1) in the query using WITH TIES to show all the ones with the same (MAX) amount.
When presenting dates to SQL Server, try to always use the format YYYYMMDD for robustness.