現實項目中用戶隨意添加序號,如何用SQL解決序號連續性問題
前段時間,一直忙於學習golang語言,沒有時間整理項目中用到的方法,今天趁著有空寫下筆記。
項目中,遇到一個比較"刁鉆"的需求:用戶用Excel導入到系統裏,每一行前面都有一個序號,序號分成兩部分,如下所示:
左邊部分是大序號,右邊是小序號,類似於書籍目錄那樣,序號是由用戶自己編寫,而且用戶可以隨意在Excel序號插入任何新序號,用戶不保證新增或者編輯的序號是否正確,我們要做的是檢查這些序號。
以下是我的檢查思路:
1.序號是否連續
我們要事先給用戶做一個限制,在大序號後面添加小序號的最大容量是100(其他容量也可以,只不過業務上100夠用了),即2到2-99都可以,用戶可以隨意追加小序號,例如有序號2,2-1,2-2,3,用戶可以在2-2後面插入2-3。
2.序號有無重復
這個比較簡單,SQL裏用group by xx having count(xx) > 1就可以解決,這個放到最後去檢查。
步驟:
0x00
導入數據
create table #SerialNum( Seq varchar(20) ) insert into #SerialNum (Seq) select ‘1‘ union all select ‘1-1‘ union all select ‘1-2‘ union all select ‘1-2‘ union all select ‘1-5‘ union all select ‘1-6‘ union allselect ‘2‘ union all select ‘2-2‘ union all select ‘4‘ union all select ‘6-8‘ union all select ‘6-10‘
0x01
寫一個函數,把序號轉換為decimal(18,2)類型的小數,例如把2-1轉換成2.01,因為小序號的容量是100,所以2-1=>2+1/100=2.01。
/* select dbo.StringToDecimal(‘2-1‘) --輸出2.01 select dbo.StringToDecimal(‘32-68‘) --輸出32.68 */ CREATE function[dbo].[StringToDecimal]( @Str as varchar(20) ) returns decimal(18,2) as begin declare @IntNum decimal(18,2) --整數部分 declare @DeNum decimal(18,2) --小數點後面 declare @CharIndex int set @CharIndex = charindex(‘-‘,@Str) IF @CharIndex = 0 --沒有‘-‘直接轉decimal return cast(@Str as decimal(18,2)) set @IntNum = cast(left(@Str,@CharIndex - 1) as decimal(18,2)) --獲取整數部分 set @DeNum = cast(RIGHT(@Str,len(@Str) - @CharIndex) as decimal(18,2))/100 --獲取小數部分 return cast(@IntNum + @DeNum as decimal(18,2)) end
再把#SerialNum的數據轉換一次,放入另一個臨時表#temp裏。
create table #temp( Seq decimal(18,2) ) insert into #temp (Seq) select dbo.StringToDecimal(Seq) from #SerialNum
0x02
首先找出小序號是否連續,比如1-1,1-2,1-5,1-6,2,這裏就出現斷號,我們要做的是把1-5找出來(但是2前面不算斷號),提示該序號前面有斷號,先上代碼。
select min(Seq) as Seq --找出脫離連續序號的一組序號的最小值,就知道哪裏斷開了 from (select [gid] = Seq - cast(cast(pid -1 as decimal(18,2))/100 as decimal(18,2)) --計算差值,如果序號連續,那麽gid一定等於序號的整數部分 ,Seq from (select row_number() over(partition by cast(Seq as int) order by Seq) as pid --在每個大序號內的排序 ,Seq from #temp) a ) b group by cast(Seq as int),gid having cast(Seq as int) <> gid --找出gid不等於序號整數的分組
先分解嵌套的a表,SQL中cast(Seq as int)的意圖就是取序號的整數部分,數據如下:
pid | Seq |
1 | 1 |
2 | 1.01 |
3 | 1.02 |
4 | 1.02 |
5 | 1.05 |
6 | 1.06 |
1 | 2 |
2 | 2.02 |
1 | 4 |
1 | 6.08 |
2 | 6.1 |
再看嵌套的b表,b表的gid就是Seq - (pid)/100,數據如下:
gid | Seq |
1 | 1 |
1 | 1.01 |
1 | 1.02 |
0.99 | 1.02 |
1.01 | 1.05 |
1.01 | 1.06 |
2 | 2 |
2.01 | 2.02 |
4 | 4 |
6.08 | 6.08 |
6.09 | 6.1 |
看出來了嗎?你會發現當gid等於Seq整數部分的時候就是連續的,不等於就代表出現斷號了,那麽接下來就直接通過group by cast(Seq as int),gid having cast(Seq as int) <> gid把斷號塊找出來,拿出斷號塊的第一個序號,就能準確提醒用戶這裏出現斷號。
0x03
接下來檢查出整數序號連續性
select min(Seq) as Seq from (select row_number() over (order by _Seq) as pid,Seq from (select cast(Seq as int) as _Seq, min(Seq) as Seq from #temp group by cast(Seq as int)) a) b group by (cast(Seq as int) - pid) having (cast(Seq as int) - pid) <> 0 --序號減去行號不等於零就是不連續
0x04
找出重復的序號
select Seq from #temp group by Seq having count(Seq) > 1
0x05
最後把前三步篩選出來的數據用CTE公用表達式整合在一起,放入臨時表#rst
;with t1 as ( --t1篩選出小數序號連續性 select min(Seq) as Seq --找出脫離連續序號的一組序號的最小值,就知道哪裏斷開了 from (select [gid] = Seq - cast(cast(pid -1 as decimal(18,2))/100 as decimal(18,2)) --計算差值,如果序號連續,那麽gid一定等於序號的整數部分 ,Seq from (select row_number() over(partition by cast(Seq as int) order by Seq) as pid --在每個大序號內的排序 ,Seq from #temp) a ) b group by cast(Seq as int),gid having cast(Seq as int) <> gid --找出gid不等於序號整數的分組 ),t2 as ( --t2篩選出整數序號連續性 select min(Seq) as Seq from (select row_number() over (order by _Seq) as pid,Seq from (select cast(Seq as int) as _Seq, min(Seq) as Seq from #temp group by cast(Seq as int)) a) b group by (cast(Seq as int) - pid) having (cast(Seq as int) - pid) <> 0 --序號減去行號不等於零就是不連續 ),t3 as ( --t3檢查重復性 select Seq from #temp group by Seq having count(Seq) > 1 union all select Seq from t1 union all select Seq from t2 ) select distinct Seq into #rst from t3 order by Seq
0x06
但是要把小數還原成X-XX的格式,所以要寫一個轉換函數。
/* select dbo.DecimalToString(2) --輸出2 select dbo.DecimalToString(2.02) --輸出2-2 select dbo.DecimalToString(2.2) --輸出2-20 */ CREATE function [dbo].[DecimalToString]( @Num as decimal(18,2) ) returns varchar(20) as begin declare @Index int declare @Str varchar(20) set @Str = cast(@Num as varchar(20)) set @Index = charindex(‘.‘,@Str) if @Num%1 <> 0 begin set @Str = left(@Str,@Index-1) + ‘-‘ + cast(cast((@Num - cast(@Num as int))*100 as int) as varchar(20)) end else begin set @Str = cast(cast(@Num as int) as varchar(20)) end return @Str end
0x07
給用戶提示
select N‘序號 ‘ + dbo.DecimalToString(Seq) + N‘ 重復或者前面有斷號‘ as BreakNum from #rst
BreakNum |
序號 1-2 重復或者前面有斷號 |
序號 1-5 重復或者前面有斷號 |
序號 2-2 重復或者前面有斷號 |
序號 4 重復或者前面有斷號 |
序號 6-8 重復或者前面有斷號 |
序號 6-10 重復或者前面有斷號 |
所有源代碼(可以直接復制按F5運行):
create table #SerialNum( Seq varchar(20) ) insert into #SerialNum (Seq) select ‘1‘ union all select ‘1-1‘ union all select ‘1-2‘ union all select ‘1-2‘ union all select ‘1-5‘ union all select ‘1-6‘ union all select ‘2‘ union all select ‘2-2‘ union all select ‘4‘ union all select ‘6-8‘ union all select ‘6-10‘ create table #temp( Seq decimal(18,2) ) insert into #temp (Seq) select dbo.StringToDecimal(Seq) from #SerialNum ;with t1 as ( --t1篩選出小數序號連續性 select min(Seq) as Seq --找出脫離連續序號的一組序號的最小值,就知道哪裏斷開了 from (select [gid] = Seq - cast(cast(pid -1 as decimal(18,2))/100 as decimal(18,2)) --計算差值,如果序號連續,那麽gid一定等於序號的整數部分 ,Seq from (select row_number() over(partition by cast(Seq as int) order by Seq) as pid --在每個大序號內的排序 ,Seq from #temp) a ) b group by cast(Seq as int),gid having cast(Seq as int) <> gid --找出gid不等於序號整數的分組 ),t2 as ( --t2篩選出整數序號連續性 select min(Seq) as Seq from (select row_number() over (order by _Seq) as pid,Seq from (select cast(Seq as int) as _Seq, min(Seq) as Seq from #temp group by cast(Seq as int)) a) b group by (cast(Seq as int) - pid) having (cast(Seq as int) - pid) <> 0 --序號減去行號不等於零就是不連續 ),t3 as ( --t3檢查重復性 select Seq from #temp group by Seq having count(Seq) > 1 union all select Seq from t1 union all select Seq from t2 ) select distinct Seq into #rst from t3 order by Seq select N‘序號 ‘ + dbo.DecimalToString(Seq) + N‘ 重復或者前面有斷號‘ as BreakNum from #rst drop table #SerialNum drop table #temp drop table #rstView Code
總結:這是本人第一次寫博客筆記,語言組織上可能有一點缺陷,望前輩們見諒。
PS:此文章是原創,轉載需聲明出處。
現實項目中用戶隨意添加序號,如何用SQL解決序號連續性問題