Create View using EF Core - sql-server

Following guidance in this blog post, I've added an ef core (dotnet 6) migration into which I've pasted the t-sql to create a view.
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(#"
exec('create view [dbo].[vwSpotlightExtract] as
with
exceptions as (
select id from dbo.Application
where CTaxNumber in (
select
app.CTaxNumber
from dbo.Application app
group by app.CTaxNumber
having count(*) > 1
)
union
-- duplicate bank account
select id from dbo.Application where concat(BankSortCode, ':', BankAccountNumber) in (
select concat(app.BankSortCode, ':', app.BankAccountNumber)
from dbo.Application app
where app.BankAccountNumber is not null
group by concat(app.BankSortCode, ':', app.BankAccountNumber)
having count(*) > 1
)
union
-- duplicate uprn
select id from dbo.Application where uprn in (
select app.uprn from dbo.Application app
group by app.uprn having count(*)>1
)
),
LastAppStatus as (
select app.Id, app.UTRN,
max(rh.Id) LastRebateHistoryID
from dbo.Application app
inner join dbo.RebateHistory rh on app.ID = rh.ApplicationId
where rh.ApplicationId is not null --and rh.ApplicationId not in (select ID from exceptions)
and app.RequestType = 0 -- BACS
and ISNULL(app.PaymentStopped, 0) = 0 -- Payment NOT Stopped
--and app.id not in (select id from exceptions) -- to prevent sending stuff to spotlight
group by app.Id, app.UTRN
)
select
app.UTRN [Application Number],
'Personal' [Personal or Business Bank Account? (required)], -- always Personal
REPLACE(app.BankSortCode, '-', '') [Sort code (required)],
app.BankAccountNumber [Account number (required)],
NULL [Business name (required if business)],
app.AccountPayerFirstName [First Name (required if personal)],
app.AccountPayerSurname [Surname (required if personal)],
CASE WHEN LEN(CONCAT(addr.SAO_Start_No, addr.SAO_Start_Sfx)) > 0 and LEN (CONCAT(addr.SAO_End_No, addr.SAO_End_Sfx)) > 0 then CONCAT(addr.SAO_Start_No, addr.SAO_Start_Sfx, '-', addr.SAO_End_No, addr.SAO_End_Sfx)
WHEN LEN(CONCAT(addr.SAO_Start_No, addr.SAO_Start_Sfx)) > 0 and LEN (CONCAT(addr.SAO_End_No, addr.SAO_End_Sfx)) = 0 then CONCAT(addr.SAO_Start_No, addr.SAO_Start_Sfx)
WHEN LEN(CONCAT(addr.SAO_Start_No, addr.SAO_Start_Sfx)) = 0 and LEN (CONCAT(addr.SAO_End_No, addr.SAO_End_Sfx)) > 0 then CONCAT(addr.SAO_End_No, addr.SAO_End_Sfx)
else NULL end as
[Flat number (optional)], -- secondary addressable number or name
case when LEN(addr.PAO_Desc) > 0 then addr.PAO_Desc
when LEN(addr.SAO_Desc) < 0 then addr.SAO_Desc
ELSE NULL
end as [Building name (optional)],-- primary addressable name
CASE WHEN LEN(CONCAT(addr.PAO_Start_No, addr.PAO_Start_Sfx)) > 0 and LEN (CONCAT(addr.PAO_End_No, addr.PAO_End_Sfx)) > 0 then CONCAT(addr.PAO_Start_No, addr.PAO_Start_Sfx, '-', addr.PAO_End_No, addr.PAO_End_Sfx)
WHEN LEN(CONCAT(addr.PAO_Start_No, addr.PAO_Start_Sfx)) > 0 and LEN (CONCAT(addr.PAO_End_No, addr.PAO_End_Sfx)) = 0 then CONCAT(addr.PAO_Start_No, addr.PAO_Start_Sfx)
WHEN LEN(CONCAT(addr.PAO_Start_No, addr.PAO_Start_Sfx)) = 0 and LEN (CONCAT(addr.PAO_End_No, addr.PAO_End_Sfx)) > 0 then CONCAT(addr.PAO_End_No, addr.PAO_End_Sfx)
end [Building number (optional)],
addr.Street_Name [Street name (required)],
addr.Postcode [Address postcode (required)],
case when app.AccountType = 0 then 'Single'
when app.AccountType = 1 then 'Joint'
end as [Single or Joint account type (if personal)], -- new field coming
convert(varchar, app.AccountPayerDOB, 103) [Date of birth (if personal)],
NULL [Limited or non-limited (if business)],
NULL [Company registration number (if business)]
, ec.EventName
, app.ID
from dbo.Application app
left outer join dbo.AcademyNonDDPayers nondd on app.CTaxNumber = nondd.CTaxNumber
inner join dbo.CTaxAddress addr on RIGHT('000000000000'+ISNULL(app.uprn,''),12) = addr.uprn and nondd.CTaxPropertyRef = addr.CTaxPropertyRef
inner join LastAppStatus las on app.ID = las.ID
inner join dbo.RebateHistory rh on las.LastRebateHistoryID = rh.ID
inner join dbo.EventCode ec on rh.EventCodeId = ec.ID
where ec.ID = 11')
");
}
This causes a failure in the release pipeline. If I take the content of the sql script from the deployment artefact and paste into SSMS then I see the following error:
I'm not sure why this is because the create view statement seems to be correctly wrapped with begin and end statements:

CREATE VIEW can’t be inside IF/BEGIN logic; it has to be the only statement in the batch. The typical workaround is to create a script like this:
IF OBJECT_ID(N'dbo.viewname', N'V') IS NULL
BEGIN
EXEC sys.sp_executesql N'CREATE VIEW dbo.viewname AS SELECT 1;';
END
GO
ALTER VIEW dbo.viewname
AS
... real view code ...
In newer versions of SQL Server, you can just do:
CREATE OR ALTER VIEW dbo.viewname
AS
... real view code ...
But that still has to live in its own batch. Meaning it can't be inside IF/BEGIN unless you use dynamic SQL for the whole view. e.g.:
IF (some condition)
BEGIN
EXEC sys.sp_executesql N'CREATE VIEW dbo.viewname
AS
... all that view code ...;';
END
Whether you'll be able to generate any of those forms from EF core, I just don't know. Sometimes we expect an ORM to do absolutely everything but, usually, it is only capable of a very small subset.
I'm not sure of the logic you're trying to get to anyway. (If a row doesn't exist in some table, create a view? That will only work once.)

Related

How can I use the EXISTS operator?

Someone maybe can help me to use EXISTS operator to check - ev.tfe_evento = 'Inizio lettura file' and ev.tfe_evento = 'lettura file'. I need to display all files that contain inizio lettura file and lettura file. The template of code is:
SELECT column-names
FROM table-name
WHERE EXISTS (
SELECT column-name
FROM table-name
WHERE condition
)
But how I can change my code below to follow this example?
DROP TABLE IF EXISTS #tflussi_eventi;
SELECT ev.*
INTO tab1
FROM tab1 ev (nolock)
JOIN (
SELECT lab
FROM #lab1
GROUP BY labname
) tf ON ev.labnameLIKE tf.labname+ '%'
WHERE ev.labevent= 'today'
AND ev.labevent= 'yesterday'
Sometimes I like to use window functions when checking conditions like this. Where you are looking for multiple conditions across the group.
This is not your actual query, just to give you an idea.
With window functions:
SELECT DISTINCT fileName
FROM (SELECT E.*,
CASE WHEN MAX(CASE WHEN E.tfe_evento = 'lettura file' THEN 1
ELSE 0
END
) OVER ( PARTITION BY E.FileName ) = 1
AND MAX(CASE WHEN E.tfe_evento = 'Inizio lettura file' THEN 1
ELSE 0
END
) OVER ( PARTITION BY E.FileName ) = 1
THEN 1
ELSE 0
END AS FileHasBothEventConds
FROM EVENT E
)
WHERE FileHasBothEventConds = 1;
With EXISTS:
SELECT DISTINCT fileName
FROM EVENTS E
WHERE EXISTS
( SELECT 1
FROM EVENTS E_LF
WHERE E.fileName = E_LF.fileName
AND E_FL.tfe_evento = 'lettura file'
)
AND EXISTS
( SELECT 1
FROM EVENTS E_ILF
WHERE E.fileName = E_ILF.fileName
AND E_ILF.tfe_evento = 'Inizio lettura file'
)
You should use LIKE operator instead of = for that to happen. ie:
SELECT ev.*
INTO #tflussi_eventi
FROM tflussi_eventi ev (nolock)
JOIN (
SELECT FileNameCutoff
FROM #FileCutoffTime
GROUP BY FileNameCutoff
) tf ON ev.tfe_sorgente LIKE tf.FileNameCutoff + '%'
WHERE ev.tfe_evento LIKE '%Inizio lettura file%'
-- Unnecessary because of first criteria
-- AND ev.tfe_evento = 'lettura file'
AND ev.tfe_data >= #DateToCheck
AND ev.tfe_data < #DateToCheck + 1 -- not sure what you mean here, if it is datetime, using DateAdd() is safer
However, this might suffer from performance, you should think of creating fulltext indexes and use full text search.
EDIT: While it would still suffer because you are not doing a full text search, you might save, maybe negligible time on the joined table which practically has no importance other than "existence check":
SELECT ev.*
INTO #tflussi_eventi
FROM tflussi_eventi ev (nolock)
WHERE ev.tfe_evento LIKE '%Inizio lettura file%'
-- Unnecessary because of first criteria
-- AND ev.tfe_evento = 'lettura file'
AND ev.tfe_data >= #DateToCheck
AND ev.tfe_data < #DateToCheck + 1 -- not sure what you mean here, if it is datetime, using DateAdd() is safer
and EXISTS (
SELECT *
FROM #FileCutoffTime tf
where ev.tfe_sorgente LIKE tf.FileNameCutoff + '%');

Convert view from SQL Server to Redshift

I have view in SQL Server, that I need to convert to Redshift
Here is the view:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.BE_ContactRelationsDual') AND type in (N'V'))
DROP VIEW public.contactrelationsdual;
GO
CREATE VIEW public.contactrelationsdual
WITH SCHEMABINDING
AS
SELECT CASE d.id
WHEN 0 THEN
cr.contactId
ELSE
cr.relatedContactId
END me,
CASE d.id
WHEN 0 THEN
cr.relatedContactId
ELSE
cr.contactId
END him,
cr.id,
cr.permissionId
FROM public.contact_relations cr
CROSS JOIN
public.system_dual d
WHERE cr.contactId > 0
AND cr.relatedContactId > 0
AND cr.deletedDate IS NULL
GO
CREATE UNIQUE CLUSTERED INDEX
UX_BE_ContactRelationsDual_Me_Him
ON public.contactrelationsdual (me, him)
GO
I need to convert it to redshift. I have 2 problems:
This:
CREATE VIEW public.contactrelationsdual
WITH SCHEMABINDING
and this:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'dbo.BE_ContactRelationsDual') AND type in (N'V')) DROP
VIEW public.contactrelationsdual; GO
How I can correctly convert it to Redshift.
Remove with schemabinding:
CREATE VIEW public.contactrelationsdual AS
SELECT (CASE d.id WHEN 0 THEN cr.contactId ELSE cr.relatedContactId
END) as me,
(CASE d.id WHEN 0 THEN cr.relatedContactId ELSE cr.contactId
END) as him,
cr.id,
cr.permissionId
FROM public.contact_relations cr CROSS JOIN
public.system_dual d
WHERE cr.contactId > 0 AND
cr.relatedContactId > 0 AND
cr.deletedDate IS NULL;
This is pretty standard SQL and should work on almost any database.
The IF EXISTS part can be replaced with:
drop view if exists public.contactrelationsdual;
There is no need for a clustered index in Redshift.

Create View - Declare a variable

I am creating a view that is using that STUFF function. I want to put the result of STUFF in a variable for my view. The problem I am having is declaring my variable. It gives me the message "Incorrect Syntax near 'DECLARE'. Expecting '(' or SELECT." I already have the '(' in there. I have tried putting a BEGIN before it. I have tried putting it after the SELECT word. But nothing seems to work and I cannot find a solution in my search. I am using SQL Server 2012
CREATE VIEW [AQB_OB].[GISREQUESTEDBURNS]
AS
(DECLARE #CONDITIONS AS varchar(20)
SET #CONDITIONS = (SELECT DISTINCT BD.[RequestedBurnsID]
,[ConditionsReasonsID] = STUFF((SELECT ', ' + CONVERT(VARCHAR (20),[ConditionsReasonsID]) FROM [AQB_OB].[BurnDecisions] WHERE [RequestedBurnsID]= BD.[RequestedBurnsID] ORDER BY [RequestedBurnsID] ASC
FOR XML PATH ('')) , 1 , 1, '') FROM
[AQB_OB].[BurnDecisions] BD)
SELECT RB.[RequestedBurnsID] AS REQUESTEDBURNID
,BUY.[BurnYear] AS BURNYEAR
,CY.[CurrentYear] AS CURRENTYEAR
,RB.[BurnSitesID] AS BURNSITESID
,[BurnerID] AS BURNERID
,[Contact] AS CONTACT
,[BurnDecision] AS BURNDECISION
,RB.[Comment] AS COMMENT
,#CONDITIONS AS CONDITIONS
FROM [AQB_MON].[AQB_OB].[RequestedBurns] RB
LEFT join AQB_MON.[AQB_OB].[PileDryness] PD on RB.[PileDrynessID] = PD.[PileDrynessID]
inner join AQB_MON.[AQB_OB].[BurnYear] BUY on BUY.BurnYearID = BP.BurnYearID
inner join AQB_MON.[AQB_OB].[CurrentYear] CY on CY.CurrentYearID = BUY.CurrentYearID
GO
You can't declare variables in a view. Could you make it into a function or stored procedure?
Edit - you might also be able to put something into a CTE (Common Table Expression) and keep it as a view.
e.g.
WITH conditions as
(
... do the STUFF here
)
SELECT blah
FROM blah
INNER JOIN conditions
(or CROSS JOIN conditions if its just one row, I can't quite decipher what your data is like)
Here is a sample query that uses a CTE (Common Table Expression) to nicely emulate internal variable construction, as described by James Casey. You can test-run it in your version of SQL Server.
CREATE VIEW vwImportant_Users AS
WITH params AS (
SELECT
varType='%Admin%',
varMinStatus=1)
SELECT status, name
FROM sys.sysusers, params
WHERE status > varMinStatus OR name LIKE varType
SELECT * FROM vwImportant_Users
yielding output:
status name
12 dbo
0 db_accessadmin
0 db_securityadmin
0 db_ddladmin
also via JOIN
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers INNER JOIN params ON 1=1
WHERE status > varMinStatus OR name LIKE varType
also via CROSS APPLY
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers CROSS APPLY params
WHERE status > varMinStatus OR name LIKE varType
Or use a CTE (common table expression) as subselect like:
WITH CTE_Time(Clock)
AS(
SELECT 11 AS [Clock] -- set var
)
SELECT
DATEPART(HOUR, GETDATE()) AS 'actual hour',
CASE
WHEN DATEPART(HOUR, GETDATE()) >= (SELECT [Clock] FROM CTE_Time) THEN 'after'
ELSE 'before'
END AS [Data]
Try put the condition subquery directly inside the the view select statement. you may CAST the XML to VARCHAR(20).
CREATE VIEW [AQB_OB].[GISREQUESTEDBURNS]
AS
SELECT RB.[RequestedBurnsID] AS REQUESTEDBURNID
,BUY.[BurnYear] AS BURNYEAR
,CY.[CurrentYear] AS CURRENTYEAR
,RB.[BurnSitesID] AS BURNSITESID
,[BurnerID] AS BURNERID
,[Contact] AS CONTACT
,[BurnDecision] AS BURNDECISION
,RB.[Comment] AS COMMENT,
(
SELECT DISTINCT BD.[RequestedBurnsID],
[ConditionsReasonsID] = STUFF((SELECT ', ' + CONVERT(VARCHAR (20), [ConditionsReasonsID]) FROM [AQB_OB].[BurnDecisions]
WHERE [RequestedBurnsID]= BD.[RequestedBurnsID] ORDER BY [RequestedBurnsID] ASC
FOR XML PATH ('')) , 1 , 1, '') FROM
[AQB_OB].[BurnDecisions] BD
) AS CONDITIONS
FROM [AQB_MON].[AQB_OB].[RequestedBurns] RB
LEFT join AQB_MON.[AQB_OB].[PileDryness] PD on RB.[PileDrynessID] = PD.[PileDrynessID]
inner join AQB_MON.[AQB_OB].[BurnYear] BUY on BUY.BurnYearID = BP.BurnYearID
inner join AQB_MON.[AQB_OB].[CurrentYear] CY on CY.CurrentYearID = BUY.CurrentYearID

Check if record exists while inserting using User defined table type sql server

I am trying to bulk insert the records in sql server. I am using User Defined Table type to pass the collection of records from my .net application. Please take a look at the insert query below.
INSERT INTO MachineItems([Name],[Price],[Quantity],[ItemGroupID],[SubGroup] ,[IsDefault],
[IsRemovable],[MachineTypeID],[ItemType],[CreatedBy],[CreatedOn] )
SELECT mi.Name
,mi.Price
,mi.Quantity
,(SELECT ID from ItemGroups WHERE NAME=mi.ItemGroup) as ID
,mi.SubGroup,
CASE
WHEN mi.IsDefault ='Yes' THEN 1
WHEN mi.IsDefault ='No' THEN 0
WHEN mi.IsDefault IS NULL THEN 0
END ,
CASE
WHEN mi.IsRemovable ='Yes' THEN 1
WHEN mi.IsRemovable ='No' THEN 0
END ,
(SELECT ID from MachineTypes WHERE Name=mi.MachineType),
(SELECT ID from MachineItemTypes WHERE Name=mi.ItemType),
mi.CreatedBy
,mi.CreatedOn
FROM #MachineItems mi
What i want to do is put the check before inserting the records , Whether record with [MachineTypeID] and [Name] already exists in table or not. If it does not exists then insert Eles Update the record.
How can i do that with User Defined Table Type ?
You should use the MERGE command rather than a straight insert. What you are wanting to do is not really specific to User-Defined Table Types.
It would be better / more efficient if you joined the 3 subtables rather than having subqueries for columns which will execute per-row.
Example:
MERGE MachineItems AS Target
USING (SELECT mi.Name,
mi.Price,
mi.Quantity,
ig.ID, -- ItemGroupID
mi.SubGroup,
CASE
WHEN mi.IsDefault ='Yes' THEN 1
WHEN mi.IsDefault ='No' THEN 0
WHEN mi.IsDefault IS NULL THEN 0
END, -- IsDefault
CASE
WHEN mi.IsRemovable ='Yes' THEN 1
WHEN mi.IsRemovable ='No' THEN 0
END, -- IsRemovable
mt.ID, -- MachineTypeID
mit.ID, -- ItemType
mi.CreatedBy,
mi.CreatedOn
FROM #MachineItems mi
INNER JOIN ItemGroups ig
ON ig.[Name] = mi.ItemGroup
INNER JOIN MachineTypes mt
ON mt.[Name] = mi.MachineType
INNER JOIN MachineItemTypes mit
ON mit.[Name] = mi.ItemType) AS Source (
[Name],[Price],[Quantity],[ItemGroupID],[SubGroup],[IsDefault],
[IsRemovable],[MachineTypeID],[ItemType],[CreatedBy],[CreatedOn])
ON (
Target.[MachineTypeID] = Source.[MachineTypeID]
AND Target.[Name] = Source.[Name]
)
WHEN MATCHED THEN
UPDATE SET Price = Source.Price,
Quantity = Source.Quantity,
ItemGroupID = Source.ItemGroupID,
SubGroup = Source.SubGroup,
IsDefault = Source.IsDefault,
IsRemovable = Source.IsRemovable,
MachineTypeID = Source.MachineTypeID,
ItemType = Source.ItemType,
CreatedBy = Source.CreatedBy,
CreatedOn = Source.CreatedOn
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Name],[Price],[Quantity],[ItemGroupID],[SubGroup] ,[IsDefault],
[IsRemovable],[MachineTypeID],[ItemType],[CreatedBy],[CreatedOn])
VALUES (Source.[Name], Source.[Price], Source.[Quantity], Source.[ItemGroupID],
Source.[SubGroup], Source.[IsDefault], Source.[IsRemovable],
Source.[MachineTypeID], Source.[ItemType], Source.[CreatedBy],
Source.[CreatedOn]);
You can Use Merge Here
Using Merge
You can Insert if Not Exists
You can Delete if Already Exists
You can Update if Already Exists
MERGE MachineItems
USING #MachineItems ON MachineItems.id = #MachineItems.id
and MachineItems.Name=#MachineItems.Name
WHEN NOT MATCHED THEN
INSERT INTO MachineItems([Name],[Price],[Quantity],[ItemGroupID],[SubGroup]
,[IsDefault],
[IsRemovable],[MachineTypeID],[ItemType],[CreatedBy],[CreatedOn] )
SELECT mi.Name
,mi.Price
,mi.Quantity
,(SELECT ID from ItemGroups WHERE NAME=mi.ItemGroup) as ID
,mi.SubGroup,
CASE
WHEN mi.IsDefault ='Yes' THEN 1
WHEN mi.IsDefault ='No' THEN 0
WHEN mi.IsDefault IS NULL THEN 0
END ,
CASE
WHEN mi.IsRemovable ='Yes' THEN 1
WHEN mi.IsRemovable ='No' THEN 0
END ,
(SELECT ID from MachineTypes WHERE Name=mi.MachineType),
(SELECT ID from MachineItemTypes WHERE Name=mi.ItemType),
mi.CreatedBy
,mi.CreatedOn
FROM #MachineItems mi

Sql query to create teams

I need a query to assign teams to a series of users. Data looks like this:
UserId Category Team
1 A null
2 A null
3 B null
4 B null
5 A null
6 B null
8 A null
9 B null
11 B null
Teams should be created by sorting by userid and the first userid becomes the team number and the consecutive A's are part of that team as are the B's that follow. The first A after the Bs starts a new team. There will always be at least one A and one B. So after the update, that data should look like this:
UserId Category Team
1 A 1
2 A 1
3 B 1
4 B 1
5 A 5
6 B 5
8 A 8
9 B 8
11 B 8
EDIT:
Need to add that the user id's will not always increment by 1. I edited the example data to show what I mean. Also, the team ID doesn't strictly have to be the id of the first user, as long as they end up grouped properly. For example, users 1 - 4 could all be on team '1', users 5 and 6 on team '2' and users 8,9 and 11 on team '3'
First you could label each row with an increasing number. Then you can use a left join to find the previous user. If the previous user has category 'B', and the current one category 'A', that means the start of a new team. The team number is then the last UserId that started a new team before the current UserId.
Using SQL Server 2008 syntax:
; with numbered as
(
select row_number() over (order by UserId) rn
, *
from Table1
)
, changes as
(
select cur.UserId
, case
when prev.Category = 'B' and cur.Category = 'A' then cur.UserId
when prev.Category is null then cur.UserId
end as Team
from numbered cur
left join
numbered prev
on cur.rn = prev.rn + 1
)
update t1
set Team = team.Team
from Table1 t1
outer apply
(
select top 1 c.Team
from changes c
where c.UserId <= t1.UserId
and c.Team is not null
order by
c.UserId desc
) as team;
Example at SQL Fiddle.
You can do this with a recursive CTE:
with userCTE as
(
select UserId
, Category
, Team = UserId
from users where UserId = 1
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
from userCTE
inner join users on users.UserId = userCTE.UserId + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
You can update the CTE to get this to go:
with userOrder as
(
select *
, userRank = row_number() over (order by userId)
from users
)
, userCTE as
(
select UserId
, Category
, Team = UserId
, userRank
from userOrder where UserId = (select min(UserId) from users)
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
, users.userRank
from userCTE
inner join userOrder users on users.userRank = userCTE.userRank + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
For larger datasets you'll need to add the maxrecursion query hint; I've edited the previous queries to show this. From Books Online:
Specifies the maximum number of recursions allowed for this query.
number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied.
In this case I've set it to 0, i.e. not limit on recursion.
Query Hints.
I actually ended up going with the following. It finished on all 3 million+ rows in a half an hour.
declare #userid int
declare #team int
declare #category char(1)
declare #lastcategory char(1)
set #userid = 1
set #lastcategory='B'
set #team=0
while #userid is not null
begin
select #category = category from users where userid = #userid
if #category = 'A' and #lastcategory = 'B'
begin
set #team = #userid
end
update users set team = #team where userid = #userid
set #lastcategory = #category
select #userid = MIN(userid) from users where userid > #userid
End

Resources