Get list of dates without entries in SQL Server - sql-server

I am looking to find a solution to this problem. I have a table called LogEntry that stores information used by multiple offices, where they have to log any visitors that come in to their office on any given day. If no visitors come in, they are still required to log "No Visitors" for the day. How do I run a query that pulls all dates where an office failed to create even a "No Visitors" log?
I've looked at this question (and the article linked within), but even adapting that query, I'm only able to create a blank row for a date where an office is missing an entry for a date, not specify the actual office that did not create an entry. Is there a way to do what I'm trying to do?
declare #temp table (
CDate datetime,
loc_id varchar(50)
)
insert into #temp SELECT DISTINCT entryDate, locationID FROM LogEntry WHERE entryDate >= '05/01/2017' AND entryDate <= '07-31-2017'
;with d(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
)
select DISTINCT t.loc_id, CONVERT(date, d.date)
FROM d LEFT OUTER JOIN #temp t ON d.date = t.CDate
GROUP BY t.loc_id, d.date
ORDER BY t.loc_id
As I said, this query returns me a list of dates in the date range, and all locations that submitted entries on that date, but I'd like to find a way to extract essentially the opposite information: if an office (specified by locationID) did not submit an entry on a given day, return only those locationIDs and the dates that they missed.
Sample data
EntryID | locationID | entryDate
=================================
1 1 07-01-2017
2 1 07-02-2017
3 2 07-02-2017
4 1 07-04-2017
Expected Result (for date range of 07-01 to 07-04)
locationID | missedEntryDate
============================
1 07-03-2017
2 07-01-2017
2 07-03-2017
2 07-04-2017

Your first step was good, you create a list of all dates, but you also need a list of all locations. Then you create a cross join to have all combinations and then you perform the left join to find out what is missing.
;with allDates(date) as (
select cast('05/01/2017' as datetime)
union all
select date+1
from d
where date < '07/31/2017'
), allLocations as (
SELECT DISTINCT loc_id
FROM #temp
), allCombinations as (
SELECT date, loc_id
FROM allDates
CROSS JOIN allLocations
)
SELECT AC.loc_id, AC.date
FROM allCombinations AC
LEFT JOIN #temp t
ON AC.date = t.CDate
AND AC.loc_id = t.loc_id
WHERE t.loc_id IS NULL -- didnt find a match on #temp

If your dataset is not too large you can try this:
select t.loc_id, CONVERT(date, d.date)
FROM d
-- Cross join dates to all available locs
CROSS JOIN (SELECT DISTINCT loc_id FROM #temp ) AS Locs
LEFT JOIN
( SELECT loc_id, t.CDate
FROM #temp
GROUP BY loc_id, d.date ) AS t ON d.date = t.CDate AND Locs.loc_id = t.loc_id
ORDER BY Locs.loc_id
This should be a bit faster:
;WITH cte AS (
SELECT a.LocID, RangeStart.CDate, ( CASE WHEN Input.LocID IS NULL THEN 1 ELSE 0 END ) AS IsMissing
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN ( SELECT CONVERT( DATETIME, '2017-05-01' ) AS CDate ) AS RangeStart
LEFT JOIN
( SELECT LocID, MIN( CDate ) AS CDate
FROM #temp
WHERE CDate = '2017-05-01'
GROUP BY LocID ) AS Input ON a.LocID = Input.LocID AND RangeStart.CDate = Input.CDate
UNION ALL
SELECT a.LocID, a.CDate + 1 AS CDate,
ISNULL( ItExists, 0 ) AS IsMissing
FROM cte AS a
OUTER APPLY( SELECT LocID, 1 AS ItExists FROM #temp AS b WHERE a.LocID = b.LocID AND a.CDate + 1 = b.CDate ) AS c
WHERE a.CDate < '2017-07-01'
)
SELECT * FROM cte OPTION( MAXRECURSION 0 )
You can also add an index:
CREATE INDEX IX_tmp_LocID_CDate ON #temp( LocID, CDate )
Sample data set for the second query:
CREATE TABLE #temp( LocID VARCHAR( 50 ), CDate DATETIME )
INSERT INTO #temp
VALUES
( '1', '2017-05-01' ), ( '1', '2017-05-02' ), ( '1', '2017-05-03' ), ( '1', '2017-05-04' ), ( '1', '2017-05-05' ),
( '2', '2017-05-01' ), ( '2', '2017-05-02' ), ( '2', '2017-05-03' ), ( '2', '2017-05-04' ), ( '2', '2017-05-05' )
;WITH d AS (
SELECT CAST( '05/01/2017' AS DATETIME ) AS date
UNION ALL
SELECT date + 2
FROM d
WHERE date < '2018-07-31'
)
INSERT INTO #temp
SELECT LocID, d.date
FROM ( SELECT DISTINCT LocID FROM #temp ) AS a
CROSS JOIN d
OPTION( MAXRECURSION 0 )

Related

What is the most effcient way to replace values in a specific column of a table for this specific scenario?

I am using SQL Server 2014 and I have a table in my database called t1 (extract of only 2 columns shown below):
ResaID StayDate
100 2020-02-03
100 2020-02-04
100 2020-02-05
120 2020-04-06
120 2020-04-07
120 2020-04-08
120 2020-04-09
120 2020-04-10
I need to change the dates in the StayDate column based on the following information (extract shown exactly as provided):
ID StartDate EndDate
100 2020-06-04 2020-06-06
120 2021-03-01 2021-03-05
I have started writing my T-SQL query as follows (but it is getting quite tedious as I have to do it for more than 100 ResaID!):
USE MyDatabase
UPDATE t1
SET StayDate = CASE WHEN ResaID = 100 and StayDate = '2020-02-03' THEN '2020-06-04'
WHEN ResaID = 100 and StayDate = '2020-02-04' THEN '2020-06-05'
WHEN ResaID = 100 and StayDate = '2020-02-05' THEN '2020-06-06'
...
ELSE StayDate
END
Is there a more efficient way to tackle this problem?
You can use recursive approach :
with r_cte as (
select id, convert(date, startdate) as startdate, convert(date, enddate) as enddate
from ( values (100, '2020-06-04', '2020-06-06'),
(120, '2021-03-01', '2021-03-03')
) t(id, startdate, enddate)
union all
select id, dateadd(day, 1, startdate), enddate
from cte c
where startdate < enddate
), r_cte_seq as (
select r.cte.*, row_number() over(partition by id order by startdate) as seq
from r_cte
), cte_seq as (
select t1.*, row_number() over (partition by ResaID order by staydate) as seq
from t1
)
update cs
set cs.staydate = rc.startdate
from cte_seq cs inner join
r_cte_seq rc
on rc.id = cs.ResaID and rc.seq = cs.seq;
Here is my approach to this problem. I would use a numbers table to generate a record for each date in the new range for each reservation ID. I would then partition this data by reservation ID, ordered by the date. Doing the same partition logic on the existing data will allow records to be properly joined together.
I would then do a DELETE operation followed by an INSERT operation. This would leave you with the appropriate amount of records. The only manual thing that would need to be done is to populate the auxiliary data for reservations with expanded date ranges. I expanded one of your new ranges to show this scenario.
I've marked where the setup for this demo ends in the code below. Everything below that is my intended solution that should be able to be implemented with your real tables.
--Ranges Table
DECLARE #ranges TABLE
(
ID INT
,StartDate DATETIME
,EndDate DATETIME
)
DECLARE #t1 TABLE
(
ResaID INT
,StayDate DATETIME
,ColA INT
,ColB NVARCHAR(100)
,ColC BIT
)
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
VALUES
(100, '2020-02-03', 1, 'A', 0)
,(100, '2020-02-04', 100, 'B', 1)
,(100, '2020-02-05', 255, 'C', 1)
,(120, '2020-04-06', 34, 'D', 1)
,(120, '2020-04-07', 67, 'E', 0)
,(120, '2020-04-08', 87, 'F', 0)
,(120, '2020-04-09', 545, 'G', 1)
,(120, '2020-04-10', 288, 'H', 0)
INSERT INTO #ranges
(
ID
,StartDate
,EndDate
)
VALUES
(100, '2020-06-04', '2020-06-07')
,(120, '2021-03-01', '2021-03-05')
--END DEMO SETUP
DROP TABLE IF EXISTS #numbers
DROP TABLE IF EXISTS #newRecords
--GENERATE NUMBERS TABLE
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b) -- 5*10000
SELECT ROW_NUMBER() OVER (ORDER BY n) as Num
INTO #numbers
FROM e4
ORDER BY n;
;with oldData --PARTITION THE EXISTING RECORDS
AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ResaID ORDER BY STAYDATE) as ResPartID
FROM #t1
)
,newRanges --GENERATE YOUR NEW RANGES AND PARITITION
AS
(
select
r.ID
,CAST(n.num as DATETIME) as StayDate
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY n.num) as ResPartID
from #ranges r
inner join #numbers n on CAST(r.StartDate as INT) <= n.Num AND CAST(r.EndDate as INT) >= n.Num
)
SELECT n.ID
,n.StayDate
,o.ColA
,o.ColB
,o.ColC
into #newRecords
FROM newRanges n
left join oldData o on n.ID = o.ResaID and n.ResPartID = o.ResPartID
--DELETE OLD RECORDS
DELETE t
FROM #t1 t
inner join #ranges r on t.ResaID = r.ID
--INSERT NEW DATA
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
SELECT
ID
,StayDate
,ColA
,ColB
,ColC
FROM #newRecords
SELECT * FROM #t1
The following code converts the t1 dates into ranges and then uses the corresponding range dates to calculate new StayDate values. You can swap out the final select for one of the commented statements to see what is going on in the CTEs. The final select can be replaced with an update if you want to change the original table data.
-- Thanks to Aaron Hughes for setting up the sample data.
-- I changed the DateTime columns to Date .
--Ranges Table
DECLARE #ranges TABLE
(
ID INT
,StartDate DATE
,EndDate DATE
)
DECLARE #t1 TABLE
(
ResaID INT
,StayDate DATE
,ColA INT
,ColB NVARCHAR(100)
,ColC BIT
)
INSERT INTO #t1
(
ResaID
,StayDate
,ColA
,ColB
,ColC
)
VALUES
(100, '2020-02-03', 1, 'A', 0)
,(100, '2020-02-04', 100, 'B', 1)
,(100, '2020-02-05', 255, 'C', 1)
,(120, '2020-04-06', 34, 'D', 1)
,(120, '2020-04-07', 67, 'E', 0)
,(120, '2020-04-08', 87, 'F', 0)
,(120, '2020-04-09', 545, 'G', 1)
,(120, '2020-04-10', 288, 'H', 0)
INSERT INTO #ranges
(
ID
,StartDate
,EndDate
)
VALUES
(100, '2020-06-04', '2020-06-07')
,(120, '2021-03-01', '2021-03-05');
with
-- Calculate the date range for each stay in #t1 .
ResaRanges as (
select ResaId, Min( StayDate ) as ResaStartDate, Max( StayDate ) as ResaEndDate
from #t1
group by ResaId ),
-- Match up the #t1 date ranges with the #ranges date ranges.
CombinedRanges as (
select RR.ResaId, RR.ResaStartDate, RR.ResaEndDate, DateDiff( day, RR.ResaStartDate, RR.ResaEndDate ) + 1 as ResaDays,
R.StartDate, R.EndDate, DateDiff( day, R.StartDate, R.EndDate ) + 1 as RangeDays,
DateDiff( day, RR.ResaStartDate, R.StartDate ) as DaysOffset
from ResaRanges as RR inner join
#ranges as R on R.ID = RR.ResaId )
-- Calculate the new StayDate values for all #t1 ranges that are not longer than the corresponding #range .
-- The difference between range starting dates is added to each StayDate .
select T.ResaId, T.StayDate, DateAdd( day, CR.DaysOffset, T.StayDate ) as NewStayDate
from #t1 as T inner join
CombinedRanges as CR on CR.ResaID = T.ResaID
where CR.RangeDays >= CR.ResaDays;
-- To see the steps you can use one of the following select staements to view the intermediate results:
-- select * from ResaRanges;
-- select * from CombinedRanges;

Variable within SQL query

I have this:
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
'{"EntityName":"account","Ids":["'+CAST(AccountId as varchar(50))+'"],"OwnerId":"0C01C994-1205-E511-988E-26EE4189191B"}' as [Parameters]
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
Within the parameter field is an id (0C01C994-1205-E511-988E-26EE4189191B). Is it possible it could sequentially assign a different id from a list for each row? There are 5 id's in total.
What i'm trying to get to is this result set equally split between the 5 different id's.
Thanks
You can add one more NEWID() in the sub query and handle in the SELECT as below:
SELECT id, [name], TypeId, OrgName, SystemUserId, StatusId, CreatedAt,
'{"EntityName":"account","Ids":["' + AccountId +'"],"OwnerId":"' + ParamId + '"}' as [Parameters]
FROM (
SELECT NEWID() as id,
'OwnerReassign' as name,
1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId,
GETDATE() as CreatedAt,
CAST(NEWID() AS VARCHAR (36)) as ParamId,
CAST(AccountId as varchar(50)) as AccountId
FROM Account
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) A
You can use something like the following. Basically, use a row number for both your IDs and your data table to update, then do a MOD (%) operation with the amount of ID's you want to assign, so your data table to update is split into N groups. Then use that group ID to assign each ID.
IF OBJECT_ID('tempdb..#IDsToAssign') IS NOT NULL
DROP TABLE #IDsToAssign
CREATE TABLE #IDsToAssign (
IDToAssign VARCHAR(100))
-- 3 IDs example
INSERT INTO #IDsToAssign (
IDToAssign)
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
UNION ALL
SELECT IDToAssign = NEWID()
DECLARE #AmountIDsToAssign INT = (SELECT COUNT(1) FROM #IDsToAssign)
IF OBJECT_ID('tempdb..#Account') IS NOT NULL
DROP TABLE #Account
CREATE TABLE #Account (
PrimaryKey INT PRIMARY KEY,
AssignedID VARCHAR(100))
-- 10 Rows example
INSERT INTO #Account (
PrimaryKey)
VALUES
(100),
(200),
(351),
(154),
(194),
(345),
(788),
(127),
(124),
(14)
;WITH DataRowNumber AS
(
SELECT
A.*,
RowNumber = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Account AS A
),
IDsRowNumbers AS
(
SELECT
D.IDToAssign,
RowNumber = ROW_NUMBER() OVER (ORDER BY D.IDToAssign)
FROM
#IDsToAssign AS D
),
NewIDAssignation AS
(
SELECT
R.*,
IDRowNumberAssignation = (R.RowNumber % #AmountIDsToAssign) + 1
FROM
DataRowNumber AS R
)
UPDATE A SET
AssignedID = R.IDToAssign
FROM
NewIDAssignation AS N
INNER JOIN IDsRowNumbers AS R ON N.IDRowNumberAssignation = R.RowNumber
INNER JOIN #Account AS A ON N.PrimaryKey = A.PrimaryKey
SELECT
*
FROM
#Account AS A
ORDER BY
A.AssignedID
/* Results:
PrimaryKey AssignedID
----------- ------------------------------------
124 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
194 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
351 1CC7F0F1-7EDE-4F7F-B0A3-739D74A62390
788 2A58A573-EDCB-428E-A87A-6BFCED265A9C
200 2A58A573-EDCB-428E-A87A-6BFCED265A9C
127 2A58A573-EDCB-428E-A87A-6BFCED265A9C
14 2A58A573-EDCB-428E-A87A-6BFCED265A9C
100 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
154 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
345 FD8036DA-0E15-453E-8A59-FA3C2BDB8FB1
*/
The ordering of the ROW_NUMBER() function will determine how ID's are assigned.
You could potentially do this by using the ROW_NUMBER() field in a subquery; for example:
SELECT NEWID() as id, 'OwnerReassign' as name, 1 as TypeId,
'MyOrganisation' as OrgName,
'07DA8E53-74BD-459C-AF94-A037897A51E3' as SystemUserId,
0 as StatusId, GETDATE() as CreatedAt,
case B / ##ROWCOUNT
when 0 then '0C01C994-1205-E511-988E-26EE4189191B'
when 1 then '12345677-1205-E511-988E-26EE4189191B'
when 2 then '66666666-1205-E511-988E-26EE4189191B'
etc...
end
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY A.Id)
FROM Account A
WHERE OwnerIdName IN ('John Smith') AND New_AccountType = 1
) AS B
If you want the system to pick those values then you could put then in their own temporary table, too.

Return value from function in SQL Server

I want to get the last Saturday from today + 21 days.
In order to achieve this, I have written this script shown below. But the problem is that I can't get success to return the value from the result.
I want to create this function in SQL Server and will get this value in a stored procedure where I want.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #NumOfDays INT
DECLARE #resultDate smalldatetime
SET #StartDate = GETDATE()
SET #EndDate = GETDATE()+21
SET #NumOfDays = DATEDIFF(DD,#StartDate , #EndDate) + 1 ;
WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1
DATEADD(DD, (RN - 1), #StartDate) SaturdayDates
FROM
Numbers
WHERE
RN <= #NumOfDays
AND DATENAME (WEEKDAY, (DATEADD(DD, (RN - 1), #StartDate))) = 'Saturday'
ORDER BY
SaturdayDates DESC
Can you please guide me to achieve my goal? Thanks
Just rewrite it like this table-valued function:
CREATE FUNCTION dbo.Get_NextSaturdayDay()
RETURNS TABLE
AS
RETURN
(
-- Add the SELECT statement with parameter references here
WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1 DATEADD( DD,(RN - 1) , GETDATE() ) as SaturdayDates
FROM
Numbers
WHERE
RN <= (DATEDIFF(DD,GETDATE() , DATEADD(day,21,GETDATE()) ) + 1) AND DATENAME ( WEEKDAY, (DATEADD( DD,(RN - 1) , GETDATE() )) ) = 'Saturday'
ORDER BY SaturdayDates DESC
)
GO
Than do:
SELECT *
FROM dbo.Get_NextSaturdayDay()
Output:
SaturdayDates
2016-10-15 11:02:33.570
If you need scalar-valued function:
CREATE FUNCTION dbo.Get_NextSaturdayDay ()
RETURNS datetime
AS
BEGIN
DECLARE #datetime datetime
;WITH Tens AS
(
SELECT 1 N UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1
),
HUNDREDS AS
(
SELECT T1.N FROM TENS T1 CROSS JOIN TENS T2
),
THOUSANDS AS
(
SELECT T1.N FROM HUNDREDS T1 CROSS JOIN HUNDREDS T2
),
Numbers AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) RN FROM THOUSANDS
)
SELECT TOP 1 #datetime = DATEADD( DD,(RN - 1) , GETDATE() )
FROM
Numbers
WHERE
RN <= (DATEDIFF(DD,GETDATE() , DATEADD(day,21,GETDATE()) ) + 1) AND DATENAME ( WEEKDAY, (DATEADD( DD,(RN - 1) , GETDATE() )) ) = 'Saturday'
ORDER BY DATEADD( DD,(RN - 1) , GETDATE() ) DESC
-- Return the result of the function
RETURN #datetime
END
GO
Then run:
SELECT dbo.Get_NextSaturdayDay()
Output:
2016-10-15 11:02:33.570

Multi-statement Scalar to Multi-statement TVF

I have a question on these few lines of code, particularly how this #workTable is being populated with the StartingCost and EndingCost values:
DECLARE #workTable TABLE
(
ProductId INT ,
StartingCost MONEY ,
EndingCost MONEY
) ;
The full query is listed below.
IF OBJECT_ID(N'Production.ms_tvf_ProductCostDifference',N'TF' ) IS NOT NULL
--SELECT * FROM sys.objects WHERE name LIKE 'm%'
DROP FUNCTION Production.ms_tvf_ProductCostDifference ;
GO
CREATE FUNCTION Production.ms_tvf_ProductCostDifference
(
#StartDate DATETIME ,
#EndDate DATETIME
)
RETURNS #retCostDifference TABLE
(
ProductId INT ,
CostDifference MONEY
)
AS
BEGIN
DECLARE #workTable TABLE
(
ProductId INT ,
StartingCost MONEY ,
EndingCost MONEY
) ;
INSERT INTO #retCostDifference
( ProductId ,
CostDifference
)
SELECT ProductID ,
StandardCost
FROM ( SELECT pch.ProductID ,
pch.StandardCost ,
ROW_NUMBER() OVER
( PARTITION BY ProductID
ORDER BY StartDate DESC ) AS rn
FROM Production.ProductCostHistory AS pch
WHERE EndDate BETWEEN
#StartDate AND #EndDate
) AS x
WHERE x.rn = 1 ;
UPDATE #retCostDifference
SET CostDifference = CostDifference - StandardCost
FROM #retCostDifference cd
JOIN ( SELECT ProductID ,
StandardCost
FROM ( SELECT pch.ProductID ,
pch.StandardCost ,
ROW_NUMBER() OVER
( PARTITION BY ProductID
ORDER BY StartDate ASC )
AS rn
FROM Production.ProductCostHistory
AS pch
WHERE EndDate BETWEEN
#StartDate AND #EndDate
) AS x
WHERE x.rn = 1
) AS y ON cd.ProductId = y.ProductID ;
RETURN ; select top 20 * from Production.ProductCostHistory
END
Go
/********************************************************************************
The code above represents
Listing 17: A multi-statement TVF
This TVF, Instead of retrieving a single row from the database and calculating
the price difference, pulls back all rows from the database and calculates the
price difference for all rows at once.
*********************************************************************************/
SELECT p.ProductID ,
p.Name ,
p.ProductNumber ,
pcd.CostDifference
FROM Production.Product AS p
INNER JOIN Production.ms_tvf_ProductCostDifference
('2001-01-01', GETDATE()) AS pcd
ON p.ProductID = pcd.ProductID ;
In your code, the below block is simply declared and it never used.
DECLARE #workTable TABLE
(
ProductId INT ,
StartingCost MONEY ,
EndingCost MONEY
) ;
Since #retCostDifference is the return table for this function and all the transactions (INSERT, UPDATE) are happened to the #retCostDifference table only.
There is no use of #workTable in the function.

Concatenate date ranges in SQL (T/SQL preferred)

I need to concatenate rows with a date and a code into a date range
Table with two columns that are a composite primary key (date and a code )
Date Code
1/1/2011 A
1/2/2011 A
1/3/2011 A
1/1/2011 B
1/2/2011 B
2/1/2011 A
2/2/2011 A
2/27/2011 A
2/28/2011 A
3/1/2011 A
3/2/2011 A
3/3/2011 A
3/4/2011 A
Needs to be converted to
Start Date End Date Code
1/1/2011 1/3/2011 A
2/1/2011 2/2/2011 A
1/1/2011 1/2/2011 B
2/27/2011 3/4/2011 A
Is there any other way or is a cursor loop the only way?
declare #T table
(
[Date] date,
Code char(1)
)
insert into #T values
('1/1/2011','A'),
('1/2/2011','A'),
('1/3/2011','A'),
('1/1/2011','B'),
('1/2/2011','B'),
('3/1/2011','A'),
('3/2/2011','A'),
('3/3/2011','A'),
('3/4/2011','A')
;with C as
(
select *,
datediff(day, 0, [Date]) - row_number() over(partition by Code
order by [Date]) as rn
from #T
)
select min([Date]) as StartDate,
max([Date]) as EndDate,
Code
from C
group by Code, rn
sql server 2000 has it limitations. Rewrote the solution to make it more readable.
declare #t table
(
[Date] datetime,
Code char(1)
)
insert into #T values
('1/1/2011','A'),
('1/2/2011','A'),
('1/3/2011','A'),
('1/1/2011','B'),
('1/2/2011','B'),
('3/1/2011','A'),
('3/2/2011','A'),
('3/3/2011','A'),
('3/4/2011','A')
select a.code, a.date, min(b.date)
from
(
select *
from #t t
where not exists (select 1 from #t where t.code = code and t.date -1 = date)
) a
join
(
select *
from #t t
where not exists (select 1 from #t where t.code = code and t.date = date -1)
) b
on a.code = b.code and a.date <= b.date
group by a.code, a.date
Using a DatePart function for month will get you the "groups" you want
SELECT Min(Date) as StartDate, Max(Date) as EndDate, Code
FROM ThisTable Group By DatePart(m, Date), Code

Resources