SQL - Finding largest period of activity by customer - sql

I had a difficult time w/ the title, hope this is a little clearer...
I have a table of data (simplified) like so;
Date
Customer
Amount
1/1/2014
1
100.5
4/4/2014
1
122.5
2/1/2014
3
3.25
...but just short of a million records.
I would like to find the x day (let's say 90) period for each customer that has the largest total amount.
To phase the question a little differently, given all the transactions for all customers, for each customer I would like to find the 90 day period that has the largest total amount and what that total amount is in the period.
Trying to advise a brute force approach where I define all the possible ranges (or iterate over all possible ranges on the fly).
Any thoughts on a more elegant solution?

You can use a self-join for this, but the performance may not be so great:
select t.*,
(select sum(t2.amount)
from table t2
where t2.customer = t.customer and
t2.date >= dateadd(day, -90, t.date) and t2.date <= t.date
) as amount90
from table t;
There is a more efficient method in SQL Server 2012.

Here's a psuedo-code ish kind of answer that I think would work. It'd probably be really slow though.
You could have a function that calculates the number of activities provided a start date and number of days,
--function F
#userid, #startdate, #dayCount
SELECT COUNT(*)
FROM TABLE
WHERE
UserID = #userid
and date > #startDate
and date < Dateadd(#startdate, #dayCount)
and then do a max on that function?
select max(f(user, date))
from TableContainingDateRanges

Related

(Forecasting) Calculating the balance in the future

My product team has asked if I could create a very crude forecasting data table for them to work with. I have a pretty good idea on a lot of the steps I need to take, but I am stuck on figuring how to to calculate the Inventory Quantity for tomorrow, the next day, etc.
In my database, I am able to see our current quantity on hand. I would call that starting balance (for today). I am then going to create an average usage field and that will be my estimated daily sales. I will then take the starting balance - estimated daily sales = ending balance. I can do that for today, my question is how do I roll that formula forward for the next 120 days
You can use a recursive CTE to generate number from 0 to 120 and then calculate the day and balance with them.
DECLARE #estimated_daily_sales integer = 2;
DECLARE #starting_balance integer = 12345;
WITH
cte AS
(
SELECT 0 i
UNION ALL
SELECT i + 1 i
FROM cte
WHERE i + 1 <= 120
)
SELECT dateadd(day, i, convert(date, getdate())) day,
#starting_balance - i * #estimated_daily_sales balance
FROM cte
OPTION (MAXRECURSION 120);
db<>fiddle

SQL - Get data between two dates grouped by week

This question might have been solved many times or even asked many times. But as I am not a savvy in SQL, I am not able to figure out things found on the internet. Like I am not able to tweak Queries from the Internet to my needs.
And here comes my need
I have a Table named Orders Containing Fields like OrderId, OrderDate etc.
I need to generate an Excel Sheet. The sheet will have the count of orders grouped by week.
(Like how many orders placed within that week)
The user can choose the year in which he/she needs the report for.
So if the user chooses the current year then I need to generate an excel report containing data from Jan 1 to today grouped by week.
If the user chooses any other year(maybe previous years) then I need to generate a report containing all the data for that year grouped by week.
Currently, I am looking for an SQL query that returns data like this(expected output)
Week Date Range Total No of Orders
-----+--------------------------+-------------------
week#1 2018-01-01 - 2018-01-07 10
week#2 2018-01-08 - 2018-01-14 0
week#3 2018-01-15 - 2018-01-21 1
How can I write a query to achieve the same?
Looking for expert advice...
You need to use CTE recursive write calendar by week number,then Orders LEFT JOIN on CTE calendar table get COUNT.
Note:
variable #Dt mock which year you want to start.
Query look like this.
DECLARE #Dt date = '2018-01-01'
;WITH CTE(Dt,maxD) AS (
SELECT DATEPART(ww,#Dt) Dt, DATEPART(ww,MAX(OrderDate)) maxD
FROM Orders
UNION ALL
SELECT (Dt +1) Dt,maxD
FROM CTE
WHERE (Dt +1) <= maxD
)
SELECT CONCAT('week#',c.Dt) 'week',
CONCAT(
CONVERT(char(10),dateadd(week,c.Dt-1, DATEADD(wk, DATEDIFF(wk,-1,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), 0)),126)
,'-'
, CONVERT(char(10),dateadd(week,c.Dt, DATEADD(wk, DATEDIFF(wk,-1,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)), 0)),126)) 'Date Range',
COUNT(o.OrderDate) 'Total No of Orders'
FROM CTE c
LEFT JOIN Orders o on c.Dt = DATEPART(ww,o.OrderDate)
GROUP BY c.Dt
sqlfiddle:http://sqlfiddle.com/#!18/8f089/40

Joining date with datetime and values per day

I am writing a query so that I can fill my facttable. I have a table which registers the weather average per day, registered at 23:59:00 of each day (date).
I have another table in which climate control data of different rooms are registered, per minute (datetime).
And I also have a time dimension available in another database.
I want to fill my facttable with all the available timeKeys combined with all the data from my climate control table and my weather table.
I'm sorry for my English, it isn't my mother tongue.
So, to find the matching timeKey for the date values I wrote this query:
SELECT t.timeKey AS WeathertimeKey,
weather.date AS date,
weather.temperature,
weather.rainAmountMM,
weather.windDirection,
weather.windSpeed
FROM StarSchema.dbo.timeDim t, weather
WHERE DATEPART(mm, t.DATE) = DATEPART(mm, weather.date)
AND DATEPART(dd, t.DATE) = DATEPART(dd, weather.date)
AND DATEPART(Hour, t.DATE) = '23'
AND DATEPART(MINUTE, t.DATE) = '59'
RESULT: Result
My time dimension has a timeKey for every minute in 2015: timeDimension
The facttable I am trying to fill: facttable
My solution for filling the facttable was creating a view with the corresponding timeKey per day and then joining that view in my main query.
SELECT
t.timeKey as timeKey,
rt1.roomId AS roomKey,
1 AS roomDataKey,
1 AS usageKey,
1 AS knmiKey,
rt1.temperature AS temperature,
rt1.locWindow AS locWindow,
rt1.locDoor AS locDoor,
rh1.turnedOn AS turnedOn,
rh1.temperature AS temperatureHeater,
s.storyTemp AS storyTemp,
s.storyHumidity AS storyHumidity,
vw.temperature AS temperatureOutside,
vw.rainAmountMM AS rainAmountMM,
vw.windSpeed AS windSpeed,
vw.windDirection AS windDirection,
vu.gasM3 AS gasM3,
vu.electricityKWH AS electricityKWH
FROM StarSchema.dbo.timeDim t
INNER JOIN roomTemperature1 rt1 ON rt1.date = t.DATE
INNER JOIN roomHeating1 rh1 ON rt1.date = rh1.date
INNER JOIN story s ON s.date = rt1.date
INNER JOIN vw_timeKeyWeatherDay vw ON t.timeKey = vw.WeathertimeKey
INNER JOIN vw_timeKeyUsageDay vu ON t.timeKey = vu.UsagetimeKey
The result is as follows: result2
So now it only uses the timeKey of 23:59 of everyday.
I want the complete days in there, but how do I do this?
Can someone help me out?
And my apologies for my use of the English language, again.
I did my best :-)
If I understand your question property, you want to match two date columns which have a different level of precision: one is per minute the other one is per day. What I suggest is a query which stores the yyyy.mm.dd only for both. Then when you join you get a matching record every time.
You can do that by adding the number of days that distance each of your dates from the date 0 in SQL server
DECLARE #DVal DATE
DECLARE #DTVal DATETIME
SET #DVal = '2018-01-18'
SET #DTVal = '2018-01-18 12:02:01.003'
SELECT #DVal
SELECT #DTVal
SELECT DATEDIFF(D,0,#DTVal)
SELECT DATEDIFF(D,0,#DVal)
SELECT DATEADD(D,(DATEDIFF(D,0,#DTVal) ),0)
SELECT DATEADD(D,(DATEDIFF(D,0,#DVal) ),0)
Comments about the code above:
First I declare the variables, one DATE and one DATETIME and I give them slightly different values which are less than a day.
Then I select them so that we can see they are different
2018-01-18
2018-01-18 12:02:01.003
Then I select the difference in days between each date and 0, and we have the same number of days
43116
43116
Then I add this difference to the date 0, and we end up with two datetime values which are identical
2018-01-18 00:00:00.000
2018-01-18 00:00:00.000
I hope I have answered your question. Please comment on my answer if I have not. At least this is a starting point. If your goal is to get the complete range of minutes per day you can create two calculated columns, one based on the current date and the other one based on the current date +1 day, and join against the time table with a BETWEEEN ON clause, etc.

How to query date in oracle?

Assuming I have the following table in oracle:
id|orderdatetime (date type)|foodtype (string type)
1|2013-12-02T00:26:00 | burger
2|2013-12-02T00:20:00 | fries
...
(assume there are many dates and times)
Assuming someone happened to have a date in mind (i.e. "2010-12-02T00:25:00").
even though there is no database entry with that specific time in there...
is there some way to query the database such that I can get the row that has a date time that is closest to it without being ahead of the date in mind (ideally, it would be less than or equal to)?
(i.e. in this case, the sql query would return the row for "fries" and not "burger" because the time for burger is past the time the user had in mind despite the fact that the time for "burger" is closer.)
select x.* from (select id,orderdatetime,foods from orders
where orderdatetime <= YOURTIME order by orderdatetime desc)x
where rownum =1
Another would be:
select * from orders where orderdatetime = (select max(orderdatetime) from orders
where orderdatetime <= YOURTIME)

Find closest date in SQL Server

I have a table dbo.X with DateTime column Y which may have hundreds of records.
My Stored Procedure has parameter #CurrentDate, I want to find out the date in the column Y in above table dbo.X which is less than and closest to #CurrentDate.
How to find it?
The where clause will match all rows with date less than #CurrentDate and, since they are ordered descendantly, the TOP 1 will be the closest date to the current date.
SELECT TOP 1 *
FROM x
WHERE x.date < #CurrentDate
ORDER BY x.date DESC
Use DateDiff and order your result by how many days or seconds are between that date and what the Input was
Something like this
select top 1 rowId, dateCol, datediff(second, #CurrentDate, dateCol) as SecondsBetweenDates
from myTable
where dateCol < #currentDate
order by datediff(second, #CurrentDate, dateCol)
I have a better solution for this problem i think.
I will show a few images to support and explain the final solution.
Background
In my solution I have a table of FX Rates. These represent market rates for different currencies. However, our service provider has had a problem with the rate feed and as such some rates have zero values. I want to fill the missing data with rates for that same currency that as closest in time to the missing rate. Basically I want to get the RateId for the nearest non zero rate which I will then substitute. (This is not shown here in my example.)
1) So to start off lets identify the missing rates information:
Query showing my missing rates i.e. have a rate value of zero
2) Next lets identify rates that are not missing.
Query showing rates that are not missing
3) This query is where the magic happens. I have made an assumption here which can be removed but was added to improve the efficiency/performance of the query. The assumption on line 26 is that I expect to find a substitute transaction on the same day as that of the missing / zero transaction.
The magic happens is line 23: The Row_Number function adds an auto number starting at 1 for the shortest time difference between the missing and non missing transaction. The next closest transaction has a rownum of 2 etc.
Please note that in line 25 I must join the currencies so that I do not mismatch the currency types. That is I don't want to substitute a AUD currency with CHF values. I want the closest matching currencies.
Combining the two data sets with a row_number to identify nearest transaction
4) Finally, lets get data where the RowNum is 1
The final query
The query full query is as follows;
; with cte_zero_rates as
(
Select *
from fxrates
where (spot_exp = 0 or spot_exp = 0)
),
cte_non_zero_rates as
(
Select *
from fxrates
where (spot_exp > 0 and spot_exp > 0)
)
,cte_Nearest_Transaction as
(
select z.FXRatesID as Zero_FXRatesID
,z.importDate as Zero_importDate
,z.currency as Zero_Currency
,nz.currency as NonZero_Currency
,nz.FXRatesID as NonZero_FXRatesID
,nz.spot_imp
,nz.importDate as NonZero_importDate
,DATEDIFF(ss, z.importDate, nz.importDate) as TimeDifferece
,ROW_NUMBER() Over(partition by z.FXRatesID order by abs(DATEDIFF(ss, z.importDate, nz.importDate)) asc) as RowNum
from cte_zero_rates z
left join cte_non_zero_rates nz on nz.currency = z.currency
and cast(nz.importDate as date) = cast(z.importDate as date)
--order by z.currency desc, z.importDate desc
)
select n.Zero_FXRatesID
,n.Zero_Currency
,n.Zero_importDate
,n.NonZero_importDate
,DATEDIFF(s, n.NonZero_importDate,n.Zero_importDate) as Delay_In_Seconds
,n.NonZero_Currency
,n.NonZero_FXRatesID
from cte_Nearest_Transaction n
where n.RowNum = 1
and n.NonZero_FXRatesID is not null
order by n.Zero_Currency, n.NonZero_importDate