How to re write while loop using cte - sql-server

I have two tables, one with Events, the other with episodes.
An Episode has a start date and end date, the event has a single date.
Both Episodes and Events have one of six Types.
Currently I'm using some fuzzy logic to run an update script on the Events table to set it's ID field to the matching Episode. It does this by checking for the Event date between the Episode start and end, both having the same Type, as well as some other links like same User etc.
Since the Events can sit outside of the Episode, or have a different Type, what I do is loop through a sequence of expanding date ranges (StartDate-1, -2 etc) and also cycle through each Type looking for a match.
I've been reading that while loops aren't very efficient, so was wondering if there was a way to rewrite this nested loop into a CTE function.
I'm using SQL Server 2012.
Event List is just a temp table that has all the possible Types with an order to loop through.
My loop currently is:
WHILE #CurrBefore <= #Before and #CurrentAfter <= #After
BEGIN
SET #Row = 0
WHILE #Row <= #MaxRow
BEGIN
UPDATE P
SET P.ID = E.ID
FROM Event P
OUTER APPLY (SELECT TOP 1 E.Id, E.Type
FROM Episode E
WHERE E.User = P.User AND
E.Type = CASE WHEN #Row=0 THEN P.Event ELSE (SELECT Event FROM #EventList WHERE RN = #Row) END AND
P.Date BETWEEN E.StartDate-#CurrentBefore AND E.EndDate+#CurrentAfter
ORDER BY P.Date) E
WHERE P.ID = 0
INCREMENT #ROW CODE
END
INCREMENT #BEFORE/AFTER CODE
END
Sample Data:
IF OBJECT_ID('tempdb..#EventList') IS NOT NULL
BEGIN
DROP TABLE #EventList
CREATE TABLE #EventList(Event Varchar(50), RN INT);
INSERT INTO #EventList SELECT 'A', 1
INSERT INTO #EventList SELECT 'B', 2
INSERT INTO #EventList SELECT 'C', 3
INSERT INTO #EventList SELECT 'D', 4
INSERT INTO #EventList SELECT 'E', 5
INSERT INTO #EventList SELECT 'F', 6
END
CREATE TABLE dbo.Episode ([ID] INT, [Start] DateTime, [End] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].Episode ([ID], [Start], [End], [Type],[User])
VALUES
(1, '2018-07-01 10:00', '2018-07-02 14:00', 'A',10),
(2, '2018-07-05 6:00', '2018-07-06 13:00', 'A',11),
(3, '2018-07-03 9:00', '2018-07-04 8:00', 'B',10),
(4, '2018-07-02 15:00', '2018-07-03 7:00', 'B',12),
(5, '2018-07-01 1:00', '2018-07-02 8:00', 'C',13),
(6, '2018-07-01 6:00', '2018-07-01 8:00', 'D',11)
CREATE TABLE dbo.Event ([ID] INT, [Date] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].Event ([ID], [Date], [Type],[User])
VALUES
(0, '2018-07-01 12:00', 'A',10),
(0, '2018-07-05 15:00', 'A',11),
(0, '2018-07-03 13:00', 'C',10),
(0, '2018-07-10 9:00', 'B',12),
(0, '2018-07-01 5:00', 'C',10),
(0, '2018-07-01 10:00', 'D',11)
Expected result, Event now looks like this:
1 2018-07-01 12:00:00.000 A 10
2 2018-07-05 15:00:00.000 A 11
3 2018-07-03 13:00:00.000 C 10
0 2018-07-10 09:00:00.000 B 12
1 2018-07-01 05:00:00.000 C 10
6 2018-07-01 10:00:00.000 D 11

I don't know, if I fully got the logic, but this might help to get you running:
USE master;
GO
CREATE DATABASE TestDB
GO
USE TestDB;
GO
CREATE TABLE dbo.Episode ([ID] INT, [Start] DateTime, [End] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].Episode ([ID], [Start], [End], [Type],[User])
VALUES
(1, '2018-07-01 10:00', '2018-07-02 14:00', 'A',10),
(2, '2018-07-05 6:00', '2018-07-06 13:00', 'A',11),
(3, '2018-07-03 9:00', '2018-07-04 8:00', 'B',10),
(4, '2018-07-02 15:00', '2018-07-03 7:00', 'B',12),
(5, '2018-07-01 1:00', '2018-07-02 8:00', 'C',13),
(6, '2018-07-01 6:00', '2018-07-01 8:00', 'D',11)
CREATE TABLE dbo.[Event] ([ID] INT, [Date] DateTime, [Type] varchar(1), [User] INT)
INSERT INTO [dbo].[Event] ([ID], [Date], [Type],[User])
VALUES
(0, '2018-07-01 12:00', 'A',10),
(0, '2018-07-05 15:00', 'A',11),
(0, '2018-07-03 13:00', 'C',10),
(0, '2018-07-10 9:00', 'B',12),
(0, '2018-07-01 5:00', 'C',10),
(0, '2018-07-01 10:00', 'D',11)
GO
CREATE TABLE #EventList(Event Varchar(50), RN INT);
INSERT INTO #EventList VALUES ('A', 1),('B', 2),('C', 3),('D', 4),('E', 5),('F', 6);
WITH mathingEpisodes AS
(
SELECT ev.ID AS evID
,ev.[Date] AS evDate
,ev.[Type] AS evType
,ev.[User] AS evUser
,e1.RN AS evRN
,ep.ID AS epID
,ep.[Type] AS epType
,e2.RN AS epRN
FROM [Event] ev
LEFT JOIN Episode ep ON ev.[User]=ep.[User] AND ev.[Date] >= ep.[Start] AND ev.[Date] < ep.[End]
LEFT JOIN #EventList e1 ON ev.[Type]=e1.[Event]
LEFT JOIN #EventList e2 ON ep.[Type]=e2.[Event]
)
SELECT COALESCE(epID,Closest.ID) AS FittingEpisodeID
,me.evDate
,evType
,evUser
FROM mathingEpisodes me
OUTER APPLY(SELECT TOP 1 *
FROM Episode ep
CROSS APPLY(SELECT ABS(DATEDIFF(SECOND,me.evDate,ep.[Start])) AS DiffToStart
,ABS(DATEDIFF(SECOND,me.evDate,ep.[End])) AS DiffToEnd) Diffs
CROSS APPLY(SELECT CASE WHEN DiffToStart<DiffToEnd THEN DiffToStart ELSE DiffToEnd END AS Smaller) Diffs2
WHERE ep.[User] = me.evUser
AND me.epID IS NULL
ORDER BY Diffs2.Smaller
) Closest
ORDER BY evDate;
GO
USE master;
GO
DROP DATABASE TestDB;
GO
DROP TABLE #EventList
GO
The result
1 2018-01-07 05:00:00.000 C 10
6 2018-01-07 10:00:00.000 D 11
1 2018-01-07 12:00:00.000 A 10
3 2018-03-07 13:00:00.000 C 10
2 2018-05-07 15:00:00.000 A 11
4 2018-10-07 09:00:00.000 B 12
Some explanation
In the first cte I try to find fitting episodes (same user and date within range).
The second cte will compute the closest Episode for the same user in all cases, where the first cte did not succeed.
The only difference for this sample is the event for userId=12. My logic will bind this to the closest episode of this user (ID=4), while your expected output shows a zero in this place.
Anyway, my solution is fully set-based, therefore faster than a loop, and should be rather close to your needs. Try to adapt it...
UPDATE Some more thoughts...
I did not get the ghist of your #EventList... I bound the results into the set (you can make it visible by using SELECT * instead of the explicit column list. But this is - assumably - not what you meant...

Related

Find the date when a milestone was achieved

I have people that do many multi-day assignments (date x to date Y). I would like to find the date that they completed a milestone e.g. 50 days work completed.
Data is stored as a single row per Assignment
AssignmentId
StartDate
EndDate
I can sum up the total days they have completed up to a date, but am struggling to see how I would find out the date that a milestone was hit. e.g. How many people completed 50 days in October 2020 showing the date within the month that this occurred?
Thanks in advance
PS. Our database is SQL Server.
As mentioned by prwvious comments, it would be much easier to help you if you could provide example data and table structure in order help you answer this question.
However, guessing a simple DB structure with a table for your peolple, your tasks and the work each user completed, you can get the required sum of days by use of a date table (or cte) which contains a entry for each day and the window function SUM with UNBOUNDED PRECEDING. Following an example:
DECLARE #people TABLE(
id int
,name nvarchar(50)
)
DECLARE #tasks TABLE(
id int
,name nvarchar(50)
)
DECLARE #work TABLE(
people_id int
,task_id int
,task_StartDate date
,task_EndDate date
)
INSERT INTO #people VALUES (1, 'Peter'), (2, 'Paul'), (3, 'Mary');
INSERT INTO #tasks VALUES (1, 'Devleopment'), (2, 'QA'), (3, 'Sales');
INSERT INTO #work VALUES
(1, 1, '2019-04-05', '2019-04-08')
,(1, 1, '2019-05-05', '2019-06-08')
,(1, 1, '2019-07-05', '2019-09-08')
,(2, 2, '2019-04-08', '2019-06-08')
,(2, 2, '2019-09-08', '2019-10-03')
,(3, 1, '2019-11-01', '2019-12-01')
;WITH cte AS(
SELECT CAST('2019-01-01' AS DATE) AS dateday
UNION ALL
SELECT DATEADD(d, 1, dateday)
FROM cte
WHERE DATEADD(d, 1, dateday) < '2020-01-01'
),
cteWorkDays AS(
SELECT people_id, task_id, dateday, 1 AS cnt
FROM #work w
INNER JOIN cte c ON c.dateday BETWEEN w.task_StartDate AND w.task_EndDate
),
ctePeopleWorkdays AS(
SELECT *, SUM(cnt) OVER (PARTITION BY people_id ORDER BY dateday ROWS UNBOUNDED PRECEDING) dayCnt
FROM cteWorkDays
)
SELECT *
FROM ctePeopleWorkdays
WHERE dayCnt = 50
OPTION (MAXRECURSION 0)
The solution depends on how you store your data. The solution below assumes that each worked day exists as a single row in your data model.
The approach below uses a common table expression (cte) to generate a running total (Total) for each person (PersonId) and then filters on the milestone target (I set it to 5 to reduce the sample data size) and target month.
Sample data
create table WorkedDays
(
PersonId int,
TaskDate date
);
insert into WorkedDays (PersonId, TaskDate) values
(100, '2020-09-01'),
(100, '2020-09-02'),
(100, '2020-09-03'),
(100, '2020-09-04'),
(100, '2020-09-05'), -- person 100 worked 5 days by 2020-09-05 = milestone (in september)
(200, '2020-09-29'),
(200, '2020-09-30'),
(200, '2020-10-01'),
(200, '2020-10-02'),
(200, '2020-10-03'), -- person 200 worked 5 days by 2020-10-03 = milestone (in october)
(200, '2020-10-04'),
(200, '2020-10-05'),
(200, '2020-10-06'),
(300, '2020-10-10'),
(300, '2020-10-11'),
(300, '2020-10-12'),
(300, '2020-10-13'),
(300, '2020-10-14'), -- person 300 worked 5 days by 2020-10-14 = milestone (in october)
(300, '2020-10-15'),
(400, '2020-10-20'),
(400, '2020-10-21'); -- person 400 did not reach the milestone yet
Solution
with cte as
(
select wd.PersonId,
wd.TaskDate,
count(1) over(partition by wd.PersonId
order by wd.TaskDate
rows between unbounded preceding and current row) as Total
from WorkedDays wd
)
select cte.PersonId,
cte.TaskDate as MileStoneDate
from cte
where cte.Total = 5 -- milestone reached
and year(cte.TaskDate) = 2020
and month(cte.TaskDate) = 10; -- in october
Result
PersonId MilestoneDate
-------- -------------
200 2020-10-03
300 2020-10-14
Fiddle (also shows the common table expression output).

calculate car parking hours in sql server

I have one doubt in sql server. How to calculate car login and logout time details?
table: cardetails
Here need to calculate each car how many hours spend in parking area.
creatE TABLE [dbo].[CarDetails](
[carid] [int] NULL,
[DateTimeDetail] [datetime] NULL,
[Flag] [varchar](50) NULL
)
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (1, CAST(N'2019-01-20T19:05:00.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (1, CAST(N'2019-01-20T22:30:00.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-20T20:30:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T02:10:10.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T07:07:40.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T10:50:40.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T11:00:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T14:15:30.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T08:20:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T10:20:10.000' AS DateTime), N'out')
GO
These are the records:
carid DateTimeDetail Flag
1 2019-01-20 19:05:00.000 in
1 2019-01-20 22:30:00.000 out
2 2019-01-20 20:30:10.000 in
2 2019-01-21 02:10:10.000 out
3 2019-01-23 07:07:40.000 in
3 2019-01-23 10:50:40.000 out
3 2019-01-23 11:00:10.000 in
3 2019-01-23 14:15:30.000 out
2 2019-01-21 08:20:10.000 in
2 2019-01-21 10:20:10.000 out
Based on above data I want output like below :
carid | DateTimeDetails | Totaltime(hh:mm:ss)
1 |2019-01-20 | 03:25:00
2 |2019-01-20 | 05 :49:40
2 |2019-01-21 | 02:00:00
3 |2019-01-23 | 06:58:20
I tried like below
select a.carid , sum(datediff(mm,b.datetimedetail,a.datetimedetail))as totalmm from CarDetails a join CarDetails b
on a.carid=b.carid
where a.datetimedetail<=(select max(c.[DateTimeDetail]) from CarDetails c join CarDetails a on a.carid=c.carid
)
group by a.carid
please tell me how to write query to achive this task in sql server
This doesn't quite give you the expected results in your post, however, I have good reason for it not to be, and I suspect your expected results are wrong:
--Because, from experience, people can somehow enter things twice before exiting...
WITH Grp AS(
SELECT CD.carid,
CD.DateTimeDetail,
CD.Flag,
COUNT(CASE Flag WHEN 'Out' THEN 1 END) OVER (PARTITION BY carid ORDER BY CD.DateTimeDetail ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS Grp
FROM dbo.CarDetails CD),
InOut AS(
SELECT carid,
MIN(CASE Flag WHEN 'In' THEN Grp.DateTimeDetail END) AS TimeIn,
MAX(CASE Flag WHEN 'Out' THEN Grp.DateTimeDetail END) AS [TimeOut]
FROM Grp
GROUP BY carid, grp)
SELECT carid,
CONVERT(date,TimeIn) AS DateTimeDetails,
CONVERT(time(0),DATEADD(SECOND,SUM(DATEDIFF(SECOND,TimeIn, [TimeOut])),'00:00:00')) AS TotalTime
FROM InOut
GROUP BY carid,
CONVERT(date,TimeIn)
ORDER BY carid ASC;
This gives the results below:
carid DateTimeDetails TotalTime
----------- --------------- ----------------
1 2019-01-20 03:25:00
2 2019-01-20 05:40:00
2 2019-01-21 02:00:00
3 2019-01-23 06:58:20
Notice I have a 05:40:00 for car 2 on 2019-01-20. Car 2 enters at 20:30:10 and leaves at 02:10:10, which is 5 hours, and 40 minutes later; not 05 hours 49 minutes and 40 seconds later. if you have that time in your expected results for a reason, you need to explain why.
Note: This will not work if a car stays more than 24 hours! You didn't respond to my question, so I have assumed not. If they can, SQL Server does not support times great than 24 hours, therefore you will be better returning the number of seconds, and having your application display a 24+ hour time.
you can try this
select carid,dateIn, cast(DATEADD(ms, SUM(DATEDIFF(ms, '00:00:00.000', timeout)), '00:00:00.000') as time) timeOut
from (
select carid,cast([DateTimeDetail]as DATE) DateIn ,
(select top 1 [DateTimeDetail]
from [CarDetails] b
where b.carid=a.carid and b.[DateTimeDetail]>a.[DateTimeDetail] and flag='out' )
-[DateTimeDetail] timeOut
from [CarDetails] a
where Flag='in') c
group by carid,dateIn
This gives the results below:
carid dateIn timeOut
1 2019-01-20 03:25:00.0000000
2 2019-01-20 05:40:00.0000000
2 2019-01-21 02:00:00.0000000
3 2019-01-23 06:58:20.0000000

Total Number of Leaves of same type in a month

I have 2 tables name EmployeeInfo and Leave and I am storing the values that which employee have taken which type of leave in month and how many times.
I am trying to calculate the number of leaves of same type but I'm stuck at one point for long time.
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('Leave'))
BEGIN;
DROP TABLE [Leave];
END;
GO
IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID('EmployeeInfo'))
BEGIN;
DROP TABLE [EmployeeInfo];
END;
GO
CREATE TABLE [EmployeeInfo] (
[EmpID] INT NOT NULL PRIMARY KEY,
[EmployeeName] VARCHAR(255)
);
CREATE TABLE [Leave] (
[LeaveID] INT NOT NULL PRIMARY KEY,
[LeaveType] VARCHAR(255) NULL,
[DateFrom] VARCHAR(255),
[DateTo] VARCHAR(255),
[Approved] Binary,
[EmpID] INT FOREIGN KEY REFERENCES EmployeeInfo(EmpID)
);
GO
INSERT INTO EmployeeInfo([EmpID], [EmployeeName]) VALUES
(1, 'Marcia'),
(2, 'Lacey'),
(3, 'Fay'),
(4, 'Mohammad'),
(5, 'Mike')
INSERT INTO Leave([LeaveID],[LeaveType],[DateFrom],[DateTo], [Approved], [EmpID]) VALUES
(1, 'Annual Leave','2018-01-08 04:52:03','2018-01-10 20:30:53', 1, 1),
(2, 'Sick Leave','2018-02-10 03:34:41','2018-02-14 04:52:14', 0, 2),
(3, 'Casual Leave','2018-01-04 11:06:18','2018-01-05 04:11:00', 1, 3),
(4, 'Annual Leave','2018-01-17 17:09:34','2018-01-21 14:30:44', 0, 4),
(5, 'Casual Leave','2018-01-09 23:31:16','2018-01-12 15:11:17', 1, 3),
(6, 'Annual Leave','2018-02-16 18:01:03','2018-02-19 17:16:04', 1, 2)
My query which I have tried so far look something like this.
SELECT Info.EmployeeName, Leave.LeaveType, SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves], DatePart(MONTH, Leave.DateFrom)
FROM EmployeeInfo Info, Leave
WHERE Info.EmpID = Leave.EmpID AND Approved = 1
GROUP BY Info.EmployeeName, Leave.LeaveType, [Leave].[DateFrom], [Leave].[DateTo]
And the record like given below
EmployeeName LeaveType #OfLeaves MonthNumber
-------------- ----------------- ----------- -----------
Fay Casual Leave 1 1
Fay Casual Leave 3 1
Lacey Annual Leave 3 2
Marcia Annual Leave 2 1
I want the record to look like this
EmployeeName LeaveType #OfLeaves MonthNumber
-------------- ----------------- ----------- -----------
Fay Casual Leave 4 1
Lacey Annual Leave 3 2
Marcia Annual Leave 2 1
If you don't want to modify existing query due to some constraint, this might work:
Select iq.EmployeeName, iq.LeaveType, SUM(iq.#OfLeaves) as #OfLeaves, iq.MonthNumber
From (
SELECT Info.EmployeeName, Leave.LeaveType, SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves], DatePart(MONTH, Leave.DateFrom) as MonthNumber
FROM EmployeeInfo Info, Leave
WHERE Info.EmpID = Leave.EmpID AND Approved = 1
GROUP BY Info.EmployeeName, Leave.LeaveType, [Leave].[DateFrom], [Leave].[DateTo]
)iq
group by iq.EmployeeName, iq.LeaveType, iq.MonthNumber
This just need small adjustment with your query in the GROUP BY clause. Instead of grouping them by [Leave].[DateFrom] and [Leave].[DateTo] which causes the row to be separated, you need to group it with the calculated column that uses datepart.
SELECT Info.EmployeeName,
Leave.LeaveType,
SUM(DATEDIFF(Day, Leave.DateFrom, Leave.DateTo)) [#OfLeaves],
DatePart(MONTH, Leave.DateFrom)
FROM EmployeeInfo Info
INNER JOIN Leave
ON Info.EmpID = Leave.EmpID
WHERE Approved = 1
GROUP BY Info.EmployeeName,
Leave.LeaveType,
DatePart(MONTH, Leave.DateFrom) -- <<<< change only this part
Here's a Demo.
I have also modified the syntax into ANSI format.

How to write a case when statement when there are overlaps in T-SQL

I have a table like this
How can I group it to this
Small is the sum of the count when Count <25; Large is the sum of the count when Count>=25; Total is the sum of all counts.
Try it like this...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
ID INT NOT NULL PRIMARY KEY,
nCount int NOT NULL
);
INSERT #TestData (ID, nCount) VALUES
(1, 10), (2, 15), (3, 22), (4, 23),
(5, 25), (6, 27), (7, 30);
--=====================================
WITH
cte_Totals AS (
SELECT
Total = SUM(td.nCount),
Small = SUM(CASE WHEN td.nCount < 25 THEN td.nCount ELSE 0 END),
Large = SUM(CASE WHEN td.nCount >= 25 THEN td.nCount ELSE 0 END)
FROM
#TestData td
)
SELECT
x.[Group],
x.[Count]
FROM
cte_Totals t
CROSS APPLY (VALUES (1, 'Total', t.Total), (2, 'Small', t.Small), (3, 'Large', t.Large) ) x (SortBy, [Group],[Count])
ORDER BY
x.SortBy;
Results...
Group Count
----- -----------
Total 152
Small 70
Large 82
HTH,
Jason
The simplest way is to use CASE:
SELECT
SUM(Count) as Total,
SUM(CASE WHEN Count < 25 THEN Count ELSE 0 END) as Small,
SUM(CASE WHEN Count >= 25 THEN Count ELSE 0 END) as Large
FROM table
Late answer (keep the accepted as is), but I did want to introduce a concept which may be more helpful down the line.
I maintain a generic Tier Table. The following is a simplified example, but you can take the aggregation tiers out of the code, and put it in a table... things change, and you can serve multiple masters.
Sample Data
Declare #YourTable table (ID int,[Count] int)
Insert Into #YourTable values
(1, 10), (2, 15), (3, 22), (4, 23), (5, 25), (6, 27), (7, 30)
Declare #Tier table (Tier varchar(50),Seq int,Title varchar(50),R1 int,R2 int)
Insert Into #Tier values
('MyGroup',1,'Total',0,99999)
,('MyGroup',2,'Small',0,25)
,('MyGroup',3,'Large',25,99999)
The Actual Query
Select T.Title
,[Count] = sum(D.[Count])
From #Tier T
Join #YourTable D on (T.Tier='MyGroup' and D.Count >= T.R1 and D.Count<T.R2)
Group By T.Title,T.Seq
Order By T.Seq
Returns
Title Count
Total 152
Small 70
Large 82
EDIT - There are many ways you can construct this
Example
Declare #YourTable table (ID varchar(50),[Count] int)
Insert Into #YourTable values
('Tywin', 10), ('Tywin', 15), ('Tyrion', 22), ('Bran', 23), ('Ned', 25), ('John', 27), ('Robb', 30)
Declare #Tier table (Tier varchar(50),Seq int,Title varchar(50),R1 int,R2 int,C1 varchar(50),C2 varchar(50))
Insert Into #Tier values
('MyGroup',1,'Total' ,null,null,'a','z')
,('MyGroup',2,'Group 1',null,null,'Tywin,Tyrion',null)
,('MyGroup',3,'Group 2',null,null,'Bran,Ned,John,Robb',null)
Select T.Title
,[Count] = sum(D.[Count])
From #Tier T
Join #YourTable D on T.Tier='MyGroup' and (D.ID between C1 and C2 or patindex('%,'+D.ID+',%',','+C1+',')>0)
Group By T.Title,T.Seq
Order By T.Seq
Returns
Title Count
Total 152
Group 1 47
Group 2 105

In T-SQL is there a built-in command to determine if a number is in a range from another table

This is not a homework question.
I'm trying to take the count of t-shirts in an order and see which price range the shirts fall into, depending on how many have been ordered.
My initial thought (I am brand new at this) was to ask another table if count > 1st price range's maximum, and if so, keep looking until it's not.
printing_range_max printing_price_by_range
15 4
24 3
33 2
So for example here, if the order count is 30 shirts they would be $2 each.
When I'm looking into how to do that, it looks like most people are using BETWEEN or IF and hard-coding the ranges instead of looking in another table. I imagine in a business setting it's best to be able to leave the range in its own table so it can be changed more easily. Is there a good/built-in way to do this or should I just write it in with a BETWEEN command or IF statements?
EDIT:
SQL Server 2014
Let's say we have this table:
DECLARE #priceRanges TABLE(printing_range_max tinyint, printing_price_by_range tinyint);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
You can create a table with ranges that represent the correct price. Below is how you would do this in pre-2012 and post-2012 systems:
DECLARE #priceRanges TABLE(printing_range_max tinyint, printing_price_by_range tinyint);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
-- post-2012 using LAG
WITH pricerange AS
(
SELECT
printing_range_min = LAG(printing_range_max, 1, 0) OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
)
SELECT * FROM pricerange;
-- pre-2012 using ROW_NUMBER and a self-join
WITH prices AS
(
SELECT
rn = ROW_NUMBER() OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
),
pricerange As
(
SELECT
printing_range_min = ISNULL(p2.printing_range_max, 0),
printing_range_max = p1.printing_range_max,
p1.printing_price_by_range
FROM prices p1
LEFT JOIN prices p2 ON p1.rn = p2.rn+1
)
SELECT * FROM pricerange;
Both queries return:
printing_range_min printing_range_max printing_price_by_range
------------------ ------------------ -----------------------
0 15 4
15 24 3
24 33 2
Now that you have that you can use BETWEEN for your join. Here's the full solution:
-- Sample data
DECLARE #priceRanges TABLE
(
printing_range_max tinyint,
printing_price_by_range tinyint
-- if you're on 2014+
,INDEX ix_xxx NONCLUSTERED(printing_range_max, printing_price_by_range)
-- note: second column should be an INCLUDE but not supported in table variables
);
DECLARE #orders TABLE
(
orderid int identity,
ordercount int
-- if you're on 2014+
,INDEX ix_xxy NONCLUSTERED(orderid, ordercount)
-- note: second column should be an INCLUDE but not supported in table variables
);
INSERT #priceRanges VALUES (15, 4), (24, 3), (33, 2);
INSERT #orders(ordercount) VALUES (10), (20), (25), (30);
-- Solution:
WITH pricerange AS
(
SELECT
printing_range_min = LAG(printing_range_max, 1, 0) OVER (ORDER BY printing_range_max),
printing_range_max,
printing_price_by_range
FROM #priceRanges
)
SELECT
o.orderid,
o.ordercount,
--p.printing_range_min,
--p.printing_range_max
p.printing_price_by_range
FROM pricerange p
JOIN #orders o ON o.ordercount BETWEEN printing_range_min AND printing_range_max
Results:
orderid ordercount printing_price_by_range
----------- ----------- -----------------------
1 10 4
2 20 3
3 25 2
4 30 2
Now that we have that we can

Resources