How select all data using HAVING clause in WHERE condition? - sql

I have created a table which is keeps records of which product is sold by whom and how much each month;
month
total
product
cons_name
2020-01
10
xyz
123
2020-02
5
abc
456
2020-02
4
def
789
I was creating a query from this table to find out who has sold over 500 products on certain products since the beginning of the year, but I was a bit confused at the time of writing. Because i am not needed to query how much it sells during the year, but how much it sells each month that's i need to find. I can easily find more than 500 sales in total during a year with this query:
SELECT cons_name, product, SUM(total)
FROM TMP_REPORT
WHERE product IN ('abc','xyz')
GROUP BY cons_name, product
HAVING sum(total) > 500
But when it came to querying in detail I got this far:
SELECT
month,
product,
cons_name,
total
FROM TMP_REPORT
WHERE product IN ('abc','xyz')
AND cons_name IN
(SELECT
cons_name
FROM TMP_REPORT
WHERE
product IN ('abc','xyz')
GROUP BY
cons_name
HAVING sum(total) > 500)
The result of this query showed even the totals of sold product are not 500. For example, we would expect the cons_name named '123' to not be in the query result for only 200 sold 'abc' products in a year, but it does exist because of where clause. I knew my mistakes but I don't know that how to fix.
How can i do it?
Thanks for your help.

One approach uses SUM as an analytic function:
WITH cte AS (
SELECT t.*, SUM(total) OVER (PARTITION BY cons_name, product) sum_total
FROM TMP_REPORT t
WHERE product IN ('abc', 'xyz')
)
SELECT *
FROM cte
WHERE sum_total > 500;

I couldn't clearly understand you requirement, I assume you need a query to fetch the product that sold more than 500 unit on monthly basis. I hope below query will fetch the records you need.
-- YearlyReportCTE will Qualify the people who sold more than 500 units in total (i.e. yearly from your statement)
WITH YearlyReportCTE AS (
SELECT
product,
cons_name,
SUM(total) AS Total
FROM #TMP_REPORT
WHERE product in ('abc','xyz')
GROUP BY product,cons_name
HAVING SUM(total) > 500
)--This Query will fetch the month wise report from the qulified records
SELECT month,
TR.product,
TR.cons_name,
SUM(TR.total) AS Total
FROM #TMP_REPORT TR
JOIN YearlyReportCTE YR ON YR.cons_name = TR.cons_name
AND YR.product = TR.product
GROUP BY month,TR.product,TR.cons_name

Related

SQL SELECT data from table group by different data

Display pymtmode, and total number of payments for those payments which were paid before the year 2015 and total number of payments should be more than 1 from the data given:
ORDERID QUOTATIONID QTYORDERED ORDERDATE STATUS PYMTDATE DELIVEREDDATE AMOUNTPAID PYMTMODE
O1001 Q1002 100 30-OCT-14 Delivered 05-NOV-14 05-NOV-14 140000 Cash
O1003 Q1003 50 15-DEC-14 Delivered 18-DEC-14 20-DEC-14 310000 Cash
O1004 Q1006 100 15-DEC-14 Delivered 25-DEC-14 30-DEC-14 80000 Cheque
O1005 Q1002 50 30-JAN-15 Delivered 01-FEB-15 03-FEB-15 70000 Cheque
O1006 Q1008 75 20-FEB-15 Delivered 22-FEB-15 23-FEB-15 161250 Cash
I've tried the below code for fetching Year and select only values before the year 2015 and grouping by year.
SELECT pymtmode, COUNT(*) as pymtcount
FROM orders
GROUP BY to_char(pymtdate, 'Year')
HAVING to_char(pymtdate,'Year')<2015 AND count(*)>1
I've learnt that group by columns/functions should be mentioned in SELECT statement as well. But this question and it's expected result doesn't relate with it. Clarity with basic explanations would help
Expected Result
PYMTMODE PYMTCOUNT
Cash 2
Thanks!
Your expected result needs pymtmode to be selected, so you must GROUP BY pymtmode and not GROUP BY to_char(pymtdate, 'Year'), because you don't need to get results for each year, right?.
Also the condition to_char(pymtdate,'Year')<2015 might as well be put in a WHERE clause, so to restrict the rows before aggregation:
SELECT pymtmode, COUNT(*) as pymtcount
FROM orders
WHERE EXTRACT(YEAR FROM pymtmode) < 2015
GROUP BY pymtmode
HAVING count(*) > 1
I strongly recommend using direct date comparisons for this purpose:
SELECT o.pymtmode, COUNT(*) as pymtcount
FROM orders o
WHERE o.pymtdate < DATE '2015-01-01'
GROUP BY o.pymtmode
HAVING COUNT(*) > 1;
Notes:
You want to do the filtering before the aggregation, not afterwards. I think that is the source of your confusion.
A direct comparison to dates makes it easier for the optimizer to generate the best execution plan.
Learning how to use table aliases (the o) is a good habit.
select pymtmode, count(pymtmode) pymtcount from orders where extract(year from to_date(pymtdate))<2015 group by pymtmode having count(pymtmode)>1;

Calculate average between rows in SQL by using lag and ignore first row

I am trying to write a SQL query that calculate the average days from purchase to purchase for all customers who made two or more purchases:
Customer_ID | Average number of day
1033 | 175
11 | 334
1100 | 202.5
111 | 52.5
I succeeded to show all the purchase dates for all customers and calculate the days between purchase to purchase.
SELECT Customer_ID, Order_Date Cur,
LAG(Order_Date, 1) OVER (ORDER BY Customer_ID) AS Previous,
DATEDIFF(day, LAG(Order_Date, 1) OVER (ORDER BY Customer_ID), Order_Date)
[Days Between Purchases]
FROM Orders
How can I ignore the first row per customer and calculate averages between purchase to purchase?
(I have to use LAG in my answer
The simplest method is aggregation and some arithmetic:
SELECT CustomerId,
DATEDIFF(day, MIN(o.Order_Date), MAX(o.Order_date)) * 1.0 / NULLIF(COUNT(*) - 1, 0)
FROM Orders o
GROUP BY CustomerId
HAVING COUNT(*) >= 2;
In a sense, the "average days between orders" is a trick question. You think that you have to calculate the difference between each order and the next.
In fact, you just need to divide the total time from the first order to the last order by one less than the number of orders. I can let you work out why this works.
Your script is almost OK. Just add the customer ID relation to it. Other wise first row all eact customer will not be NULL.
SELECT
cur.Customer_ID,
cur.Order_Date Cur,
previous.Order_Date Previous,
DATEDIFF(day, previous.Order_Date, cur.Order_Date) [Days Between purchases]
FROM tblDifference cur
LEFT OUTER JOIN tblDifference previous
ON cur.RowNumber = previous.RowNumber+1
AND cur.customer_id = previous.customer_id;

How to show all holiday types with the code

For my homework, I have to write sql code to show "Among all the orders in 2015, calculate the number of days for each holidaytype, and the average sales per day for each holidaytype. Exclude holidaytype=NULL. Sort the results by the average sales per day from high to low."
This is the code that I have been trying to use
select distinct holidaytype, sum(AvgSales) as AvgSales, sum(NumDays) as NumDays
from(
Select min(holidaytype) as holidaytype, count(order_date) as cnt, count(numholidays) as NumDays, avg(o.sales*o.quantity) as AvgSales
from orderline o, orders1 o1, calendar c
where o.Order_ID=o1.Order_ID and datepart(yyyy,order_date)= 2015 and holidaytype is not null) J
group by holidaytype
go
In the output, only 1 holiday type is showing but I am supposed to have 6 or 7 different holiday types.
You are grouping by Holiday Type, so it doesn't make sense to aggregate it, which isn't what you want anyway, according to your question.
So instead of this:
Select min(holidaytype) as holidaytype,
You should do this:
Select holidaytype,

Grouping or Counting after a Sum

Using Oracle/SQL, I am looking for a way to count the number distinct IDs above or below a specified value following a sum. For example, I need to find how many customers have ordered $1,000 or more in goods last year, regardless of whether it was in one large order or several smaller orders. I don't need to identify each customer specifically, just find the total number of customers over this amount.
So far I am able to find the total of orders with:
select sum (Order_Amount), Customer_ID
from table.orders_placed
where year = 2013
group by Customer_ID
order by Customer_ID
I can also expand it doing this:
select count (dinstinct Customer_ID)
from(
select sum (Order_Amount), Customer_ID
from table.orders_placed
where year = 2013
group by Customer_ID
order by Customer_ID
)
but this just gives me the total number of distinct Customer_ID. Any other argument that I add to try to narrow what the "count" gives me results in an error. How can I specify that I want the total Order_Amount of $1,000 or more?
Try (no need to order by in inner query)
select count (dinstinct Customer_ID)
from(
select sum (Order_Amount) total_order_amount, Customer_ID
from table.orders_placed
where year = 2013
group by Customer_ID
) where total_order_amount > 1000
OR with Having
select sum (Order_Amount) total_order_amount, Customer_ID
from table.orders_placed
where year = 2013
group by Customer_ID
having sum(order_amount) > 1000
Use a HAVING clause to restrict the results of the grouping:
select sum (Order_Amount) as Total, Customer_ID
from table.orders_placed
where year = 2013
group by Customer_ID
having sum (Order_Amount) >= 1000
Then you can use this as a subquery to perform your aggregation and filtering.

Can I limit the amount of rows to be used for a group in a GROUP BY statement

I'm having an odd problem
I have a table with the columns product_id, sales and day
Not all products have sales every day. I'd like to get the average number of sales that each product had in the last 10 days where it had sales
Usually I'd get the average like this
SELECT product_id, AVG(sales)
FROM table
GROUP BY product_id
Is there a way to limit the amount of rows to be taken into consideration for each product?
I'm afraid it's not possible but I wanted to check if someone has an idea
Update to clarify:
Product may be sold on days 1,3,5,10,15,17,20.
Since I don't want to get an the average of all days but only the average of the days where the product did actually get sold doing something like
SELECT product_id, AVG(sales)
FROM table
WHERE day > '01/01/2009'
GROUP BY product_id
won't work
If you want the last 10 calendar day since products had a sale:
SELECT product_id, AVG(sales)
FROM table t
JOIN (
SELECT product_id, MAX(sales_date) as max_sales_date
FROM table
GROUP BY product_id
) t_max ON t.product_id = t_max.product_id
AND DATEDIFF(day, t.sales_date, t_max.max_sales_date) < 10
GROUP BY product_id;
The date difference is SQL server specific, you'd have to replace it with your server syntax for date difference functions.
To get the last 10 days when the product had any sale:
SELECT product_id, AVG(sales)
FROM (
SELECT product_id, sales, DENSE_RANK() OVER
(PARTITION BY product_id ORDER BY sales_date DESC) AS rn
FROM Table
) As t_rn
WHERE rn <= 10
GROUP BY product_id;
This asumes sales_date is a date, not a datetime. You'd have to extract the date part if the field is datetime.
And finaly a windowing function free version:
SELECT product_id, AVG(sales)
FROM Table t
WHERE sales_date IN (
SELECT TOP(10) sales_date
FROM Table s
WHERE t.product_id = s.product_id
ORDER BY sales_date DESC)
GROUP BY product_id;
Again, sales_date is asumed to be date, not datetime. Use other limiting syntax if TOP is not suported by your server.
Give this a whirl. The sub-query selects the last ten days of a product where there was a sale, the outer query does the aggregation.
SELECT t1.product_id, SUM(t1.sales) / COUNT(t1.*)
FROM table t1
INNER JOIN (
SELECT TOP 10 day, Product_ID
FROM table t2
WHERE (t2.product_ID=t1.Product_ID)
ORDER BY DAY DESC
)
ON (t2.day=t1.day)
GROUP BY t1.product_id
BTW: This approach uses a correlated subquery, which may not be very performant, but it should work in theory.
I'm not sure if I get it right but If you'd like to get the average of sales for last 10 days for you products you can do as follows :
SELECT Product_Id,Sum(Sales)/Count(*) FROM (SELECT ProductId,Sales FROM Table WHERE SaleDAte>=#Date) table GROUP BY Product_id HAVING Count(*)>0
OR You can use AVG Aggregate function which is easier :
SELECT Product_Id,AVG(Sales) FROM (SELECT ProductId,Sales FROM Table WHERE SaleDAte>=#Date) table GROUP BY Product_id
Updated
Now I got what you meant ,As far as I know it is not possible to do this in one query.It could be possible if we could do something like this(Northwind database):
select a.CustomerId,count(a.OrderId)
from Orders a INNER JOIN(SELECT CustomerId,OrderDate FROM Orders Order By OrderDate) AS b ON a.CustomerId=b.CustomerId GROUP BY a.CustomerId Having count(a.OrderId)<10
but you can't use order by in subqueries unless you use TOP which is not suitable for this case.But maybe you can do it as follows:
SELECT PorductId,Sales INTO #temp FROM table Order By Day
select a.ProductId,Sum(a.Sales) /Count(a.Sales)
from table a INNER JOIN #temp AS b ON a.ProductId=b.ProductId GROUP BY a.ProductId Having count(a.Sales)<=10
If this is a table of sales transactions, then there should not be any rows in there for days on which there were no Sales. I.e., If ProductId 21 had no sales on 1 June, then this table should not have any rows with productId = 21 and day = '1 June'... Therefore you should not have to filter anything out - there should not be anything to filter out
Select ProductId, Avg(Sales) AvgSales
From Table
Group By ProductId
should work fine. So if it's not, then you have not explained the problem completely or accurately.
Also, in yr question, you show Avg(Sales) in the example SQL query but then in the text you mention "average number of sales that each product ... " Do you want the average sales amount, or the average count of sales transactions? And do you want this average by Product alone (i.e., one output value reported for each product) or do you want the average per product per day ?
If you want the average per product alone, for just thpse sales in the ten days prior to now? or the ten days prior to the date of the last sale for each product?
If the latter then
Select ProductId, Avg(Sales) AvgSales
From Table T
Where day > (Select Max(Day) - 10
From Table
Where ProductId = T.ProductID)
Group By ProductId
If you want the average per product alone, for just those sales in the ten days with sales prior to the date of the last sale for each product, then
Select ProductId, Avg(Sales) AvgSales
From Table T
Where (Select Count(Distinct day) From Table
Where ProductId = T.ProductID
And Day > T.Day) <= 10
Group By ProductId