Show records where team assignment = 1 - sql

by changing the below TeamRecords = 1 to = another number finds the rows with the amount I change to, its only sometimes its counting one too many which is odd. When a new Incident is created it has a unique number and every time a new assignment is added from the Task table it adds another row of the IncidentNumber, so you could have duplicate Incident number rows which I've remove with the seq = 1 below. When a new assignment is created it creates a new CreateddateTime in the Task table so for example you could do a Max(t.[CreatedDateTime] to find the last assignment of any IncidentNumber. So, the TeamRecords = 1 is what I need to find all records for that specific team where there is only 1 assignment for that team.
Does that help any?
Here is what I have so far...
Use TEST
Go
WITH RankResult AS
(
SELECT i.[IncidentNumber],
i.[CreatedDateTime],
i.[ResolutionDateAndTime],
i.[Priority],
i.[Status],
i.[ClientName],
i.[ClientSite],
t.[OwnerTeam],
t.[Owner],
row_number() over( partition by i.RecID
order by t.CreatedDateTime desc, t.OwnerTeam ) seq,
TeamRecords = COUNT(*) OVER(PARTITION BY t.ParentLink_RecID)
FROM Incident as i
Inner JOIN Task as t
ON i.RecID = t.ParentLink_RecID
WHERE t.OwnerTeam = 'Infrastructure Services'
AND i.CreatedDateTime >= '20121001'
AND i.CreatedDateTime <= '20131001'
)
SELECT DISTINCT
[IncidentNumber],
[CreatedDateTime],
[ResolutionDateAndTime],
[Priority],
[Status],
[ClientName],
[ClientSite],
[OwnerTeam],
[Owner]
FROM RankResult
Where TeamRecords = 1
And Seq = 1
Order By IncidentNumber Asc
GO

Using ROW_NUMBER means you will return the first assignment per team, not necessarily the teams with only one assignment. To do this you can use COUNT(*) OVER():
WITH RankResult AS
(
SELECT i.[IncidentNumber],
i.[CreatedDateTime],
i.[ResolutionDateAndTime],
i.[Priority],
i.[Status],
i.[ClientName],
i.[ClientSite],
t.[OwnerTeam],
t.[Owner],
TeamRecords = COUNT(*) OVER(PARTITION BY i.RecID)
FROM Incident as i
INNER JOIN Task as t
ON i.RecID = t.ParentLink_RecID
WHERE t.OwnerTeam = 'Info Services'
AND i.CreatedDateTime >= '20121001'
AND i.CreatedDateTime <= '20131001'
)
SELECT DISTINCT
[IncidentNumber],
[CreatedDateTime],
[ResolutionDateAndTime],
[Priority],
[Status],
[ClientName],
[ClientSite],
[OwnerTeam],
[Owner]
FROM RankResult
WHERE TeamRecords = 1;
2 things to note that I have changed in addition to the analytic function. Firstly I have changed your dates to the culture independant format yyyyMMdd, yyyy-MM-dd can still be ambiguous, so 2013-01-02 could be the 1st Feb or the 2nd Jan depending on your server/session settings. Secondly, Your where cluase was turning your join into an INNER JOIN anyway, so I just made it an INNER JOIN:
FROM Incident as i
LEFT JOIN Task as t
ON i.RecID = t.ParentLink_RecID
WHERE t.OwnerTeam = 'Info Services'
AND i.CreatedDateTime >= '20121001'
Here, if there is no match in Task then OwnerTeam will be NULL and NULL = 'Info Services' evaluates to false, so you will never return any rows with no match in Task thus making it an INNER JOIN). If you did in fact want a LEFT JOIN then you need to move this clause to the JOIN:
FROM Incident as i
LEFT JOIN Task as t
ON i.RecID = t.ParentLink_RecID
AND t.OwnerTeam = 'Info Services'
WHERE i.CreatedDateTime >= '20121001'

You can always use a simple subquery which groups across the relevant columns, counts them, filters where the count is 1, then joins it back to the main table to select the appropriate rows:
SELECT Incident.*
FROM (
SELECT OwnerTeam
FROM Incident AS Inc1
GROUP BY OwnerTeam
HAVING COUNT( * ) = 1
) AS Team
, Incident
WHERE Incident.OwnerTeam = Team.OwnerTeam
(Without more information, though, it's difficult to say if this will work for you.)

Related

How to use 1 SQL query related to date and time in order to compare value difference

SELECT c.treatment_category, a.treatment_id, MAX(a.counts - b.counts) AS ReviewDifference
FROM
(SELECT treatment_id, COUNT(treatment_id) AS counts
FROM review
WHERE DATE(review.created) BETWEEN DATE(TIMESTAMP'2016-01-01 00:00:00.0') AND DATE(TIMESTAMP'2016-12-31 23:59:59.999')
GROUP BY treatment_id) a
LEFT JOIN
(SELECT treatment_id, COUNT(treatment_id)
FROM review
WHERE DATE(review.created) BETWEEN DATE(TIMESTAMP'2015-01-01 00:00:00.0') AND DATE(TIMESTAMP'2015-12-31 23:59:59.999')
GROUP BY treatment_id) b
ON a = b
LEFT JOIN
(SELECT t.treatment_category AS category, r.treatment_id AS number
FROM treatment t
LEFT JOIN review r
ON t.treatment_id = r.treatment_id
GROUP BY category, number) c
ON b.treatment_id = c.number
GROUP BY a.treatment_id, c.treatment_category
ORDER BY ReviewDifference DESC
LIMIT 1;
I need some hints or simpler query on how to do this question since it is related to date and time. Thank you.
What treatment category has seen the biggest increase in reviews from 2015 to 2016?
Please see below for the tables.
I have provided my code snippet and I would like to find a simpler and cleaner way on writing the code.
SELECT t.treatment_id, t.treatment_name,
COUNT( CASE WHEN YEAR(created) = 2016 THEN r.review_id END)
- COUNT( CASE WHEN YEAR(created) = 2015 THEN r.review_id END) as review_count
FROM treatments t
JOIN reviews r
ON t.treatment_id = r.treatment_id
GROUP BY t.treatment_id, t.treatment_name,
ORDER BY review_count DESC

Filter with Dates but keep the calculation

I would like to know what can you do in the following scenario:
Lets say I am filtering on a date in the where clause (between eomonth(#StartDate) and eomonth(getdate()-1). I have a calculated column that is correct when I run the query without any filter, but the problem is that when I filter lets say #StartDate = 06/30/2017 then the calculations will obviously change. Is there any way of doing this?
The calculated column is a windowing function.
Edited:
I have added a picture of the data. So I am using a windowing function to calculate the agentfourmonthperiod. You will see that it sums the units for that month and the 3 previous months. This I dont want to change when filtering. So the units and agentfourmonthperiod columns should stay exactly the same after filtering on the #StartDate. Please see data below:
I want to design an SSRS report that the user will be filtering on the #StartDate, but then it should show the calculation as it does when no filter in used.
Hope this makes sense. Otherwise I can add the code. Its just quite long though.
Code:
WITH DATA
AS
(
SELECT
EOMONTH(SubmissionDates.original_date_c) AS IntakeMonth,
ProvincialArea.SAD_ProvMananger AS ProvManager,
RegionalArea.SAD_RegMananger AS RegManager,
SalesArea.SAD_SalesManager AS AreaSalesManager,
ConsultantUserExt.name AS Consultant,
COUNT(LeadsLink.LeadsID) OVER(PARTITION BY ConsultantUserExt.name, EOMONTH(SubmissionDates.original_date_c,0) ORDER BY EOMONTH(SubmissionDates.original_date_c,0)) AS Unit,
ROW_NUMBER() OVER(PARTITION BY ConsultantUserExt.name, EOMONTH(SubmissionDates.original_date_c,0) ORDER BY EOMONTH(SubmissionDates.original_date_c,0)) AS rn
FROM Import.OobaApplication as Application
LEFT OUTER JOIN Import.OobaApplicant applicant ON application.ApplicationID = applicant.ApplicationID
AND applicant.PrincipleApplication = 'Y'
LEFT OUTER JOIN usr_userext_cstm ON Application.Consultant = usr_userext_cstm.comcorp_key_c
or Application.Consultant = usr_userext_cstm.deal_maker_key_c
or Application.Consultant = usr_userext_cstm.ops_key_c
LEFT OUTER JOIN usr_userext AS ConsultantUserExt ON usr_userext_cstm.id_c = ConsultantUserExt.id AND ConsultantUserExt.deleted = 0
LEFT OUTER JOIN usr_userext_cstm AS ConsultantUserExtCstm on ConsultantUserExt.id = ConsultantUserExtCstm.id_c
LEFT OUTER JOIN CapcubedInternalDB.dbo.ProvincialArea AS ProvincialArea ON ConsultantUserExtCstm.sad_provincialmanager_c = ProvincialArea.ID
LEFT OUTER JOIN CapcubedInternalDB.dbo.RegionalArea AS RegionalArea ON ConsultantUserExtCstm.sad_regionalmanager_c = RegionalArea.ID
LEFT OUTER JOIN CapcubedInternalDB.dbo.SalesArea AS SalesArea ON ConsultantUserExtCstm.sad_salesmanager_c = SalesArea.ID
LEFT OUTER JOIN CapcubedInternalDB.dbo.LeadsLink AS LeadsLink ON Application.ApplicationID = LeadsLink.GroupCode
LEFT OUTER JOIN suitecrmprod.dbo.leads AS SuiteLeads ON LeadsLink.LeadsID = SuiteLeads.ID
--Latest Bank Submission
LEFT OUTER JOIN (SELECT
bankSub.ApplicationID As BankSubAppID, bankSub.SubmissionDate,
bankSub.Bank, bankSub.RequiredLoanAmount,
bankSub.BankCode AS BankSubBankCode
FROM Import.OobaBankSubmission bankSub
LEFT OUTER JOIN Import.OobaBankSubmission later ON bankSub.ApplicationID = later.ApplicationID
AND bankSub.SubmissionDate > later.SubmissionDate
WHERE later.applicationID IS NULL) AS BankSub ON Application.ApplicationID = BankSub.BankSubAppID
LEFT OUTER JOIN ccrep_calendar_cstm AS SubmissionDates ON CONVERT(VARCHAR(10),BankSub.SubmissionDate,101) = SubmissionDates.original_date_c
WHERE SubmissionDates.cc_date_c BETWEEN COALESCE(EOMONTH(#StartDate), '01/31/2016') AND COALESCE(#EndDate, GETDATE(), -1)
AND ConsultantUserExtCstm.consultantstatus_c NOT LIKE 2
)
SELECT *
INTO #Rn
FROM DATA
WHERE rn = 1
SELECT i.IntakeMonth, c.ProvManager, c.RegManager, c.AreaSalesManager, c.Consultant, COALESCE(#Rn.Unit, 0) AS Unit
INTO #FillData
FROM (SELECT DISTINCT IntakeMonth FROM #Rn) AS i
CROSS JOIN
(SELECT DISTINCT Consultant, ProvManager, RegManager, AreaSalesManager FROM #Rn) AS c
LEFT OUTER JOIN #Rn ON #Rn.IntakeMonth = i.IntakeMonth AND #Rn.Consultant = c.Consultant
ORDER BY Consultant, IntakeMonth
SELECT
IntakeMonth,
Consultant,
Unit,
SUM(Unit) OVER(PARTITION BY Consultant ORDER BY Consultant ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS agentfourmonthperiod
FROM #FillData
WHERE ('(All)' IN (#ProvincialManager) OR (ProvManager IN (#ProvincialManager)))
AND ('(All)' IN (#RegionalManager) OR (RegManager IN (#RegionalManager)))
AND ('(All)' IN (#AreaSalesManager) OR (AreaSalesManager IN (#AreaSalesManager)))
AND ('(All)' IN (#Consultant) OR (Consultant IN (#Consultant)))
DROP TABLE #Rn
DROP TABLE #FillData
You could of course remove any filter on dates from the query and apply them directly in the tablix of your report. Obvously, this means that SQL Server has to return all the data each time the report is run, so I guess that this isn't what you want.
For the window function to have access to the previous 3 rows, you will have to include the previous 3 months in your calculation. To achieve this, change the first condition in the WHERE clause in the cte (data) to something like this:
SubmissionDates.cc_date_c
BETWEEN
ISNULL(DATEADD(month, DATEDIFF(month, 0, #StartDate)-3, 0), '01/10/2015')
AND
ISNULL(#EndDate, DATEADD(day, DATEDIFF(day, 0, GETDATE())-1, 0))
As I thought that your date filter logic was wrong, I changed it to include the dates from the beginning of the month rather than from the end.
Now that the previous 3 months are included, we can apply a filter in the end to exclude the previous months from display, but this has to be done after using the window function, for example with another cte:
WITH calc AS (
SELECT
IntakeMonth,
Consultant,
Unit,
SUM(Unit) OVER(PARTITION BY Consultant ORDER BY Consultant ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS agentfourmonthperiod
FROM #FillData
WHERE ('(All)' IN (#ProvincialManager) OR (ProvManager IN (#ProvincialManager)))
AND ('(All)' IN (#RegionalManager) OR (RegManager IN (#RegionalManager)))
AND ('(All)' IN (#AreaSalesManager) OR (AreaSalesManager IN (#AreaSalesManager)))
AND ('(All)' IN (#Consultant) OR (Consultant IN (#Consultant)))
)
SELECT IntakeMonth, Consultant, Unit, agentfourmonthperiod
FROM calc
WHERE IntakeMonth >= ISNULL(EOMONTH(#StartDate), '01/31/2016')

SQL Server / T-SQL : query optimization assistance

I have this QA logic that looks for errors into every AuditID within a RoomID to see if their AuditType were never marked Complete or if they have two complete statuses. Finally, it picks only the maximum AuditDate of the RoomIDs with errors to avoid showing multiple instances of the same RoomID, since there are many audits per room.
The issue is that the AUDIT table is very large and takes a long time to run. I was wondering if there is anyway to reach the same result faster.
Thank you in advance !
IF object_ID('tempdb..#AUDIT') is not null drop table #AUDIT
IF object_ID('tempdb..#ROOMS') is not null drop table #ROOMS
IF object_ID('tempdb..#COMPLETE') is not null drop table #COMPLETE
IF object_ID('tempdb..#FINALE') is not null drop table #FINALE
SELECT distinct
oc.HotelID, o.RoomID
INTO #ROOMS
FROM dbo.[rooms] o
LEFT OUTER JOIN dbo.[hotels] oc on o.HotelID = oc.HotelID
WHERE
o.[status] = '2'
AND o.orderType = '2'
SELECT
t.AuditID, t.RoomID, t.AuditDate, t.AuditType
INTO
#AUDIT
FROM
[dbo].[AUDIT] t
WHERE
t.RoomID IN (SELECT RoomID FROM #ROOMS)
SELECT
t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO
#COMPLETE
FROM
(SELECT
RoomID,
SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
FROM
#AUDIT
GROUP BY
RoomID) t1
INNER JOIN
#AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE
t1.CompleteStatus = 0
OR t1.CompleteStatus > 1
SELECT
o.HotelID, o.RoomID,
a.AuditID, a.RoomID, a.AuditDate, a.AuditType, a.CompleteStatus,
c.ClientNum
INTO
#FINALE
FROM
#ROOMS O
LEFT OUTER JOIN
#COMPLETE a on o.RoomID = a.RoomID
LEFT OUTER JOIN
[dbo].[clients] c on o.clientNum = c.clientNum
SELECT
t.*,
Complete_Error_Status = CASE WHEN t.CompleteStatus = 0
THEN 'Not Complete'
WHEN t.CompleteStatus > 1
THEN 'Complete More Than Once'
END
FROM
#FINALE t
INNER JOIN
(SELECT
RoomID, MAX(AuditDate) AS MaxDate
FROM
#FINALE
GROUP BY
RoomID) tm ON t.RoomID = tm.RoomID AND t.AuditDate = tm.MaxDate
One section you could improve would be this one. See the inline comments.
SELECT
t1.RoomID, t3.AuditType, t3.AuditDate, t3.AuditID, t1.CompleteStatus
INTO
#COMPLETE
FROM
(SELECT
RoomID,
COUNT(1) AS CompleteStatus
-- Use the above along with the WHERE clause below
-- so that you are aggregating fewer records and
-- avoiding a CASE statement. Remove this next line.
--SUM(CASE WHEN AuditType = 'Complete' THEN 1 ELSE 0 END) AS CompleteStatus
FROM
#AUDIT
WHERE
AuditType = 'Complete'
GROUP BY
RoomID) t1
INNER JOIN
#AUDIT t3 ON t1.RoomID = t3.RoomID
WHERE
t1.CompleteStatus = 0
OR t1.CompleteStatus > 1
Just a thought. Streamline your code and your solution. you are not effectively filtering your datasets smaller so you continue to query the entire tables which is taking a lot of your resources and your temp tables are becoming full copies of those columns without the indexes (PK, FK, ++??) on the original table to take advantage of. This by no means is a perfect solution but it is an idea of how you can consolidate your logic and reduce your overall data set. Give it a try and see if it performs better for you.
Note this will return the last audit record for any room that has either not had an audit completed or completed more than once.
;WITH cte AS (
SELECT
o.RoomId
,o.clientNum
,a.AuditId
,a.AuditDate
,a.AuditType
,NumOfAuditsComplete = SUM(CASE WHEN a.AuditType = 'Complete' THEN 1 ELSE 0 END) OVER (PARTITION BY o.RoomId)
,RowNum = ROW_NUMBER() OVER (PARTITION BY o.RoomId ORDER BY a.AuditDate DESC)
FROm
dbo.Rooms o
LEFT JOIN dbo.Audit a
ON o.RoomId = a.RoomId
WHERE
o.[Status] = 2
AND o.OrderType = 2
)
SELECT
oc.HotelId
,cte.RoomId
,cte.AuditId
,cte.AuditDate
,cte.AuditType
,cte.NumOfAuditsComplete
,cte.clientNum
,Complete_Error_Status = CASE WHEN cte.NumOfAuditsComplete > 1 THEN 'Complete More Than Once' ELSE 'Not Complete' END
FROM
cte
LEFT JOIN dbo.Hotels oc
ON cte.HotelId = oc.HotelId
LEFT JOIN dbo.clients c
ON cte.clientNum = c.clientNum
WHERE
cte.RowNum = 1
AND cte.NumOfAuditsComplete != 1
Also note I changed your
WHERE
o.[status] = '2'
AND o.orderType = '2'
TO
WHERE
o.[status] = 2
AND o.orderType = 2
to be numeric without the single quotes. If the data type is truely varchar add them back but when you query a numeric column as a varchar it will do data conversion and may not take advantage of indexes that you have built on the table.

Compare values from one table with the results from a query?

First, I will explain the what is being captured. User's have a member level associated with their accounts (Bronze, Gold, Diamond, etc). A nightly job needs to run to calculate the orders from today a year back. If the order total for a given user goes over or under a certain amount their level is upgraded or downgraded. The table where the level information is stored will not change much, but the minimum and maximum amount thresholds may over time. This is what the table looks like:
CREATE TABLE [dbo].[MemberAdvantageLevels] (
[Id] int NOT NULL IDENTITY(1,1) ,
[Name] varchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ,
[MinAmount] int NOT NULL ,
[MaxAmount] int NOT NULL ,
CONSTRAINT [PK__MemberAd__3214EC070D9DF1C7] PRIMARY KEY ([Id])
)
ON [PRIMARY]
GO
I wrote a query that will group the orders by user for the year to date. The query includes their current member level.
SELECT
Sum(dbo.tbh_Orders.SubTotal) AS OrderTotals,
Count(dbo.UserProfile.UserId) AS UserOrders,
dbo.UserProfile.UserId,
dbo.UserProfile.UserName,
dbo.UserProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent as IsCurrentLevel,
dbo.MemberAdvantageLevels.Id as MemberLevelId,
FROM
dbo.tbh_Orders
INNER JOIN dbo.tbh_OrderStatuses ON dbo.tbh_Orders.StatusID = dbo.tbh_OrderStatuses.OrderStatusID
INNER JOIN dbo.UserProfile ON dbo.tbh_Orders.CustomerID = dbo.UserProfile.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ON dbo.UserProfile.UserId = dbo.UserMemberAdvantageLevels.UserId
INNER JOIN dbo.MemberAdvantageLevels ON dbo.UserMemberAdvantageLevels.MemberAdvantageLevelId = dbo.MemberAdvantageLevels.Id
WHERE
dbo.tbh_OrderStatuses.OrderStatusID = 4 AND
(dbo.tbh_Orders.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE()) and IsCurrent = 1
GROUP BY
dbo.UserProfile.UserId,
dbo.UserProfile.UserName,
dbo.UserProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent,
dbo.MemberAdvantageLevels.Id
So, I need to check the OrdersTotal and if it exceeds the current level threshold, I then need to find the Level that fits their current order total and create a new record with their new level.
So for example, lets say jon#doe.com currently is at bronze. The MinAmount for bronze is 0 and the MaxAmount is 999. Currently his Orders for the year are at $2500. I need to find the level that $2500 fits within and upgrade his account. I also need to check their LevelAchievmentDate and if it is outside of the current year we may need to demote the user if there has been no activity.
I was thinking I could create a temp table that holds the results of all levels and then somehow create a CASE statement in the query above to determine the new level. I don't know if that is possible. Or, is it better to iterate over my order results and perform additional queries? If I use the iteration pattern I know i can use the When statement to iterate over the rows.
Update
I updated my Query A bit and so far came up with this, but I may need more information than just the ID from the SubQuery
Select * into #memLevels from MemberAdvantageLevels
SELECT
Sum(dbo.tbh_Orders.SubTotal) AS OrderTotals,
Count(dbo.AZProfile.UserId) AS UserOrders,
dbo.AZProfile.UserId,
dbo.AZProfile.UserName,
dbo.AZProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent as IsCurrentLevel,
dbo.MemberAdvantageLevels.Id as MemberLevelId,
(Select Id from #memLevels where Sum(dbo.tbh_Orders.SubTotal) >= #memLevels.MinAmount and Sum(dbo.tbh_Orders.SubTotal) <= #memLevels.MaxAmount) as NewLevelId
FROM
dbo.tbh_Orders
INNER JOIN dbo.tbh_OrderStatuses ON dbo.tbh_Orders.StatusID = dbo.tbh_OrderStatuses.OrderStatusID
INNER JOIN dbo.AZProfile ON dbo.tbh_Orders.CustomerID = dbo.AZProfile.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ON dbo.AZProfile.UserId = dbo.UserMemberAdvantageLevels.UserId
INNER JOIN dbo.MemberAdvantageLevels ON dbo.UserMemberAdvantageLevels.MemberAdvantageLevelId = dbo.MemberAdvantageLevels.Id
WHERE
dbo.tbh_OrderStatuses.OrderStatusID = 4 AND
(dbo.tbh_Orders.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE()) and IsCurrent = 1
GROUP BY
dbo.AZProfile.UserId,
dbo.AZProfile.UserName,
dbo.AzProfile.Email,
dbo.MemberAdvantageLevels.Name,
dbo.MemberAdvantageLevels.MinAmount,
dbo.MemberAdvantageLevels.MaxAmount,
dbo.UserMemberAdvantageLevels.LevelAchievmentDate,
dbo.UserMemberAdvantageLevels.LevelAchiementAmount,
dbo.UserMemberAdvantageLevels.IsCurrent,
dbo.MemberAdvantageLevels.Id
This hasn't been syntax checked or tested but should handle the inserts and updates you describe. The insert can be done as single statement using a derived/virtual table which contains the orders group by caluclation. Note that both the insert and update statement be done within the same transaction to ensure no two records for the same user can end up with IsCurrent = 1
INSERT UserMemberAdvantageLevels (UserId, MemberAdvantageLevelId, IsCurrent,
LevelAchiementAmount, LevelAchievmentDate)
SELECT t.UserId, mal.Id, 1, t.OrderTotals, GETDATE()
FROM
(SELECT ulp.UserId, SUM(ord.SubTotal) OrderTotals, COUNT(ulp.UserId) UserOrders
FROM UserLevelProfile ulp
INNER JOIN tbh_Orders ord ON (ord.CustomerId = ulp.UserId)
WHERE ord.StatusID = 4
AND ord.AddedDate BETWEEN DATEADD(year,-1,GETDATE()) AND GETDATE()
GROUP BY ulp.UserId) AS t
INNER JOIN MemberAdvantageLevels mal
ON (t.OrderTotals BETWEEN mal.MinAmount AND mal.MaxAmount)
-- Left join needed on next line in case user doesn't currently have a level
LEFT JOIN UserMemberAdvantageLevels umal ON (umal.UserId = t.UserId)
WHERE umal.MemberAdvantageLevelId IS NULL -- First time user has been awarded a level
OR (mal.Id <> umal.MemberAdvantageLevelId -- Level has changed
AND (t.OrderTotals > umal.LevelAchiementAmount -- Acheivement has increased (promotion)
OR t.UserOrders = 0)) -- No. of orders placed is zero (de-motion)
/* Reset IsCurrent flag where new record has been added */
UPDATE UserMemberAdvantageLevels
SET umal1.IsCurrent=0
FROM UserMemberAdvantageLevels umal1
INNER JOIN UserMemberAdvantageLevels umal2 On (umal2.UserId = umal1.UserId)
WHERE umal1.IsCurrent = 1
AND umal2.IsCurrent = 2
AND umal1.LevelAchievmentDate < umal2.LevelAchievmentDate)
One approach:
with cte as
(SELECT Sum(o.SubTotal) AS OrderTotals,
Count(p.UserId) AS UserOrders,
p.UserId,
p.UserName,
p.Email,
l.Name,
l.MinAmount,
l.MaxAmount,
ul.LevelAchievmentDate,
ul.LevelAchiementAmount,
ul.IsCurrent as IsCurrentLevel,
l.Id as MemberLevelId
FROM dbo.tbh_Orders o
INNER JOIN dbo.UserProfile p ON o.CustomerID = p.UserId
INNER JOIN dbo.UserMemberAdvantageLevels ul ON p.UserId = ul.UserId
INNER JOIN dbo.MemberAdvantageLevels l ON ul.MemberAdvantageLevelId = l.Id
WHERE o.StatusID = 4 AND
o.AddedDate BETWEEN dateadd(year,-1,getdate()) AND GETDATE() and
IsCurrent = 1
GROUP BY
p.UserId, p.UserName, p.Email, l.Name, l.MinAmount, l.MaxAmount,
ul.LevelAchievmentDate, ul.LevelAchiementAmount, ul.IsCurrent, l.Id)
select cte.*, ml.*
from cte
join #memLevels ml
on cte.OrderTotals >= ml.MinAmount and cte.OrderTotals <= ml.MaxAmount

Return only one row when multiple rows exist

I am working with a database that tracks field trip information for schools. The query will run on a MS SQL 2005 Server. There are a few cases when my query will return multiple rows for the same Field Trip. So, what I want to do is filter my results so that if more than one row per TripID is returned, display only the row with the MIN StartDateTime.
I know there's something I can do with the PARTITION and MIN functions but I'm not sure how to go about it.
Here is my code:
SELECT DISTINCT
dbo.Trip_TripInformation.RecordID AS TripID,
dbo.Trip_TripInformation.TripDate,
Origin.LocationName AS Origin,
dbo.Trip_TripInformation.OriginDepartureTime AS StartDateTime,
dbo.Trip_TripInformation.OriginReturnTime AS ReturnDateTime,
ISNULL(dbo.Trip_TripInformation.NoOfStudents, 0) AS NumberOfStudents,
ISNULL(dbo.Trip_TripInformation.NoOfAdults, 0) AS NumberOfAdults,
ISNULL(dbo.Trip_TripInformation.NoOfStudents, 0) + ISNULL(dbo.Trip_TripInformation.NoOfAdults, 0) AS NumberOfPassengers,
Destination.LocationName AS Destination,
dbo.Vehicles.Vehicle,
Driver.LastName + ', ' + Driver.FirstName AS Driver
FROM dbo.Trip_TripInformation
LEFT JOIN dbo.Trip_Location AS Origin ON Origin.RecordID = dbo.Trip_TripInformation.OriginLocationID
LEFT JOIN dbo.Trip_TripDestinations ON dbo.Trip_TripInformation.RecordID = dbo.Trip_TripDestinations.TripID
LEFT JOIN dbo.Trip_Location AS Destination ON Destination.RecordID = dbo.Trip_TripDestinations.LocationID
LEFT JOIN dbo.Trip_TripDriverVehicle ON dbo.Trip_TripInformation.RecordID = dbo.Trip_TripDriverVehicle.TripID
AND dbo.Trip_TripDriverVehicle.DestinationID = dbo.Trip_TripDestinations.RecordID
LEFT JOIN dbo.Vehicles ON dbo.Vehicles.RecordID = dbo.Trip_TripDriverVehicle.VehicleID
LEFT JOIN dbo.Employees AS Driver ON dbo.Trip_TripDriverVehicle.DriverID = Driver.RecordID
ORDER BY TripID
Order by StartDate and then select the TOP(1)
Try adding a row number to your select and selecting your data into a temp table (or use a CTE):
ROW_NUMBER() OVER ( PARTITION BY dbo.Trip_TripInformation.RecordID
ORDER BY dbo.Trip_TripInformation.OriginDepartureTime asc
) as RowNum
Then you can just select from that where RowNum = 1
You need to GROUP them.
Happy coding
You need to join to a derived table which extracts the unique TripId and earliest departure time for each trip:
SELECT DISTINCT
...
FROM dbo.Trip_TripInformation
INNER JOIN (
SELECT TripID, MIN(OriginDepartureTime) as EarliestDepartureTime
FROM Trip_TripInformation
GROUP BY TripID
) EarliestTripOnly
ON
Trip_TripInformation.TripID = EarliestTripOnly.TripId
AND
Trip_TripInformation.OriginDepartureTime
= EarliestTripOnly.EarliestDepartureTime
LEFT JOIN dbo.Trip_Location AS Origin ON Origin.RecordID =
dbo.Trip_TripInformation.OriginLocationID
...
You can use the Row_Number function to number each start date within each TripID. In addition, I encapsulated the query into a common-table expression so that I could then fitler on only those Trips where their row numbering was 1 which will represent the earliest date should a trip return multiple rows.
With TripInfo As
(
Select TInfo.RecordID As TripID
, TInfo.TripDate
, Origin.LocationName As Origin
, TInfo.OriginDepartureTime As StartDateTime
, TInfo.OriginReturnTime As ReturnDateTime
, Coalesce( TInfo.NoOfStudents, 0 ) As NumberOfStudents
, Coalesce( TInfo.NoOfAdults, 0) As NumberOfAdults
, Coalesce( TInfo.NoOfStudents, 0) + Coalesce(TInfo.NoOfAdults, 0) As NumberOfPassengers
, Dest.LocationName As Destination
, V.Vehicle
, Driver.LastName + ', ' + Driver.FirstName As Driver
, Row_Number() Over ( Partition By TInfo.RecordId
Order By TInfo.OriginDepartureTime ) As TripDateRnk
From dbo.Trip_TripInformation As TInfo
Left Join dbo.Trip_Location AS Origin
On Origin.RecordID = TInfo.OriginLocationID
Left Join dbo.Trip_TripDestinations As TDest
On TInfo.RecordID = TDest.TripID
Left Join dbo.Trip_Location AS Destination
On Destination.RecordID = TDest.LocationID
Left Join dbo.Trip_TripDriverVehicle As TripV
On TInfo.RecordID = TripV.TripID
And TripV.DestinationID = TDest.RecordID
Left Join dbo.Vehicles As V
ON dbo.Vehicles.RecordID = TripV.VehicleID
Left Join dbo.Employees AS Driver
On dbo.Trip_TripDriverVehicle.DriverID = Driver.RecordID
)
Select TripID, TripDate, Origin, StartDateTime, ReturnDateTime
, NumberOfStudents, NumberOfAdults, NumberOfPassengers
, Destination, Vehicle, Driver
From TripInfo
Where TripDateRank = 1
Order By TripID
A couple of other observations:
I notice that every table is using a Left Join. Is it really the case that all the columns in question are nullable? For example, is it really the case that the VehicleID and the DriverID columns in the Trip_TripDriverVehicle table are nullable? You can designate a trip driver vehicle without the vehicle and without a driver?
I would recommend using Coalesce function instead of the awfully named SQL Server-specific function IsNull. They operate pretty much the same but Coalesce is standard and allows for more than two parameters whereas IsNull is restricted to two. It won't make any significant difference with respect to your code or performance. It's just a style improvement.
SELECT * FROM
(SELECT DISTINCT RowNum = ROW_NUMBER() OVER (PARTITION BY TI.RecordID ORDER BY Trip_TripDestinations.DestinationArrivalTime),
TI.RecordID AS TripID,
TI.TripDate,
Origin.LocationName AS Origin,
TI.OriginDepartureTime AS StartDateTime,
TI.OriginReturnTime AS ReturnDateTime,
ISNULL(TI.NoOfStudents, 0) AS NumberOfStudents,
ISNULL(TI.NoOfAdults, 0) AS NumberOfAdults,
ISNULL(TI.NoOfStudents, 0) + ISNULL(TI.NoOfAdults, 0) AS NumberOfPassengers,
Destination.LocationName AS Destination,
Trip_TripDestinations.DestinationArrivalTime AS DestinationArrivalDateTime,
Vehicles.Vehicle,
Driver.LastName + ', ' + Driver.FirstName AS Driver
FROM Trip_TripInformation TI
LEFT OUTER JOIN Trip_Location AS Origin ON Origin.RecordID = TI.OriginLocationID
/*More Joins... */
LEFT OUTER JOIN Employees AS Driver ON Trip_TripDriverVehicle.DriverID = Driver.RecordID) Q1
WHERE Q1.RowNum = 1 and (Q1.TripDate BETWEEN '2010/12/13 00:00:00' AND '2010/12/17 00:00:00')
ORDER BY Q1.DestinationArrivalDateTime