1. 程式人生 > >現實項目中用戶隨意添加序號,如何用SQL解決序號連續性問題

現實項目中用戶隨意添加序號,如何用SQL解決序號連續性問題

font cast 語言 另一個 有時 -1 col .com end

前段時間,一直忙於學習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 all
select 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 #rst
View Code

總結:這是本人第一次寫博客筆記,語言組織上可能有一點缺陷,望前輩們見諒。

PS:此文章是原創,轉載需聲明出處。

現實項目中用戶隨意添加序號,如何用SQL解決序號連續性問題