How to compare and find the highest average number of a value occurance in SQL for specific months? - sql

I am fairly new to SQL and need help to find which customer has the highest average number of event occurring by month in the years (2019 - 2020) and which is the top 3 busy month every year?
Please note one customer can have multiple event_IDs.
Table Snippet:
event_ID
cust_ID
event_datetime
abc123
cus11
2019-03-13T00:00:00
abc124
cus12
2020-05-23T02:34:35
abc125
cus457
2018-12-12T22:12:23
abc126
cus11
2017-01-07T13:54:56
abc127
cus7897
2021-07-11T04:43:23
I need to find the customers having the highest average number of events in the respective month( cust with highest number of events on avg in 2019, 2020, 2021).
month
cust_ID
avg_num
1
cus11
9345.8
2
cus4563
11898.5
I have tried using CTE and window functions but couldn't figure out the logic to get to the result, any help is appreciated!
EDIT:
For clarification, my example is just a snippet of the table, the actual table has more than a few million rows, I need the customer who has the maximum number of events occurring on average over month 1,2,3,.. and avg_num will have the average value of the number of event for the cust_ID with max number of event-[ (num of event in 2019 + 2020 + 2021)/3 ]

Maybe something like this:
SELECT cust_id
, COUNT(*) AS number_of_events
, EXTRACT(YEAR FROM MAX(event_datetime)) last_year_for_cust_id
, EXTRACT(YEAR FROM MIN(event_datetime)) first_year_for_cust_id
, CAST(COUNT(*) AS DECIMAL) / (EXTRACT(YEAR FROM MAX(event_datetime)) - EXTRACT(YEAR FROM MIN(event_datetime)) )
FROM table_name
GROUP BY cust_id
ORDER BY cust_id; -- or something else
I didn't test it, no idea what brand of database you use and no DB<>fiddle was available.

Related

How to list records with conditional values and non-missing records

I have a view that produces the result shown in the image below. I need help with the logic.
Requirement:
List of all employees who achieved no less than 100% target in ALL Quarters in past two years.
"B" received 90% in two different quarters. An employee who received less than 100% should NOT be listed.
Notice that "A" didn't work for Q2-2016. An employee who didn't work for that quarter should NOT be listed.
"C" is the only one who worked full two years, and received 100% in each quarter.
Edit: added image link showing Employee name,Quarter, Year, and the score.
https://i.imgur.com/FIXR0YF.png
The logic is pretty easy, it's math with quarters that is a bit of a pain.
There are 8 quarters in the last two years, so you simply need to select all the employee names in the last two years with a target >= 100%, group by employee name, and apply a HAVING clause to limit the output to those employees with count(*) = 8.
To get the current year and quarter, you can use these expressions:
cast(extract('year' from current_date) as integer) as yr,
(cast(extract('month' from current_date) as integer)-1) / 3 + 1 as quarter;
Subtract 2 from the current year to find the previous year and quarter. The code will be clearer if you put these expressions in a subquery because you will need them multiple times for the quarter arithmetic. To do the quarter arithmetic you must extract the integer value of the quarter from the text values you have stored.
Altogether, the solution should look something like this:
select
employee
from
(select employee, cast(right(quarter,1) as integer) as qtr, year
from your_table
where target >= 100
) as tgt
cross join (
select
cast(extract('year' from current_date) as integer) as yr,
(cast(extract('month' from current_date) as integer)-1) / 3 + 1 as quarter
) as qtr
where
tgt.year between qtr.yr-1 and qtr.yr
or (tgt.year = qtr.yr - 2 and tgt.qtr > qtr.quarter)
group by
employee
having
count(*) = 8;
This is untested.
If you happen to be using Postgres and expect to be doing a lot of quarter arithmetic you may want to define a custom data type as described in A Year and Quarter Data Type for PostgreSQL

SQL Server : list months between range

I am trying to calculate the values of financial hedges but I want the input to be as simple as possible. So for example, I have a contract with a quantity of 1,000 per month from 2/1/2017 to 12/31/2018 (it won't always be this date range, it could be 1 month or 3+ years) with a strike price of $3. I want to enter just 1 row of data into 4 columns: [Volume],[Start],[End],[Strike].
The issue is I need to multiply the 2017 data by one price and the 2018 by a different price. The easy answer would be to enter a row for 2017 and a row for 2018 but I don't want to do this because I may have 50 or more contracts to enter in and I want to do as little input as possible.
I would use two columns [Year],[Price] from a price table. It would look like [2017],[4.50] and [2018],[5.25]. I can easily modify this to be monthly instead of annual if it helps simplify things.
I need the final calculation to be along the lines of:
2017: 11,000 * (3 - 4.50) = -$16,500
2018: 12,000 * (3 - 5.25) = -$27,000
Total value = -$43,500
So my question is, how can I get a count of months for each year in the range?
I would like the output to be something like
2017, 11
2018, 12
Calendar tables can be helpful in situations like this, there are many sample scripts out there, here is one: https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
You could also simply use year and month in your price table and join on the range, an Integer type Year_Month_Int field could be used '201601, 201602, 201603...`
Then:
SELECT *
FROM contracts c
JOIN prices p
ON p.Year_Month_Int BETWEEN (YEAR(c.start)*100)+MONTH(c.start) AND (YEAR(c.end)*100)+MONTH(c.end)
If you included the integer version year-month for start and end in the contracts table you could simplify the JOIN criteria.
Another variant on how to count the number of months in a range would be:
Setup:
create table soTest(
id integer not null auto_increment,
start_date datetime,
end_date datetime,
primary key (id)
);
insert into soTest (start_date, end_date) values
('2017-02-01 00:00:00','2018-12-31 00:00:00');
Query:
select distinct case when extract(year from start_date) = yr
then format(datediff(concat(yr,'-12-31'), start_date)/30,0)
else format(datediff(end_date, concat(yr,'-01-01'))/30,0) end as qtyMonths,
yr
from (select s.*, extract(year from start_date) yr from soTest s
union all
select s.*, extract(year from end_date) yr from soTest s
) a;
Result:
qtyMonths yr
11 2017
12 2018
Be aware that this technique will only count the months in case of one registry. To get data from your contracts you would have to JOIN this with your dataset tables.

How to create a dynamic where clause in sql?

So I have created a table that has the following columns from a transaction table with all customer purchase records:
1. Month-Year, 2.Customer ID, 3. Number of Transactions in that month.
I'm trying to create a table that has the output of
1. Month-Year, 2. Number of active customers defined by having at least 1 purchase in the previous year.
The code that I have currently is this but the case when obviously only capturing one date and the where clause isn't dynamic. Would really appreciate your help.
select month_start_date, cust_ID,
(case when month_start_Date between date and add_months(date, -12) then count(cust_ID) else 0 end) as active
from myserver.mytable
where
month_start_Date>add_months(month_start_date,-12)
group by 1,2
EDIT: I'm just trying to put a flag next to a customer if they are active in each month defined as having at least one transaction in the last year thanks!
You might use Teradata's proprietary EXPAND ON synax for creating time series:
SELECT month_start_date, COUNT(*)
FROM
( -- create one row for every month within the next year
-- after a customer's transaction
SELECT DISTINCT
BEGIN(pd) AS month_start_date,
cust_ID
FROM myserver.mytable
EXPAND ON PERIOD(month_start_date, ADD_MONTHS(month_start_date,12)) AS pd
BY ANCHOR MONTH_BEGIN -- every 1st of month
FOR PERIOD (DATE - 500, DATE) -- use this to restrict to a specific date range
) AS dt
GROUP BY month_start_date
ORDER BY month_start_date

Display a rolling 12 weeks chart in SSRS report

I am calling the data query in ssrs like this:
SELECT * FROM [DATABASE].[dbo].[mytable]
So, the current week is the last week from the query (e.g. 3/31 - 4/4) and each number represents the week before until we have reached the 12 weeks prior to this week and display in a point chart.
How can I accomplish grouping all the visits for all locations by weeks and adding it to the chart?
I suggest updating your SQL query to Group by a descending Dense_Rank of DatePart(Week,ARRIVED_DATE). In this example, I have one column for Visits because I couldn't tell which columns you were using to get your Visit count:
-- load some test data
if object_id('tempdb..#MyTable') is not null
drop table #MyTable
create table #MyTable(ARRIVED_DATE datetime,Visits int)
while (select count(*) from #MyTable) < 1000
begin
insert into #MyTable values
(dateadd(day,round(rand()*100,0),'2014-01-01'),round(rand()*1000,0))
end
-- Sum Visits by WeekNumber relative to today's WeekNumber
select
dense_rank() over(order by datepart(week,ARRIVED_DATE) desc) [Week],
sum(Visits) Visits
from #MyTable
where datepart(week,ARRIVED_DATE) >= datepart(week,getdate()) - 11
group by datepart(week,ARRIVED_DATE)
order by datepart(week,ARRIVED_DATE)
Let me know if I can provide any more detail to help you out.
You are going to want to do the grouping of the visits within SQL. You should be able to add a calculated column to your table which is something like WorkWeek and it should be calculated on the days difference from a certain day such as Sunday. This column will then by your X value rather than the date field you were using.
Here is a good article that goes into first day of week: First Day of Week

sql to calculate daily totals minues the previous day's totals

I have a table that has a date, item, and quantity.
I need a sql query to return the totals per day, but the total is the quantity minus the previous day totals. The quantity accumulates as the month goes on. So the 1st could have 5 the 2nd have 12 and the 3rd has 20.
So the 1st adds 5
2nd adds 7 to make 12
3rd adds 8 to make 20.
I've done something like this in the past, but can not find it or remember. I know i'll need a correlated sub-query.
TIA
--
Edit 1
I'm using Microsoft Access.
Date is a datetime field,
item is a text, and
quantity is number
--
Edit 2
Ok this is what i have
SELECT oos.report_date, oos.tech, oos.total_cpe, oos_2.total_cpe
FROM oos INNER JOIN (
SELECT oos_2.tech, Sum(oos_2.total_cpe) AS total_cpe
FROM oos_2
WHERE (((oos_2.report_date)<#10/10/2010#))
GROUP BY oos_2.tech
) oos_2 ON oos.tech = oos_2.tech;
How do i get the oos.report_date into where i says #10/10/2010#. I thought I could just stick it in there like mysql, but no luck. I'm gonna continue researching.
Sum them by adding one to the date and making the value negative, thus taking yesterday's total from today's:
SELECT report_date, tech, Sum(total_cpe) AS total_cpe
FROM (
SELECT oos.report_date, oos.tech, oos.total_cpe
FROM oos
UNION ALL
SELECT oos.report_date+1, oos.tech, 0-oos.total_cpe
FROM oos
)
WHERE (report_date < #10/10/2010#)
GROUP BY report_date, tech
ORDER BY report_date, tech
Ok, I figured it out.
SELECT o.report_date, o.tech, o.total_cpe,
o.total_cpe - (
SELECT IIf(Sum(oos.total_cpe) is null, 0,Sum(oos.total_cpe)) AS total_cpe
FROM oos
WHERE (((oos.tech)=o.tech) AND ((oos.report_date)<o.report_date))
) AS total
FROM oos o;