I have a scenario where I have to find out missing record.
--Code for Creating Source Table
CREATE TABLE [dbo].[NaTarget](
[BillKey] [int] NULL,
[StartDate] [date] NULL,
[EndDate] [date] NULL
)
GO
--Code for Creating Target Table
CREATE TABLE [dbo].[NaSource](
[BillKey] [int] NULL,
[StartDate] [date] NULL,
[EndDate] [date] NULL
)
GO
--Inserting Records in Source
INSERT INTO [dbo].[NaSource] ([BillKey],[StartDate],[EndDate])
VALUES('1','2014-01-13','2014-03-27')
GO
INSERT INTO [dbo].[NaSource]([BillKey],[StartDate],[EndDate])
VALUES('2','2014-02-14','2014-04-20')
GO
INSERT INTO [dbo].[NaSource]([BillKey],[StartDate],[EndDate])
VALUES('3','2013-11-13','2014-01-18')
GO
--Inserting records In Target
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('1','2014-01-13' , '2014-01-31' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('1','2014-02-01' , '2014-02-28' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('1','2014-03-01' , '2014-03-27' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('2','2014-02-14' , '2014-02-28' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('2','2014-03-01' , '2014-03-31' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('2','2014-04-01' , '2014-04-20' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('3','2013-11-13' , '2013-11-30' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('3','2013-12-01' , '2013-12-31' )
INSERT INTO [dbo].[NaTarget] ([BillKey] ,[StartDate],[EndDate])
VALUES ('3','2014-01-01' , '2014-01-18' )
Now for any BillKey, StartDate in target will be StartDate from Source and EndDate will be last date of month and now for same Billkey, next record will have 1st date of next month and EndDate will be last date, until last date of same BillKey is reached.
I have to find any record if it gets deleted.
Example if BillKey = 3
StartDate= 2013-12-01 EndDate = 2013-12-31 is
not present in target we need to find it
Example will explain it better
Here is an attempt at this: If I understand your question correctly, you're looking to check to see if any expected values in the Target table based on the Start and End Dates in the Source table aren't actually there.
You'll need to essentially recreate the results table with what you are expecting from the NaSource table's StartDate and EndDate, and check that against the NaTarget table.
I'm positive there's a more efficient way of doing this (preferably without using cursors and while loops), but this should give you the results you're looking for:
Declare #Results Table
(
BillKey Int,
StartDate Date,
EndDate Date
)
Declare #BillKey Int
Declare #EndDate Date
Declare #Cur Date
Declare cur Cursor Fast_Forward For
Select BillKey, StartDate, EndDate
From NaSource
Open cur
While 1 = 1
Begin
Fetch Next From cur Into #BillKey, #Cur, #EndDate
If ##FETCH_STATUS <> 0 Break
While (#Cur < #EndDate)
Begin
Insert #Results
Select #BillKey, #Cur,
Case When DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #Cur) + 1, 0)) > #EndDate
Then Convert(Date, #EndDate)
Else Convert(Date, DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #Cur) + 1, 0)))
End As EndDate
Set #Cur = DATEADD(m, DATEDIFF(m, -1, #Cur), 0)
End
End
Close cur
Deallocate cur
Select R.*
From #Results R
Where Not Exists
(
Select 1
From NaTarget T
Where R.BillKey = T.BillKey
And R.StartDate = T.StartDate
And R.EndDate = T.EndDate
)
Here's my solution using recursive CTE. Build what the natarget table should look like and compare it to the actual natarget. I started getting confused on the dates piece so it may be simplified but this does work.
;with targetCte
as
(
select billkey,
startdate,
CAST(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, startdate) + 1, 0)) as DATE) as enddate
from nasource
union all
select t.billkey,
cast(DATEADD(month, DATEDIFF(mm, 0, dateadd(mm, 1, t.startdate)), 0) as DATE) ,
case
when cast(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, DATEDIFF(mm, 0, dateadd(mm, 1, t.startdate)), 0)) + 1, 0)) as DATE) < n.enddate then cast(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, DATEADD(month, DATEDIFF(mm, 0, dateadd(mm, 1, t.startdate)), 0)) + 1, 0)) as DATE)
else n.enddate
end
as enddate
from targetCte t
join nasource n on n.billkey = t.billkey
where t.enddate < n.enddate
)
select * from targetcte t
where not exists
(select *
from natarget nt
where t.billkey = nt.billkey
and t.startdate = nt.startdate
and t.enddate = nt.enddate)
Insert all records into one table with a unique ID (call this main table)
Take the table with deleted records then run a SELECT * on the Main table where ID NOT IN ID column of the deleted records table
It's filtering by reconciliation. LEFT Join both tables on StartDate AND EndDate pairs WHERE RIGHT keys are NULL. Google SQL Joins and you can find a very useful diagram on issues like that.
Related
I want to recursively loop though the temporary table by date1 field using following query in Sql server. I want that the #stardate and #enddate should auto increment up to the #stopcriteria initially starting from
'01/01/2011' to 12/31/2011' and then
'01/01/2012' to 12/31/2012' and so on up to #stopCriteria and resultset should keep joining with union. I have written following query using cte but is not working.
CREATE TABLE #tempbbs576 ( id int, amount money, date1 date)
insert into #tempbbs576(id,amount,date1) values(1,1000,'1/1/2018')
insert into #tempbbs576(id,amount,date1) values(2,20010,'22/3/2019')
insert into #tempbbs576(id,amount,date1) values(3,40010,'1/1/2017')
insert into #tempbbs576(id,amount,date1) values(4,50010,'23/4/2018')
insert into #tempbbs576(id,amount,date1) values(5,60010,'1/1/2018')
insert into #tempbbs576(id,amount,date1) values(6,70010,'21/1/2018')
DECLARE #startDate DATE;
DECLARE #endDate DATE;
DECLARE #stopCriteria DATE;
SET #startDate='01/01/2011'; SET #endDate='12/31/2011'; SET #stopCriteria='01/01/2018';
;WITH ctesequence
AS (SELECT 'Year' + Datename(year, Dateadd(year, 1, #startDate)) /*'2012'*/
AS FiscalYear, Count(DISTINCT id) AS Last2Years
FROM [dbo].[#tempbbs576](nolock) WHERE date1 BETWEEN #startDate AND
#endDate AND id IN (SELECT id FROM [dbo].[#tempbbs576]
WHERE date1 < Dateadd(year, -1, #startDate) AND id
NOT IN (SELECT id FROM [dbo].[#tempbbs576] WHERE date1 BETWEEN
Dateadd(year,-1, #startDate) AND Dateadd( year, -1, #endDate ) ))
UNION
SELECT 'Year' + Datename(year, Dateadd(year, 1, #startDate)) /*'2012'*/
AS FiscalYear, Count(DISTINCT id) AS Last2Years FROM ctesequence WHERE date1 <= #stopCriteria)
SELECT * FROM ctesequence OPTION(maxrecursion 0)
Please let me know if it is possible through cte.
Thanks in Advance,
Amar
I'm trying to write a stored procedure which groups up rows based on their month and return a sum of all items if they exist and 0 if they don't.
For the date part of the query, what I am trying to get is today's date - extract the month and go back 5 months to gather any data if it exists.
At this stage, the query runs fine as is but I'm wondering if there's any way to optimise this as it looks like I'm running the same set of data over and over again and also it's hard coded to an extent.
The dataset I am trying to achieve is as follows:
Month TotalAmount TotalCount
-----------------------------------
2017-11 0 0
2017-12 200.00 2
2018-01 300.00 3
2018-02 0 0
2018-03 300.00 3
2018-04 100.00 1
Using the following query below, I was able to achieve what I want but as you can see, it's hard coding back the past 5 months so if I wanted to go back 12 months, I'd have to add in more code.
DECLARE #5MonthAgo date = CAST(DATEADD(MONTH, -5, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -5, GETDATE())) AS DATE)
DECLARE #4MonthAgo date = CAST(DATEADD(MONTH, -4, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -4, GETDATE())) AS DATE)
DECLARE #3MonthAgo date = CAST(DATEADD(MONTH, -3, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -3, GETDATE())) AS DATE)
DECLARE #2MonthAgo date = CAST(DATEADD(MONTH, -2, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -2, GETDATE())) AS DATE)
DECLARE #1MonthAgo date = CAST(DATEADD(MONTH, -1, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -1, GETDATE())) AS DATE)
DECLARE #CurrentMonth date = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)
-- Table to return grouped and sum data
DECLARE #StatsTable TABLE ([Month] DATE,
[Total Amount] DECIMAL(18,2),
[Total Count] INT
)
-- Temporary table to hold onto data batch - so table isn't used later on
DECLARE #TempGenTable TABLE ([Id] INT,
[Date] DATETIME,
[Lines] INT NULL,
[Amount] DECIMAL(18, 2) NULL
)
INSERT INTO #TempGenTable
SELECT
Id, Date, Lines, Amount
FROM
TallyTable
WHERE
Date >= #5MonthAgo
INSERT INTO #StatsTable
SELECT
#5MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
UNION
SELECT
#4MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
...
Is there an easier way to be able to get the above data with more flexibility in the number of months?
Is it better to just have the query pass in a month variable and it checks just the current month and have a loop within the controller to go back x number of months?
I would generate the data using a recursive CTE and then use left join:
with months as (
select datefromparts(year(getdate()), month(getdate()), 1) as month_start, 5 as n
union all
select dateadd(month, -1, month_start), n - 1
from months
where n > 0
)
select m.month_start, count(s.id), sum(s.amount)
from months m left join
#StatsTable s
on m.month_start = s.month
group by m.month_start
order by m.month_start;
You haven't provided sample data, so I'm not sure what s.month looks like. You might want the join condition to be:
on s.month >= m.month_start and s.month < dateadd(month, 1, m.month_start)
Below is a set-based method to generate the needed monthly periods:
--sample data
CREATE TABLE dbo.TallyTable (
Id int
, Date datetime
, Lines int
, Amount decimal(18, 2)
);
INSERT INTO dbo.TallyTable
VALUES
(1, '2017-12-05', 1, 50.00)
,(2, '2017-12-06', 1, 150.00)
,(3, '2018-01-10', 1, 100.00)
,(4, '2018-01-11', 1, 100.00)
,(5, '2018-01-12', 1, 100.00)
,(6, '2018-03-15', 1, 225.00)
,(7, '2018-03-15', 1, 25.00)
,(8, '2018-03-15', 1, 50.00)
,(9, '2018-04-20', 1, 100.00);
GO
DECLARE #Months int = 5; --number of historical months
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t100 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b)
, periods AS (SELECT
CONVERT(varchar(7), DATEADD(month, DATEDIFF(month, '', GETDATE()) - num, ''),121) AS Month
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num, '') AS PeriodStart
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num + 1, '') AS NextPeriodStart
FROM t100
WHERE num <= #Months
)
SELECT periods.Month, COALESCE(SUM(Amount), 0) AS TotalAmount, COALESCE(COUNT(ID), 0) AS TotalCount
FROM periods
LEFT JOIN dbo.TallyTable ON
TallyTable.Date >= PeriodStart
AND TallyTable.Date < NextPeriodStart
GROUP BY periods.Month
ORDER BY periods.Month;
I want to split date range in months. I will pass startdate(1-jan-2011) and enddate(31-dec-2011) as a parameter then it must return result like
1-jan-2011 - 31-jan-2011
1-feb-2011 - 28-feb-2011
1-mar-2011 - 31-mar-2011
Please send me a stored procedure.....
Thanks,
Abhishek
Try this:
CREATE PROC SplitDateRange
#from DATETIME,
#to DATETIME
AS
BEGIN
SET NOCOUNT ON;
SET #from = CONVERT(VARCHAR, DATEADD(DAY, -DATEPART(DAY, #from)+1, #from), 112)
-- Sql 2000
CREATE TABLE #temp (DateFrom DATETIME, DateTo DATETIME)
WHILE #from < #to
BEGIN
INSERT #temp VALUES (#from, DATEADD(DAY, -1, DATEADD(MONTH, 1, #from)))
SET #from = DATEADD(MONTH, 1, #from)
END
SELECT * FROM #temp
DROP TABLE #temp
--sql 2005+
/*
;WITH Ranges(DateFrom, DateTo) AS
(
SELECT #from DateFrom, DATEADD(DAY, -1, DATEADD(MONTH, 1, #from)) DateTo
UNION ALL
SELECT DATEADD(MONTH, 1, DateFrom), DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEADD(MONTH, 1, DateFrom)))
FROM Ranges
WHERE DateFrom < DATEADD(MONTH, -1, #To)
)
SELECT * FROM Ranges
OPTION(MAXRECURSION 0)
*/
END
GO
EXEC SplitDateRange '2011-01-02', '2012-06-06'
So that you can use the results in another SQL Query (which I assume is where you're going) I'd put that into a table valued function.
Assuming SQL Server 2005+ you could use this...
CREATE FUNCTION dbo.ufnMonthlyIntervals(
#from_date SMALLDATETIME,
#end_date SMALLDATETIME
)
RETURNS TABLE
WITH
intervals (
from_date,
end_date
)
AS
(
SELECT #from_date, DATEADD(MONTH, 1, #from_date ) - 1
UNION ALL
SELECT end_date + 1, DATEADD(MONTH, 1, end_date + 1) - 1 FROM intervals WHERE end_date < #end_date
)
RETURN
SELECT
from_date,
CASE WHEN end_date > #end_date THEN #end_date ELSE end_date END AS end_date
FROM
intervals
Then you just use SELECT * FROM dbo.ufnMonthlyIntervals('20110101', '20111201') AS intervals
I am currently working on a function in which I use a recursive CTE, but it seems that have poor performance. I need this to be in function (so no temp tables) so I can easily use it within stored procedures.
Here is the code:
CREATE FUNCTION [dbo].[Web_GetDailyLoadListUDF]
(
#CustomerID INT
, #StartDate DATETIME
, #Days INT
, #IncludeChildren BIT
)
RETURNS #TableOfValues TABLE
(
RowID SMALLINT IDENTITY(1,1)
, DailyLoadCount INT
, DailyLoadDate VARCHAR(6)
, FullDate DATETIME
)
AS
BEGIN
DECLARE #MaxDate DATETIME;
SET #MaxDate = DATEADD(dd, #Days * -1.7, DATEDIFF(dd, 0, #StartDate));
WITH DateCTE AS
(
SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, #StartDate)) AS DateValue
UNION ALL
SELECT DATEADD(DAY, -1, DateValue)
FROM DateCTE
WHERE DATEADD(DAY, -1, DateValue) > #MaxDate
)
INSERT INTO #TableOfValues
SELECT * FROM
(
SELECT TOP (#Days)
(
SELECT COUNT(*)
FROM dbo.[Load] l WITH (NOLOCK)
JOIN dbo.LoadCustomer lc WITH (NOLOCK)
ON lc.LoadID = l.ID
JOIN dbo.Customer c WITH (NOLOCK)
ON c.ID = lc.CustomerID
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
AND l.StateType = 1
AND lc.Main = 1
AND (c.ID = #CustomerID OR (#IncludeChildren = 1 AND c.ParentCustomerID = #CustomerID))
) AS DailyLoadCount
, CONVERT(VARCHAR(6), dct.DateValue, 107) AS DailyLoadDate
, dct.DateValue
FROM DateCTE dct
WHERE
DATEPART(DW, dct.DateValue) NOT IN (1, 7)
AND dct.DateValue NOT IN
(
SELECT HolidayDate FROM Holiday
)
ORDER BY dct.DateValue DESC
) AS S
ORDER BY s.DateValue ASC
RETURN
END
What this SQL is supposed to retrieve is the number of loads per day, for the past #Days that are business days (no weekends/holidays).
I basically just need some help optimizing this so that it doesn't run so slow. (Takes up to 20 seconds per customer, and this will be called over thousands).
Your main problem is just here
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
It should be
WHERE l.LoadDate >= dct.DateValue
AND l.LoadDate < dct.DateValue +1
Create composite indexs on Load(LoadDate, ID) and Load(ID, LoadDate) and drop the one that does not get used in the query plan.
You should show the query plan whenever you are asking questions about performance. To view the query plan, run the query inside the function on its own using variables for the input parameters. From the menu in SSMS, enable the option "Query -> Include Actual Execution Plan"
Since you don't have enough rep to post images, you can reveal the text plan as follows. Provide some sensible parameters in the first SELECT statement.
set showplan_text on;
Then, run the below in TEXT mode, i.e. press Ctrl-T then Ctrl-E.
DECLARE
#CustomerID INT
, #StartDate DATETIME
, #Days INT
, #IncludeChildren BIT
SELECT
#CustomerID = 1
, #StartDate = '20110201'
, #Days = 10
, #IncludeChildren = 1
DECLARE #TableOfValues TABLE
(
RowID SMALLINT IDENTITY(1,1)
, DailyLoadCount INT
, DailyLoadDate VARCHAR(6)
, FullDate DATETIME
)
DECLARE #MaxDate DATETIME;
SET #MaxDate = DATEADD(dd, #Days * -1.7, DATEDIFF(dd, 0, #StartDate));
WITH DateCTE AS
(
SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, #StartDate)) AS DateValue
UNION ALL
SELECT DATEADD(DAY, -1, DateValue)
FROM DateCTE
WHERE DATEADD(DAY, -1, DateValue) > #MaxDate
)
INSERT INTO #TableOfValues
SELECT * FROM
(
SELECT TOP (#Days)
(
SELECT COUNT(*)
FROM dbo.[Load] l WITH (NOLOCK)
JOIN dbo.LoadCustomer lc WITH (NOLOCK)
ON lc.LoadID = l.ID
JOIN dbo.Customer c WITH (NOLOCK)
ON c.ID = lc.CustomerID
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
AND l.StateType = 1
AND lc.Main = 1
AND (c.ID = #CustomerID OR (#IncludeChildren = 1 AND c.ParentCustomerID = #CustomerID))
) AS DailyLoadCount
, CONVERT(VARCHAR(6), dct.DateValue, 107) AS DailyLoadDate
, dct.DateValue
FROM DateCTE dct
WHERE
DATEPART(DW, dct.DateValue) NOT IN (1, 7)
AND dct.DateValue NOT IN
(
SELECT HolidayDate FROM Holiday
)
ORDER BY dct.DateValue DESC
) AS S
ORDER BY s.DateValue ASC
SELECT * FROM #TableOfValues
Edit the plan into your question
You should use an inline UDF instead (right now you are actually using a temp table)
See http://msdn.microsoft.com/en-us/library/ms189294.aspx
Or convert it into a view instead.
Correlated subqueries run row-by-row, do not use them. Use a join or a join to a derived table instead. You also need to make sure any where clauses can take advantage of the indexing. Search on saragble queries to see what kinds of things cannot use indexes and what can be done to make it use an index.
Please use following sample data.
declare #tbl table (id int, fid int, arrival datetime, created datetime)
insert into #tbl select 1, 10, DATEADD(dd, -10, GETDATE()), DATEADD(dd, -6, getdate())
insert into #tbl select 1, 10, DATEADD(dd, -10, GETDATE()), DATEADD(dd, -6, getdate())
insert into #tbl select 1, 10, DATEADD(dd, -10, GETDATE()), null
insert into #tbl select 2, 20, DATEADD(dd, -3, GETDATE()), DATEADD(dd, -2, getdate())
insert into #tbl select 2, 20, DATEADD(dd, -3, GETDATE()), null
I would like to have all rows between arrival '2011-02-25' and '2011-02-28' which created after this date '2011-02-20' including created date null.
Query1:
select * from #tbl
where arrival >= '2011-02-25' and arrival < '2011-02-28'
and created >= '2011-02-20'
Above query fetch two rows but I need 3rd row of FID = 10 which has created date null
Qery2: select row of FID = 20 which I don't need because it is not in range of Arrival date.
select * from #tbl
where arrival >= '2011-02-25' and arrival < '2011-02-28'
and created >= '2011-02-20' OR created is null
This is sample data. The original query fetch data from different table and has joins with 10+ tables so I don't want to run query again to include rows in temp table.
Thanks.
EDIT:
Sorry, wanted to ask this but put wrong question. Thanks for your help.
declare #tbl table (id int, fid int, arrival datetime, created datetime)
insert into #tbl select 1, 10, DATEADD(dd, -10, GETDATE()), DATEADD(dd, -6, getdate())
insert into #tbl select 1, 10, DATEADD(dd, -10, GETDATE()), DATEADD(dd, -6, getdate())
insert into #tbl select 1, 10, null, DATEADD(dd, -6, getdate())
insert into #tbl select 2, 20, DATEADD(dd, -3, GETDATE()), DATEADD(dd, -2, getdate())
insert into #tbl select 2, 20, null, DATEADD(dd, -2, getdate())
select * from #tbl
where arrival >= '2011-02-26' and arrival < '2011-02-28'
Need the third row of fid = 10 too where arrival date is NULL
This might do what you want.
;with cte as
(
select distinct fid
from #tbl
where
arrival >= '2011-02-26' and
arrival < '2011-02-28'
)
select *
from cte as C
inner join #tbl as T
on C.fid = T.fid
where
(arrival >= '2011-02-26' and
arrival < '2011-02-28') or
arrival is null
I think should work:
select * from #tbl
where (arrival >= '2011-02-25' and arrival < '2011-02-28')
and (created >= '2011-02-20' or created is Null)
As per your Edit you will need to do this:
select * from #tbl
where ((arrival >= '2011-02-25' and arrival < '2011-02-28') or arrival is null)
and (created >= '2011-02-20' or created is Null)
This will return the 3rd FID = 10 row, however it will also return the row ID = 2 and FID = 20 because that row also satisfies the filter conditions.
This is a slightly different take from the more obvious answers posted above
select * from #tbl
where arrival >= '2011-02-25' and arrival < '2011-02-28'
and COALESCE(created,'2011-02-25') >= '2011-02-20'