Best way to write TSQL query - sql-server

I'm curious if there is a better way to write this query, either syntactically to make simpler/easier to follow, or for processing speed.
I've been writing a lot of similar queries recently and hoping to streamline this. I'm not a developer by trade, but I have to dive into sql
quintiles table
costcenter
quintile
quintilevalue
A
Max
50
A
Q1
8
A
Q2
12
A
Q3
14
A
Q4
18
B
Max
45
B
Q1
5
B
Q2
10
B
Q3
12
B
Q4
16
employees table
costcenter
employee
hiredate
A
W
2021-01-01
A
X
2021-02-08
B
Y
2020-12-16
B
Z
2021-01-15
workcomplete table
employee
workdate
widgetsassembled
W
2021-02-26
4
W
2021-03-05
5
X
2021-05-24
6
X
2021-05-31
3
Y
2021-04-07
2
Y
2021-04-14
8
Z
2021-02-07
4
Z
2021-02-14
1
My goal: for each record in the workcomplete table, find out what the tenure was when the employee did the work and what quintile it falls in.
employee
workdate
widgetsassembled
costcenter
tenure
smallestquintile
quintile
W
2021-02-26
4
A
8
8
Q1
W
2021-03-05
5
A
9
12
Q2
X
2021-05-24
6
A
15
18
Q4
X
2021-05-31
3
A
16
18
Q4
Y
2021-04-07
2
B
16
16
Q4
Y
2021-04-14
8
B
17
45
Max
Z
2021-02-07
4
B
4
5
Q1
Z
2021-02-14
1
B
5
5
Q1
This is what I did, it works fine:
WITH quintiles AS (
SELECT 'A' as costcenter
,'Q1' as quintile
,8 as quintilevalue
UNION SELECT 'A','Q2',12
UNION SELECT 'A','Q3',14
UNION SELECT 'A','Q4',18
UNION SELECT 'A','Max',50
UNION SELECT 'B','Q1',5
UNION SELECT 'B','Q2',10
UNION SELECT 'B','Q3',12
UNION SELECT 'B','Q4',16
UNION SELECT 'B','Max',45
),
employees AS
(
SELECT 'A' as costcenter
,'W' as employee
,'2021-01-01' as hiredate
UNION SELECT 'A','X','2021-02-08'
UNION SELECT 'B','Y','2020-12-16'
UNION SELECT 'B','Z','2021-01-15'
),
workcomplete AS
(
SELECT 'W' as employee
,'2021-02-26' as workdate
,4 as widgetsassembled
UNION SELECT 'W','2021-03-05',5
UNION SELECT 'X','2021-05-24',6
UNION SELECT 'X','2021-05-31',3
UNION SELECT 'Y','2021-04-07',2
UNION SELECT 'Y','2021-04-14',8
UNION SELECT 'Z','2021-02-07',4
UNION SELECT 'Z','2021-02-14',1
)
SELECT t.*
,q.quintile
FROM (
SELECT wc.employee
,wc.workdate
,wc.widgetsassembled
,e.costcenter
,DATEDIFF(week,e.hiredate,wc.workdate) AS tenure
,MIN(q.quintilevalue) as smallestquintile
FROM workcomplete wc
LEFT JOIN employees e
ON wc.employee = e.employee
LEFT JOIN quintiles q
ON q.costcenter = e.costcenter and DATEDIFF(week,e.hiredate,wc.workdate) <= q.quintilevalue
GROUP BY wc.employee
,wc.workdate
,wc.widgetsassembled
,e.costcenter
,DATEDIFF(week,e.hiredate,wc.workdate)
)t
LEFT JOIN quintiles q
ON t.smallestquintile = q.quintilevalue and t.costcenter = q.costcenter
This also works.
WITH quintiles AS (
SELECT 'A' as costcenter
,'Q1' as quintile
,8 as quintilevalue
UNION SELECT 'A','Q2',12
UNION SELECT 'A','Q3',14
UNION SELECT 'A','Q4',18
UNION SELECT 'A','Max',50
UNION SELECT 'B','Q1',5
UNION SELECT 'B','Q2',10
UNION SELECT 'B','Q3',12
UNION SELECT 'B','Q4',16
UNION SELECT 'B','Max',45
),
employees AS
(
SELECT 'A' as costcenter
,'W' as employee
,'2021-01-01' as hiredate
UNION SELECT 'A','X','2021-02-08'
UNION SELECT 'B','Y','2020-12-16'
UNION SELECT 'B','Z','2021-01-15'
),
workcomplete AS
(
SELECT 'W' as employee
,'2021-02-26' as workdate
,4 as widgetsassembled
UNION SELECT 'W','2021-03-05',5
UNION SELECT 'X','2021-05-24',6
UNION SELECT 'X','2021-05-31',3
UNION SELECT 'Y','2021-04-07',2
UNION SELECT 'Y','2021-04-14',8
UNION SELECT 'Z','2021-02-07',4
UNION SELECT 'Z','2021-02-14',1
)
SELECT t.*
,q.quintile
FROM (
SELECT DISTINCT wc.employee
,wc.workdate
,wc.widgetsassembled
,e.costcenter
,DATEDIFF(week,e.hiredate,wc.workdate) AS tenure
,MIN(q.quintilevalue) OVER (PARTITION BY wc.employee, wc.workdate) as smallestquintile
FROM workcomplete wc
LEFT JOIN employees e
ON wc.employee = e.employee
LEFT JOIN quintiles q
ON q.costcenter = e.costcenter and DATEDIFF(week,e.hiredate,wc.workdate) <= q.quintilevalue
)t
LEFT JOIN quintiles q
ON t.smallestquintile = q.quintilevalue and t.costcenter = q.costcenter
Is there a simpler way to do this, without nesting selects?

It sounds like you just need a top-1-per-group query.
The standard solution for that is to use ROW_NUMBER
WITH quintiles AS (
SELECT 'A' as costcenter
,'Q1' as quintile
,8 as quintilevalue
UNION SELECT 'A','Q2',12
UNION SELECT 'A','Q3',14
UNION SELECT 'A','Q4',18
UNION SELECT 'A','Max',50
UNION SELECT 'B','Q1',5
UNION SELECT 'B','Q2',10
UNION SELECT 'B','Q3',12
UNION SELECT 'B','Q4',16
UNION SELECT 'B','Max',45
),
employees AS
(
SELECT 'A' as costcenter
,'W' as employee
,'2021-01-01' as hiredate
UNION SELECT 'A','X','2021-02-08'
UNION SELECT 'B','Y','2020-12-16'
UNION SELECT 'B','Z','2021-01-15'
),
workcomplete AS
(
SELECT 'W' as employee
,'2021-02-26' as workdate
,4 as widgetsassembled
UNION SELECT 'W','2021-03-05',5
UNION SELECT 'X','2021-05-24',6
UNION SELECT 'X','2021-05-31',3
UNION SELECT 'Y','2021-04-07',2
UNION SELECT 'Y','2021-04-14',8
UNION SELECT 'Z','2021-02-07',4
UNION SELECT 'Z','2021-02-14',1
)
SELECT t.*
FROM (
SELECT wc.employee
,wc.workdate
,wc.widgetsassembled
,e.costcenter
,DATEDIFF(week,e.hiredate,wc.workdate) AS tenure
,q.quintile
,ROW_NUMBER() OVER (PARTITION BY wc.employee, wc.workdate ORDER BY q.quintilevalue) as rn
FROM workcomplete wc
LEFT JOIN employees e
ON wc.employee = e.employee
LEFT JOIN quintiles q
ON q.costcenter = e.costcenter and DATEDIFF(week,e.hiredate,wc.workdate) <= q.quintilevalue
)t
WHERE rn = 1;
db<>fiddle

Related

Use Dynamic Syntax in a View or a Function

I have a dynamic query that I've written that looks like the following:
DECLARE #sql nvarchar(max)
SELECT #sql =
'select distinct datetable.Date
from (
select cast(DATEADD(day,-(a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)),getdate()) as date) AS Date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as d
union all
select cast(DATEADD(day,(a.a + (10 * b.a) + (100 * c.a) + (1000 * d.a)),getdate()) as date) AS Date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as d
) datetable
where '
+
replace(replace(replace(stuff((SELECT ' or datetable.Date between cast(''' + cast(cast(hld1.StrDate as date) as nvarchar(12)) + N''' as date) and cast(''' + cast(cast(hld1.endDate as date) as nvarchar(12))+ N''' as date)
'
from hld1 for xml path('')),1,3,''), '<', '<'), '>', '>'), '
', char(13)) +
'order by datetable.Date '
--print #sql
EXEC sys.sp_executeSQL #SQL
HLD1 is a list of holidays, where each holiday has a start and end date. The query itself returns a list of dates that are defined as holidays. (The reason that I can't just select the start dates and union them to the end dates is that there could very feasibly be a holiday with three days, and the middle day wouldn't show up in either list.
However, I'm using this monstrosity to create a function, and, as part of the function, I want to be able to do something like "If the date is in this list, then do the following."
My original plan was to set up a view that would just be the list of dates; however, this is not possible, because it uses a variable, and variables aren't allowed in views.
My next thought was to create a function that would just return the list. However, when I put in the syntax to create it as a function, I get the error The last statement included within a function must be a return statement.
I am unsure what path I should pursue from here. The reason that I can't just make a table and list out the dates manually is that currently the list only extends through 2016. In addition, the holiday list (start and end dates) may be created/added differently for different databases that the end goal function would be added to and used on.
If you need more background/information, please let me know and I'd be happy to provide. I'm just learning as I go. :)
Edit 1: I found the following link, but it doesn't appear to apply in this case: Create A View With Dynamic Sql
Why not just create a numbers or tally table as a persistent table or a view and avoid all this nastiness. 99% of this query is just generating a bunch of numbers.
For example, here is a view that performs 0 reads and will generate 10,000 rows of sequential numbers nearly instantly.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
There is your numbers portion. The next part would be to create the persistent dates table with the holidays and such like you are doing.
Here is an awesome article from my buddy Dwain Camps (RIP) about creating a calendar table. http://www.sqlservercentral.com/blogs/dwainsql/2014/03/30/calendar-tables-in-t-sql/
--EDIT--
Here is an example of having a table (#Something) with start and end dates for a holiday. This will list each date between those two dates. Unless I am missing something this should be pretty much what you are trying to do.
create table #Something
(
HolidayName varchar(10)
, StartDate date
, EndDate date
)
insert #Something
select 'phroureo', '2016-03-01', '2016-03-05' union all
select 'Sean', '2016-07-04', '2016-07-05'
select HolidayName
, StartDate
, EndDate
, DATEADD(day, t.N - 1, StartDate) as ResultDate
from #Something s
join cteTally t on t.N <= DATEDIFF(day, StartDate, EndDate) + 1
order by HolidayName
drop table #Something

Getting quantity between a range of months from 2 date parameters

I have a table that stores budget quantities for a company whose fiscal year begins 1st April and ends on 31st March the next year.
I have this query to extract figures for a particular month.
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
WHERE T1.U_Month = DATENAME(MONTH, '2015-04-01') AND T0.U_Source = 'NTEL'
There is an existing report that takes two parameters, a Start and End Date. (type datetime)
Table below: The month column is of type nvarchar.
How do I modify the query such when a user enters StartDate and EndDate e.g.
1st May 2015 and 31st July 2015, I will get a quantity result of 12640.
You can use couple of ways to do this.
One way would be to use PARSE. Like this.
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
WHERE PARSE((T1.U_Month + CONVERT(VARCHAR(4),YEAR(CURRENT_TIMESTAMP))) as datetime) BETWEEN #StartDate AND #EndDate
AND T0.U_Source = 'NTEL'
Another way would be to use a numbers table to map your month name to a month number and use it in your query.
;WITH CTE AS (
SELECT 1 as rn 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 UNION ALL SELECT 1 UNION ALL SELECT 1
),
MonthMap AS
(
SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE
)
SELECT monthnumber,DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1)) FROM MonthMap;
and then join it with your month table like this.
;WITH CTE AS (
SELECT 1 as rn 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 UNION ALL SELECT 1 UNION ALL SELECT 1
),
MonthMap AS
(
SELECT ROW_NUMBER()OVER(ORDER BY rn ASC) as monthnumber FROM CTE
)
SELECT SUM(T1.U_Quantity) AS 'YTDBOwnMadeTea'
FROM [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA] T0
INNER JOIN [SL_NTEL_DB_LIVE].[dbo].[#U_BUDG_MADETEA_ROW] T1
ON T0.DocEntry = T1.DocEntry
INNER JOIN MonthMap M ON T1.U_Month = DATENAME(MONTH,DATEFROMPARTS(2016,monthnumber,1))
WHERE M.monthnumber BETWEEN DATEPART(MONTH,#StartDate) AND DATEPART(MONTH,#EndDate)
AND T0.U_Source = 'NTEL';
You should compare both the approaches for performance. PARSE is simpler to use but would be difficult to index properly.
On a Separate note, you should avoid storing dates or date parts as month names as these take up more storage(even more since you are using NVARCHAR), and are difficult to use efficiently.

Need help in SQL Query 5

I am using SQL Server 2008. I have data by each employee for each day. Below is the sample data.
WITH RawData as
(
SELECT '10001' AS EmpNo,'2015-01-01' as AttendanceDate,'FS' AS ShiftCode UNION
SELECT '10001','2015-01-02','WO' UNION
SELECT '10001','2015-01-03','FS' UNION
SELECT '10001','2015-01-04','FS' UNION
SELECT '10001','2015-01-05','FS' UNION
SELECT '10001','2015-01-06','FS' UNION
SELECT '10001','2015-01-07','FS' UNION
SELECT '10001','2015-01-08','FS' UNION
SELECT '10001','2015-01-09','WO' UNION
SELECT '10001','2015-01-10','FS' UNION
SELECT '10001','2015-01-11','FS' UNION
SELECT '10001','2015-01-12','FS' UNION
SELECT '10001','2015-01-13','FS' UNION
SELECT '10001','2015-01-14','FS' UNION
SELECT '10001','2015-01-15','FS' UNION
SELECT '10001','2015-01-16','WO' UNION
SELECT '10001','2015-01-17','FS' UNION
SELECT '10001','2015-01-18','FS' UNION
SELECT '10001','2015-01-19','FS' UNION
SELECT '10001','2015-01-20','FS' UNION
SELECT '10001','2015-01-21','FS' UNION
SELECT '10001','2015-01-22','FS' UNION
SELECT '10001','2015-01-23','WO' UNION
SELECT '10001','2015-01-24','FS' UNION
SELECT '10001','2015-01-25','FS' UNION
SELECT '10001','2015-01-26','FS' UNION
SELECT '10001','2015-01-27','FS' UNION
SELECT '10001','2015-01-28','FS' UNION
SELECT '10001','2015-01-29','FS' UNION
SELECT '10001','2015-01-30','WO' UNION
SELECT '10001','2015-01-31','FS' UNION
SELECT '10002','2015-01-01','FS' UNION
SELECT '10002','2015-01-02','WO' UNION
SELECT '10002','2015-01-03','WO' UNION
SELECT '10002','2015-01-04','FS' UNION
SELECT '10002','2015-01-05','FS' UNION
SELECT '10002','2015-01-06','FS' UNION
SELECT '10002','2015-01-07','FS' UNION
SELECT '10002','2015-01-08','FS' UNION
SELECT '10002','2015-01-09','WO' UNION
SELECT '10002','2015-01-10','WO' UNION
SELECT '10002','2015-01-11','FS' UNION
SELECT '10002','2015-01-12','FS' UNION
SELECT '10002','2015-01-13','FS' UNION
SELECT '10002','2015-01-14','FS' UNION
SELECT '10002','2015-01-15','FS' UNION
SELECT '10002','2015-01-16','WO' UNION
SELECT '10002','2015-01-17','WO' UNION
SELECT '10002','2015-01-18','FS' UNION
SELECT '10002','2015-01-19','FS' UNION
SELECT '10002','2015-01-20','FS' UNION
SELECT '10002','2015-01-21','FS' UNION
SELECT '10002','2015-01-22','FS' UNION
SELECT '10002','2015-01-23','WO' UNION
SELECT '10002','2015-01-24','WO' UNION
SELECT '10002','2015-01-25','FS' UNION
SELECT '10002','2015-01-26','FS' UNION
SELECT '10002','2015-01-27','FS' UNION
SELECT '10002','2015-01-28','FS' UNION
SELECT '10002','2015-01-29','FS' UNION
SELECT '10002','2015-01-30','WO' UNION
SELECT '10002','2015-01-31','WO')
SELECT * FROM RawData Order By EmpNo,AttendanceDate
How to write SQL Query to get following output based on this sample data ? The workweek of each employee starts on a Day after weekly off and it can be any day (mon, tue etc). The shift code denotes WO: weekly off, FS: First Shift, SS: Second Shift.
EmpNo WeekFrom WeekTo
10001 2015-01-01 2015-01-02
10001 2015-01-03 2015-01-09
10001 2015-01-10 2015-01-16
10001 2015-01-17 2015-01-23
10001 2015-01-24 2015-01-30
10001 2015-01-31 2015-01-31
10002 2015-01-01 2015-01-03
10002 2015-01-04 2015-01-10
10002 2015-01-11 2015-01-17
10002 2015-01-18 2015-01-24
10002 2015-01-25 2015-01-31
Got a solution. But its taking quite a long time on live table with 1 Million rows. Have I done something wrong in a query ? Or there is a better way of doing this.
WITH RawData as
(
-- Insert above data here.
)
,ProcessData AS (
SELECT EmpNo,AttendanceDate,ShiftCode,RowID = ROW_NUMBER() OVER (
ORDER BY EmpNo, AttendanceDate
), WeekNo = 1 FROM RawData
)
,FinalData
AS (
SELECT EmpNo, AttendanceDate, ShiftCode, RowID, WeekNo = 1
FROM ProcessData DA
WHERE RowID = 1
UNION ALL
SELECT DA.EmpNo, DA.AttendanceDate, DA.ShiftCode, DA.RowID,
WeekNo = (CASE WHEN FinalData.EmpNo != DA.EmpNo THEN 1 ELSE FinalData.WeekNo + (CASE WHEN (FinalData.ShiftCode = 'WO' AND DA.ShiftCode != 'WO') THEN 1 ELSE 0 END) END)
FROM FinalData
INNER JOIN ProcessData DA ON DA.RowID = FinalData.RowID + 1
)
SELECT EmpNo, MIN(AttendanceDate) AS StartDate, MAX(AttendanceDate) AS EndDate, WeekNo
FROM FinalData
GROUP BY EmpNo, WeekNo
ORDER BY EmpNo, WeekNo
Try this:
SQL Fiddle
;WITH RawData AS (
-- Your insert statements here
),
Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY EmpNo, grp ORDER BY AttendanceDate DESC)
FROM (
SELECT *,
grp = DATEADD(DAY, -ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate), AttendanceDate)
FROM RawData
WHERE ShiftCode = 'WO'
)t
),
CteWeekOff AS(
SELECT EmpNo, AttendanceDate, ShiftCode FROM cte WHERE RN = 1
),
CteFinal AS(
SELECT
EmpNo,
WeekFrom = MIN(AttendanceDate),
Weekto = MAX(AttendanceDate)
FROM (
SELECT *,
grp = DATEADD(DAY, - ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate), AttendanceDate)
FROM RawData
WHERE ShiftCode <> 'WO'
)t
GROUP BY EmpNo, grp
)
SELECT
EmpNo,
WeekFrom = x.WeekFrom,
WeekTo = w.AttendanceDate
FROM CteWeekOff w
CROSS APPLY(
SELECT TOP 1 WeekFrom
FROM CteFinal r
WHERE
r.EmpNo = w.EmpNo
AND r.WeekFrom <= w.AttendanceDate
ORDER BY r.WeekFrom DESC
)x(WeekFrom)
UNION ALL
SELECT
EmpNo,
WeekFrom = x.WeekFrom,
WeekTo = t.AttendanceDate
FROM (
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY EmpNo ORDER BY AttendanceDate DESC)
FROM RawData
)t
CROSS APPLY(
SELECT TOP 1 AttendanceDate
FROM CteFinal r
WHERE
r.EmpNo = t.EmpNo
AND r.WeekFrom < t.AttendanceDate
ORDER BY r.WeekFrom DESC
)x(WeekFrom)
WHERE
RN = 1
AND ShiftCode <> 'WO'
ORDER BY EmpNo, WeekFrom
Finally this worked. 5 seconds on 230,000 records. I will go ahead with my solution. Thanks for your time. Hope this solution helps someone.
-- Step 1 : Save it to temp table
SELECT EmpNo,AttendanceDate,ShiftCode,RowID = ROW_NUMBER() OVER (
ORDER BY EmpNo, AttendanceDate
), WeekNo = 1 into #RawData FROM -- My table
-- Step 2 : Use temp table
;WITH FinalData
AS (
SELECT EmpNo, AttendanceDate, ShiftCode, RowID, WeekNo = 1
FROM #RawData DA
WHERE RowID = 1
UNION ALL
SELECT DA.EmpNo, DA.AttendanceDate, DA.ShiftCode, DA.RowID,
WeekNo = (CASE WHEN FinalData.EmpNo != DA.EmpNo THEN 1 ELSE FinalData.WeekNo + (CASE WHEN (FinalData.ShiftCode = 'WO' AND DA.ShiftCode != 'WO') THEN 1 ELSE 0 END) END)
FROM FinalData
INNER JOIN #RawData DA ON DA.RowID = FinalData.RowID + 1
)
SELECT EmpNo, MIN(AttendanceDate) AS StartDate, MAX(AttendanceDate) AS EndDate, WeekNo
FROM FinalData
GROUP BY EmpNo, WeekNo
ORDER BY EmpNo, WeekNo
OPTION (MAXRECURSION 0)

Finding the id of the nearest neighbour in SQL

I have a table, #geo, with points in geolocation.
Id geolocation
9201 0xE6100000010CE33995EB71164CC054791243B87441C0
9202 0xE6100000010C56B77A4E7A1B4CC0D15790662C6E41C0
I calculated the distance to the nearest neighbour for each data point.
I have 1000 points for 24 month. Now I replicate my code with the first 19 points in a month
create table #Geo
(
id int
,geolocation geography
)
INSERT INTO #geo (id, geolocation)
Select 224,0xE6100000010CE33995EB71164CC054791243B87441C0 UNION ALL
Select 225,0xE6100000010CE7D4BE4EA4184CC0CC947B26A07341C0 UNION ALL
Select 226,0xE6100000010C97A8DE1AD81A4CC0139B8F6B436941C0 UNION ALL
Select 227,0xE6100000010C2EAC1BEF8E164CC0DF80E03B7B7341C0 UNION ALL
Select 228,0xE6100000010CE49BD09887174CC00CADD206F57341C0 UNION ALL
Select 229,0xE6100000010C2B009DB436184CC0FD8E1B5D297441C0 UNION ALL
Select 230,0xE6100000010CFBAC32535A154CC054C72AA5677241C0 UNION ALL
Select 231,0xE6100000010CAE9E93DE37024CC0A167B3EA736141C0 UNION ALL
Select 232,0xE6100000010C70B1A206D3EC4BC0B4024356B76241C0 UNION ALL
Select 233,0xE6100000010CEA78CC40651C4CC097C5C4E6E30A41C0 UNION ALL
Select 234,0xE6100000010CDBFD2AC0770F4CC09E996038D76E41C0 UNION ALL
Select 235,0xE6100000010CA1CB487B8B794BC0C84C9AED277041C0 UNION ALL
Select 236,0xE6100000010CC0076D4108154CC07DD8A069E86E41C0 UNION ALL
Select 237,0xE6100000010C103B53E8BC1E4CC062670A9DD7E03FC0 UNION ALL
Select 238,0xE6100000010CDD94A1130A004CC0ACA6B697DEBB3FC0 UNION ALL
Select 239,0xE6100000010CAB750381252B4BC0F1DDFF2A343D41C0 UNION ALL
Select 240,0xE6100000010CD925AAB706CA4BC045813E91275D40C0 UNION ALL
Select 241,0xE6100000010CD1EB4FE2F3134BC014DA6ABD7C5441C0 UNION ALL
Select 242,0xE6100000010CB32A5F238B144CC0C3E37020037441C0
--select * from #Geo
select com.id
, min( com.GeoLocation.STDistance(com2.GeoLocation)) dist
from #geo com
join #geo com2 on com.id<>com2.id
group by com.id
id dist
224 608.936575787757
225 454.190509008084
... ...
Now I need to get the nearest neighbor's id:
id dist Id_with_minimum_distance
224 608.936575787757 ?
225 454.190509008084 ?
Thank you for your help.
You can use a subquery with row_number to filter out all except the nearest com2 rows:
select *
from (
select row_number() over (
partition by id1
order by dist) rn
, *
from (
select com1.id as id1
, com2.id as id2
, com1.GeoLocation.STDistance(com2.GeoLocation) as dist
from geo com1
join geo com2
on com1.id <> com2.id
) sub1
) sub2
where rn = 1 -- Only nearest com2
Example at SQL Fiddle.

How to get all numbers between a range

I have a table as below
Id RFrom RTo
.... ....... .....
1 10 14
1 22 25
2 100 102
2 176 180
I want to get all numbers between each RFrom and RTo for each Id. My expected result is as follows
Id NUMS
.... ......
1 10
1 11
1 12
1 13
1 14
1 22
1 23
1 24
1 25
2 100
2 101
2 102
2 176
2 177
2 178
2 179
2 180
Do I have to use cursor to achieve this?
Here is your sample table
SELECT * INTO #TEMP FROM
(
SELECT 1 ID, 10 RFROM, 14 RTO
UNION ALL
SELECT 1, 22, 25
UNION ALL
SELECT 2, 100, 102
UNION ALL
SELECT 2, 176, 180
)TAB
You need to use recursion for each Id to get the result
;WITH CTE AS
(
SELECT ID,RFROM RFROM1,RTO RTO1
FROM #TEMP
UNION ALL
SELECT T.ID,RFROM1+1,RTO1
FROM #TEMP T
JOIN CTE ON CTE.ID = T.ID
WHERE RFROM1 < RTO1
)
SELECT DISTINCT ID,RFROM1 NUMS
FROM CTE
SQL FIDDLE
Another option would be to use a numbers table with a join -- recursion can be time consuming.
There are several options to create a numbers table (I'd recommend creating a permanent one), but here's a temp one created with a common-table-expression:
with numberstable as (
select top 10000 row_number() over(order by t1.number) as number
from master..spt_values t1
cross join master..spt_values t2
)
select yt.id,
nt.number
from yourtable yt
join numberstable nt on nt.number between yt.rfrom and yt.rto
SQL Fiddle Demo
Create a tally table using stacked CTE which will have better performance when compared to recursive CTE
declare #min int
select #min= min(RFrom) from yourtable
;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 e1 CROSS JOIN e2) -- 10*100
SELECT b.id,
a.n
FROM yourtable b
JOIN (SELECT n = Row_number()OVER (ORDER BY n)+ #min-1
FROM e3)a
ON a.n BETWEEN b.RFrom AND b.RTo
ORDER BY n;
Check here for info

Resources