Find closest date in SQL Server - sql

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

Related

adjust recursive sql query to exclude holidays and weekends

I have a dataset like this called data_per_day
instructional_day
points
2023-01-24
2
2023-01-23
2
2023-01-20
1
2023-01-19
0
and so on. the table shows weekdays (days minus holidays and weekends) and the number of points someone has earned. 1 is the start of a streak and 0 is the end of a streak. 2 is max points after a streak has started.
I need to find how long is the latest streak. so in this case the result should be 3
I created a recursive cte but the query returns 2 as the streak count because i'm using lag mechanism with days. instead I need to adjust so that the instructional days are used rather than all dates.
RECURSIVE cte AS (
SELECT
student_unique_id,
instructional_day,
points,
1 AS cnt
FROM
`data_per_day`
WHERE
instructional_day = DATE_ADD(CURRENT_DATE('America/Chicago'), INTERVAL -1 DAY)
UNION ALL
SELECT
a.student_unique_id,
a.instructional_day,
a.points,
c.cnt+1
FROM (
SELECT
*
FROM
`data_per_day`
WHERE
points > 0 ) a
INNER JOIN
cte c
ON
a.student_unique_id = c.student_unique_id
AND a.instructional_day = c.instructional_day - INTERVAL '1' day )
SELECT
student_unique_id,
MAX(cnt) AS streak
FROM
cte --
WHERE
student_unique_id = "419"
GROUP BY
student_unique_id
How do I adjust the query?
This is not a trivial coding exercise, so I won't actually write the code and provide it.
What you have here is a gaps and islands question. You want to identify the largest "island" of days with points within a date range. Depending upon what dates are contained in your data, you may need to generate a list of sequential dates that meet your criteria.
One problem I see is that you are trying to combine the steps to generate the date range (the recursive CTE) with the points. You'll need to separate those steps.
Define the date range.
Generate the dates within the range.
Filter the dates with isweekday = 'no' and isholiday = 'no'. You will probably want to add a row number during this step.
[left] join the dates to your data, including coalesce(points, 0)
Filter the data to points > 0.
Identify the islands.
Identify the largest island per student.

SQL Server query to get next nearest date from recurring events

This is my scenario. I have a table with FirstMaintenanceEventDate and some data repeating after certain days from FirstMaintenanceEventDate. What I need to find out through a SQL Server query is to get the nearest date of each row among them.
Ex: there is a data row FirstMaintenanceEventDate is last month and it will repeat after 40 days which is next month. Likewise there are a lot of events here. Some of them have FirstMaintenanceEventDate in the future. Out of all these items I need to get the nearest date for each row.
I could get the nearest date without considering repeating process.
This is my query
SELECT TOP 1 *
FROM FIS_MaintenanceEventInstance
WHERE VehicleName = '600-GUR'
AND FirstMaintenanceEventDate >= GETDATE()
ORDER BY FirstMaintenanceEventDate ASC
Need to update it to consider repeat events as I describe above. Probably something like this but this isn't correct.
SELECT TOP 1 *
FROM FIS_MaintenanceEventInstance
WHERE
VehicleName = '600-GUR'
AND FirstMaintenanceEventDate >= GETDATE()
AND CASE
WHEN FirstMaintenanceEventDate < GETDATE()
THEN (Getdate() + RecurringDays)
END
ORDER BY FirstMaintenanceEventDate ASC
Any suggestion would be appreciated.
NOTE: If you need more information please let me now.
EDITED
I have tried follow query as Jatin Patel suggested in his answer below.
SELECT TOP 1 *,
CASE WHEN FirstMaintenanceEventDate < GETDATE() THEN DateAdd(day,RecurringDays,FirstMaintenanceEventDate)
ELSE FirstMaintenanceEventDate END AS MaintenanceEventDate
FROM FIS_MaintenanceEventInstance
WHERE VehicleName ='600-GUR'
ORDER BY MaintenanceEventDate ASC
This is not working as expected. After calculate the repeat date (here it's MaintenanceEventDate) also should consider when get the nearest date. According to this query it is calculate repeated date (MaintenanceEventDate) if it is in past and return it without check with other dates in the table.
Try this,
SELECT TOP 1 *,
CASE WHEN FirstMaintenanceEventDate < GETDATE() THEN (Getdate()+RecurringDays) ELSE FirstMaintenanceEventDate END AS MaintenanceEventDate
FROM FIS_MaintenanceEventInstance
WHERE VehicleName ='600-GUR'
ORDER BY MaintenanceEventDate ASC

Date Range for set of same data

I am trying to build a SQL query which will give me the date range for the dates with same prices. If there is a break in the prices, I expect to see it in a new line. Even if sometime during the month there are same prices, if there is change in the prices sometime in between I want to see it as two separate rows with the specific date range.
Sample Data:
Date Price
1-Jan 3.2
2-Jan 3.2
3-Jan 3.2
4-Jan 3.2
5-Jan 3.2
6-Jan 3.2
7-Jan 3.2
8-Jan 3.2
9-Jan 3.5
10-Jan 3.5
11-Jan 3.5
12-Jan 3.5
13-Jan 3.5
14-Jan 4.2
15-Jan 4.2
16-Jan 4.2
17-Jan 3.2
18-Jan 3.2
19-Jan 3.2
20-Jan 3.2
21-Jan 3.2
22-Jan 3
23-Jan 3
24-Jan 3
25-Jan 3
26-Jan 3
27-Jan 3
28-Jan 3
29-Jan 3.5
30-Jan 3.5
31-Jan 3.5
Desired Result :
Price Date Range
3.2 1-8
3.5 9-13
4.2 14-16
3.2 17-22
3 22-28
3.5 29-31
Non-relational Solution
I don't think any of other answers are correct.
GROUP BY won't work
Using ROW_NUMBER() forces the data into a Record Filing System structure, which is physical, and then processes it as physical records. At a massive performance cost. Of course, in order to write such code, it forces you to think in terms of RFS instead of thinking in Relational terms.
Using CTEs is the same. Iterating through the data, especially data that does not change. At a slightly different massive cost.
Cursors are definitely the wrong thing for a different set of reasons. (a) Cursors require code, and you have requested a View (b) Cursors abandon the set-processing engine, and revert to row-by-row processing. Again, not required. If a developer on any of my teams uses cursors or temp tables on a Relational Database (ie. not a Record Filing System), I shoot them.
Relational Solution
Your data is Relational, logical, the two given data columns are all that is necessary.
Sure, we have to form a View (derived Relation), to obtain the desired report, but that consists of pure SELECTs, which is quite different to processing (converting it to a file, which is physical, and then processing the file; or temp tables; or worktables; or CTEs; or ROW_Number(); etc).
Contrary to the lamentations of "theoreticians", who have an agenda, SQL handles Relational data perfectly well. And you data is Relational.
Therefore, maintain a Relational mindset, a Relational view of the data, and a set-processing mentality. Every report requirement over a Relational Database can be fulfilled using a single SELECT. There is no need to regress to pre-1970 ISAM File handling methods.
I will assume the Primary Key (the set of columns that give a Relational row uniqueness) is Date, and based on the example data given, the Datatype is DATE.
Try this:
CREATE VIEW MyTable_Base_V -- Foundation View
AS
SELECT Date,
Date_Next,
Price
FROM (
-- Derived Table: project rows with what we need
SELECT Date,
[Date_Next] = DATEADD( DD, 1, O.Date ),
Price,
[Price_Next] = (
SELECT Price -- NULL if not exists
FROM MyTable
WHERE Date = DATEADD( DD, 1, O.Date )
)
FROM MyTable MT
) AS X
WHERE Price != Price_Next -- exclude unchanging rows
GO
CREATE VIEW MyTable_V -- Requested View
AS
SELECT [Date_From] = (
-- Date of the previous row
SELECT MAX( Date_Next ) -- previous row
FROM MyTable_V
WHERE Date_Next < MT.Date
),
[Date_To] = Date, -- this row
Price
FROM MyTable_Base_V MT
GO
SELECT *
FROM MyTable_V
GO
Method, Generic
Of course this is a method, therefore it is generic, it can be used to determine the From_ and To_ of any data range (here, a Date range), based on any data change (here, a change in Price).
Here, your Dates are consecutive, so the determination of Date_Next is simple: increment the Date by 1 day. If the PK is increasing but not consecutive (eg. DateTime or TimeStamp or some other Key), change the Derived Table X to:
-- Derived Table: project rows with what we need
SELECT DateTime,
[DateTime_Next] = (
-- first row > this row
SELECT TOP 1
DateTime -- NULL if not exists
FROM MyTable
WHERE DateTime > MT.DateTime
),
Price,
[Price_Next] = (
-- first row > this row
SELECT TOP 1
Price -- NULL if not exists
FROM MyTable
WHERE DateTime > MT.DateTime
)
FROM MyTable MT
Enjoy.
Please feel free to comment, ask questions, etc.
You can do this by adding a grouping column. A neat trick for this is the difference of two sequences of numbers -- when the difference is constant, then the price is the same.
select price, min(date), max(date)
from (select s.*,
(row_number() over (order by date) -
row_number() over (partition by price order by date)
) as grp
from sample s
) grp
group by grp, price;
Note: be careful that price is stored as a fixed decimal rather than a floating decimal. Otherwise, values that look the same might not actually be the same.
This is what you are looking for
declare #temptbl table (price decimal(18,2), mindate date, maxdate date)
declare #price as decimal(18,2), #date as date
declare tempcur cursor for
select price, date
from YourTable
open tempcur
fetch next from tempcur
into #price, #date
while (##fetch_status = 0)
begin
if (isnull((select price from #temptbl where maxdate = (select max(maxdate)from #temptbl)),0) <> #price)
insert into #temptbl (price,mindate,maxdate) values (#price,#date,#date)
else
update #temptbl
set maxdate = #date
where maxdate = (select max(maxdate)from #temptbl)
fetch next from tempcur
into #price, #date
end
deallocate tempcur
select price, convert(nvarchar(50), mindate) + ' to ' + convert(nvarchar(50), maxdate) as [date range] from #temptbl
Use CTE, below is working code.
WITH grouped AS (
SELECT
Pricedate, price,
grp1= ROW_NUMBER() OVER (ORDER BY Pricedate) -
ROW_NUMBER() OVER (Partition by price ORDER BY Pricedate)
FROM yourTablewithDateAndPrice
)
SELECT
DtFrom = MIN(Pricedate),
DtTo = MAX(Pricedate),
Price = price
FROM grouped
GROUP BY Price,grp1
order by DtFrom;
The internal query will created same group till the time it find same price, else group will be incremented by one.
in Final group by you will have required result.

In single select statement count number of orders within several time ranges

Thanks in advance for any thoughts, advice, and suggestions!
System: SQL Server 2008 R2
I need to count for a given customer the number of repurchases within several different time intervals (date ranges), and display these counts in a single table. I get this working with several subsequent common table expressions (cte) which I finally join together. This way, however, is cumbersome and rather inefficient (in terms of performance speed).
The SQL code I expected to be shortest and fastest, however, does not work for several reasons and will return error messages like
“ the subqueries (Select (count …….) will return several values and hence “cannot be used as an expression”
or
Another error message is: “An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.”
Please find below a sample table (WDB), the desired result table (WDB_result) and the SQL code that need improvement. Thanks a lot to everyone who may help!
Sample WDB Table:
CustomerID: customer ID
InNo: invoice number
OrderDate: order date
Result table WDB_result:
Columns
A) total number of repurchases
B) number of repurchases within the first 3 months
C) number of repurchases within the first 6 months
D) number of repurchases within the first 12 months
E) number of repurchases with last 3 months
F) number of repurchases with last 6 months
G) number of repurchases with last 12 months
Sample SQL Code to calculate columns A, B, und E:
SELECT
CustomerID
, COUNT(InNo) OVER (PARTITION by CustomerID) -1) as Norepurchases_Total
, (SELECT (COUNT(InNo) OVER (PARTITION by CustomerID) -1) as Count3
FROM WDB
WHERE OrderDate between MIN(OrderDate) and DATEADD(month, 3, MIN(OrderDate))
) as Norepurchases_1st_3months
, (SELECT (COUNT(InNo) OVER (PARTITION by CustomerID) -1) as Count3
FROM WDB
WHERE OrderDate between MAX(OrderDate) and DATEPART(y, DATEADD(m, -3, getdate()))
) as NoRepurchases_Last_3months
FROM WDB;
Typically what I would do in a situation like this is something like
SELECT CustormerID,
SUM(
CASE
WHEN OrderDate > #ThreeMonthsAgo AND OrderDate <= #CurrentDate
1
ELSE 0
END
) InLast3Months,
SUM(
CASE
WHEN OrderDate > #SixMonthsAgo AND OrderDate <= #ThreeMonthsAgo
1
ELSE 0
END
) InLast3To6Months,
...
FROM YourTable
GROUP BY CustomerID
This will alow you to determine the buckets beforehand as variables, as shown, and then count how many items falls in which buckets.
This is a very interesting query and I think what you're after can be achieved if you read over this stackoverflow article on multiple aggregate functions.
Applying the same concept as is used in this question should solve your problem.

SQL Difference between using two dates

My database has a table called 'clientordermas'. Two columns of that table are as follows.
execid and filledqty.
Three records of those two fields are as follows.
E02011/03/12-05:57_24384 : 1000
E02011/03/12-05:57_24384 : 800
E02011/03/09-05:57_24384 : 600
What i need to do is get the filledqty diffrence btween latest date and 1 before latest date which is 400(1000-400).
I have extracted the date from the execid as follows:
(SUBSTR (execid, 3, 10)
I tried so hard but but I was unable to write the sql query to get 400. Can someone please help me to do this???
P.S I need to select maximum filled quantity from the same date. That is 1000 not, 800.
You can use window functions to access "nearby" rows, so if you first clean up the data in a subquery and then use window functions to access the next row, you should get the right results. But unless you have an index on substr(execid, 3, 10), this is going to be be slow.
WITH datevalues AS
(
SELECT max(filledqty) maxfilledqty, substr(execid, 3, 10) execiddate
FROM clientordermas
GROUP BY substr(execid, 3, 10)
)
SELECT
execiddate,
maxfilledqty -
last_value(maxfilledqty) over(ORDER BY execiddate DESC ROWS BETWEEN 0 PRECEDING AND 1 FOLLOWING)
FROM datevalues
ORDER BY execiddate DESC;
WITH maxqtys AS (
SELECT substr(a.execid,3,10) AS date, MAX(a.filledqty) AS maxqty
FROM clientordermas a
GROUP BY substr(a.execid,3,10)
)
SELECT a.maxqty-b.maxqty
FROM maxqtys a, maxqtys b
WHERE a.date <> b.date AND ROWNUM=1
ORDER BY a.date DESC, b.date DESC
This first creates a subquery (maxqty) which contains the max filledqty for each unique date, then cross join this subquery to itself, excluding the same rows. This results in a table containing pairs of dates (excluding the same dates).
Sort these pairs by date descending, and the top row till contain the last and 2nd-to-last date, with the appropriate quantities.