Sql server 如何使用嵌套while循环提高性能?
我必须按照下面的链接更新标志。我的逻辑与下面提到的链接中的逻辑完全相同,while循环中还有两个嵌套循环 然而,逻辑可以很好地工作,并且在几行中可以顺利地工作。我有大约150000行,将来会更多,这种循环永远不会结束。我检查了显示计划,它说排序成本是322112%,表扫描成本是242938%。每一行都是循环的。Sql server 如何使用嵌套while循环提高性能?,sql-server,tsql,sql-server-2012,Sql Server,Tsql,Sql Server 2012,我必须按照下面的链接更新标志。我的逻辑与下面提到的链接中的逻辑完全相同,while循环中还有两个嵌套循环 然而,逻辑可以很好地工作,并且在几行中可以顺利地工作。我有大约150000行,将来会更多,这种循环永远不会结束。我检查了显示计划,它说排序成本是322112%,表扫描成本是242938%。每一行都是循环的。 我们是否可以让代码运行得更快 如评论中所述。。。在处理SQL时,考虑基于集合 declare @table table (ID int, StartDate date, EndDate
我们是否可以让代码运行得更快 如评论中所述。。。在处理SQL时,考虑基于集合
declare @table table (ID int, StartDate date, EndDate date)
Insert Into @table
(
ID
, StartDate
, EndDate
)
Values
(1, '2017-01-01', '2017-02-01'),
(1, '2017-01-09', '2017-01-28'),
(1, '2017-04-01', '2017-04-30'),
(1, '2017-04-05', '2017-05-20'),
(1, '2017-04-20', '2017-06-12'),
(2, '2017-06-02', '2017-06-20'),
(2, '2017-06-14', '2017-07-31'),
(2, '2017-06-14', '2017-07-31'),
(2, '2017-06-19', '2017-07-31'),
(2, '2017-06-19', '2017-07-31')
;with cte as(
select
t1.ID
,t1.StartDate
,t1.EndDate
,DT = (select min(StartDate) from @table t2 where t2.StartDate > DATEADD(day,30,t1.StartDate))
from
@table t1),
cte2 as(
select
ID
,StartDate
,EndDate
,dense_rank() over (order by isnull(DT,(select max(StartDate) from cte))) as Flag
from
cte)
select
ID
,StartDate
,EndDate
,case when Flag % 2 = 0 then 2 else Flag % 2 end as Flag
from cte2
下面是另一种使用递归和 测试设置:
create table t ( ID Int , StartDate Date , EndDate Date , Flag Int )
insert into t ( ID , StartDate , EndDate ) Values
(1,'2017-01-01','2017-02-01')
,(1,'2017-01-09','2017-01-28')
,(1,'2017-04-01','2017-04-30')
,(1,'2017-04-05','2017-05-20')
,(1,'2017-04-20','2017-06-12')
,(2,'2017-06-02','2017-06-20')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(3,'2017-01-01','2017-02-01')
,(3,'2017-02-01','2017-02-28')
,(3,'2017-04-01','2017-04-30')
,(3,'2017-06-01','2017-05-20')
,(3,'2017-08-01','2017-06-12')
查询:
;with cte as (
/* anchor = first start date for each id, flag = 1 */
select t.id, t.startdate, t.enddate, flag=1
from t
where not exists (
select 1
from t i
where i.id = t.id
and i.startdate < t.startdate
)
union all
/* recursive, get next startdate after 30 days of previous start date
, increment flag*/
select s.id, s.startdate, s.enddate, s.flag
from (
select t.id, t.startdate, t.enddate, flag=p.flag+1
, rn = row_number() over (partition by t.id order by t.startdate)
from t
inner join cte p
on t.id = p.id
and t.startdate > dateadd(day,30,p.startdate)
) s
where s.rn=1
)
select
t.id
, t.startdate
, t.enddate
, x.flag
from t
cross apply (
/* get flag for id, startdate from cte */
select top 1 cte.flag
from cte
where cte.id = t.id
and cte.startdate <= t.startdate
order by cte.startdate desc
) x
这个逻辑非常完美,但我知道这肯定可以用更好的方式来写。如果你们有任何好的替代方法,我很乐意更新我的代码:-
--drop table #t
create table #t ( ID Int , StartDate Date , EndDate Date , LoopLogic nvarchar(100) )
insert into #t ( ID , StartDate , EndDate ) Values
(1,'2017-01-01','2017-02-01')
,(1,'2017-01-09','2017-01-28')
,(1,'2017-04-01','2017-04-30')
,(1,'2017-04-05','2017-05-20')
,(1,'2017-04-20','2017-06-12')
,(2,'2017-06-02','2017-06-20')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(3,'2017-01-01','2017-02-01')
,(3,'2017-02-01','2017-02-28')
,(3,'2017-04-01','2017-04-30')
,(3,'2017-06-01','2017-05-20')
,(3,'2017-08-01','2017-06-12')
--drop table #auth
select *
into #auth
from #t
order by ID,StartDate
Declare
@LoopLogic As nvarchar(100) = '0'
,@Flag As int=0
,@StartDate As Date
,@LookupDate1 As Date
,@LookupDate As Date
,@ID As nvarchar(100)
,@PrevID As nvarchar(100) = '0'
,@DaysDiff As int
,@ReauthFlag As int=0;
--drop table #Temp_SO_Check
Select
*
Into #Temp_SO_Check
From #auth;
CREATE INDEX IDX_C_Users_UserID ON #Temp_SO_Check(ID,StartDate,LoopLogic);
--Select * From #Temp_SO_Check As tsc order by auth#,servicedate
While Exists
(
Select Top 1
ID
From #Temp_SO_Check
Where LoopLogic Is Null
)
Begin
Select Top 1
@ID = ID
, @StartDate = StartDate
From #Temp_SO_Check
Where LoopLogic Is Null
Order By
ID
, StartDate;
If @PrevID <> @ID
Begin
set @Flag=1;
Set @LoopLogic ='Initial_'+Cast(@Flag as nvarchar(50)) ;
Set @PrevID = @ID;
set @ReauthFlag=0;
end
else
Begin
if((@LookupDate is not null) and (datediff(day,@LookupDate,@StartDate))<14)-- if the startdate is less than 14 days from lookdate then it is reauth
Begin
Set @LoopLogic ='Re-Auth_'+Cast(@Flag as nvarchar(50)) ;
End
Else if ((@LookupDate is not null) and (datediff(day,@LookupDate,@StartDate))>14) -- if the startdate is greater than 14 days from lookdate then it is Initial
Begin
set @Flag=@Flag+1;
set @ReauthFlag=0;
Set @LoopLogic ='Initial_'+Cast(@Flag as nvarchar(50));
End
End
Set @LookupDate = DateAdd(Day, 30, @StartDate);
if( (@LoopLogic like '%Re-Auth_%') and (@LookupDate<>@LookupDate1))
Begin
Set @ReauthFlag=@ReauthFlag+1;
Set @LoopLogic ='Re-Auth_'+Cast(@Flag as nvarchar(50))+'_'+Cast(@ReauthFlag as nvarchar(50)) ;
End
set @LookupDate1=@LookupDate;
Update
#Temp_SO_Check
Set
LoopLogic = @LoopLogic
Where
ID = @ID
And StartDate Between @StartDate
And @LookupDate;
End;
select * from #Temp_SO_Check
在嵌套循环中提高性能的方法是先取消嵌套循环,然后在不使用循环的情况下重写查询。我认为上一个问题有错误,标志应该每次递增,而不是从1到2,然后on@Lamak我也这么认为,这样会更容易,但这并不是预期的结果。我不认为这会让事情变得更容易,因为我还认为国旗需要继续升到4、5、6……等等。谢谢你的帮助。。现在,我只是在OrderBy列上添加了索引,代码在6分钟内运行@西蒙。我肯定会尝试你的基本代码,而不是while循环。你的标志中有3、4、5,这不是预期的结果。。。为什么会这样?@scsimon he以我自己的身份假设旗帜需要继续计数,而相关问题实际上表明了这一点,但结果表明并非如此。不管怎样,这是一个很棒的机会answer@scsimon据我所知,这取决于你如何解释链接问题中的这一行:这一行在同一个ID上继续,直到它达到同一ID的最后一个记录。我认为这意味着30天窗口的过程,将flag增加1,直到每个Id的行结束。我假设您将其理解为:继续对其余行使用flag_2。我没有说这不可怕,我只做了UV-Zim的回答。今天这个标签上出现了有趣的敌意。@scsimon我确实同意,链接的问题肯定可以使用一些更好的样本数据和结果。
--drop table #t
create table #t ( ID Int , StartDate Date , EndDate Date , LoopLogic nvarchar(100) )
insert into #t ( ID , StartDate , EndDate ) Values
(1,'2017-01-01','2017-02-01')
,(1,'2017-01-09','2017-01-28')
,(1,'2017-04-01','2017-04-30')
,(1,'2017-04-05','2017-05-20')
,(1,'2017-04-20','2017-06-12')
,(2,'2017-06-02','2017-06-20')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-14','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(2,'2017-06-19','2017-07-31')
,(3,'2017-01-01','2017-02-01')
,(3,'2017-02-01','2017-02-28')
,(3,'2017-04-01','2017-04-30')
,(3,'2017-06-01','2017-05-20')
,(3,'2017-08-01','2017-06-12')
--drop table #auth
select *
into #auth
from #t
order by ID,StartDate
Declare
@LoopLogic As nvarchar(100) = '0'
,@Flag As int=0
,@StartDate As Date
,@LookupDate1 As Date
,@LookupDate As Date
,@ID As nvarchar(100)
,@PrevID As nvarchar(100) = '0'
,@DaysDiff As int
,@ReauthFlag As int=0;
--drop table #Temp_SO_Check
Select
*
Into #Temp_SO_Check
From #auth;
CREATE INDEX IDX_C_Users_UserID ON #Temp_SO_Check(ID,StartDate,LoopLogic);
--Select * From #Temp_SO_Check As tsc order by auth#,servicedate
While Exists
(
Select Top 1
ID
From #Temp_SO_Check
Where LoopLogic Is Null
)
Begin
Select Top 1
@ID = ID
, @StartDate = StartDate
From #Temp_SO_Check
Where LoopLogic Is Null
Order By
ID
, StartDate;
If @PrevID <> @ID
Begin
set @Flag=1;
Set @LoopLogic ='Initial_'+Cast(@Flag as nvarchar(50)) ;
Set @PrevID = @ID;
set @ReauthFlag=0;
end
else
Begin
if((@LookupDate is not null) and (datediff(day,@LookupDate,@StartDate))<14)-- if the startdate is less than 14 days from lookdate then it is reauth
Begin
Set @LoopLogic ='Re-Auth_'+Cast(@Flag as nvarchar(50)) ;
End
Else if ((@LookupDate is not null) and (datediff(day,@LookupDate,@StartDate))>14) -- if the startdate is greater than 14 days from lookdate then it is Initial
Begin
set @Flag=@Flag+1;
set @ReauthFlag=0;
Set @LoopLogic ='Initial_'+Cast(@Flag as nvarchar(50));
End
End
Set @LookupDate = DateAdd(Day, 30, @StartDate);
if( (@LoopLogic like '%Re-Auth_%') and (@LookupDate<>@LookupDate1))
Begin
Set @ReauthFlag=@ReauthFlag+1;
Set @LoopLogic ='Re-Auth_'+Cast(@Flag as nvarchar(50))+'_'+Cast(@ReauthFlag as nvarchar(50)) ;
End
set @LookupDate1=@LookupDate;
Update
#Temp_SO_Check
Set
LoopLogic = @LoopLogic
Where
ID = @ID
And StartDate Between @StartDate
And @LookupDate;
End;
select * from #Temp_SO_Check