简体   繁体   中英

Compare strings and Highlight mismatch wherever found in T-SQL

I have below Material table which contains data like this:

 [PO Number] [Actual Material]   [Ideal Material]
 ----------------------------------------------------
 1000000     Milk-Sugar-tea       Milk-Sugar-Coffee
 1000001     Milk-Water           Milk-Water-Ice-tea

I have the requirement where I need to compare two columns Actual Material and Ideal material and highlight the mismatch materials in SQL.

Mismatch would be

[PO Number] [Actual Material]   [Ideal Material]     [Mismatch]
----------------------------------------------------------------  
1000000     Milk-Sugar-tea       Milk-Sugar-Coffee    tea-coffee
1000001     Milk-Water           Milk-Water-Ice-tea   Ice-tea

How to achieve this in a SQL query?

I use a table value functions for split actual-material an ideal-material values.

Split Function detail is ALTER FUNCTION [dbo].[Split] ( @String NVARCHAR(4000), @Delimiter NCHAR(1) ) RETURNS TABLE AS RETURN ( WITH Split(stpos,endpos) AS( SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos UNION ALL SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1) FROM Split WHERE endpos > 0 ) SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)), 'Value' = LTRIM(SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)) FROM Split )

For result table is

declare @result table (
[PO Number] int , [Actual Material] varchar(100),[Ideal Material] varchar(100),Mismatch varchar(200)
)

And query for result table insert is :

;with CTE AS (
select distinct s.* ,x1.Value x1value,x2.Value x2value
from dbo.material s 
 outer apply (select *from Split(s.[Actual Material],'-')) x1 
 outer apply (select *from Split(s.[Ideal Material],'-')) x2 
),
CTE2 AS (
SELECT  distinct c.[PO Number],c.[Actual Material],c.[Ideal Material]
  ,case when not exists (select *from CTE c2 where c2.[PO Number] = c.[PO Number] and c2.x2value = c.x1value ) then c.x1value else '' end [ActualMismatch]
,case when not exists (select *from CTE c2 where c2.[PO Number] = c.[PO Number]         and c2.x1value = c.x2value ) then c.x2value else '' end [IdealMismatch]
 FROM CTE c 
 )

 insert into @result
 SELECt  c.[PO Number],c.[Actual Material],c.[Ideal Material],c.ActualMismatch Mismatch from CTE2 c
 union 
 SELECt c.[PO Number],c.[Actual Material],c.[Ideal Material] ,c.IdealMismatch Mismatch  from CTE2  c
  where 
  (c.ActualMismatch !='' or 
 c.[IdealMismatch] !='')
 order by 1 

 select [PO Number],[Actual Material],[Ideal Material],
        STUFF((
    SELECT '-' + mismatch 
    FROM @result 
    WHERE ([PO Number] = c.[PO Number]) 
    FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
  ,1,2,'') AS mismatch

  from @result c 
 where Mismatch !=''
Group by [PO Number],[Actual Material],[Ideal Material]

how to coalesce mismatch values ! => with xml stuff

As many others have very sensibly suggested, your first port of call should be to restructure your database so you are actually storing normalised data against your PO Numbers .


That said, something we are dealt a rubbish hand and have to play the cards we get. To answer your question exactly as it is asked, you can do the following:

If you are not on SQL Server 2016 and therefore cannot use the built in string_split function, start by creating your own:

create function [dbo].[StringSplit]
(
    @str nvarchar(4000) = ' '               -- String to split.
    ,@delimiter as nvarchar(1) = ','        -- Delimiting value to split on.
    ,@num as int = null                     -- Which value to return.
)
returns @results table(ItemNumber int, Item nvarchar(4000))
as
begin
    declare @return nvarchar(4000);

    -- Handle null @str values
    select @str = case when len(isnull(@str,'')) = 0 then '' else @str end;

                    -- Start tally table with 10 rows.
    with n(n)   as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))

                    -- Select the same number of rows as characters in @str as incremental row numbers.
                    -- Cross joins increase exponentially to a max possible 10,000 rows to cover largest @str length.
        ,t(t)   as (select top (select len(@str) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)

                    -- Return the position of every value that follows the specified delimiter.
        ,s(s)   as (select 1 union all select t+1 from t where substring(@str,t,1) = @delimiter)

                    -- Return the start and length of every value, to use in the SUBSTRING function.
                    -- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
        ,l(s,l) as (select s,isnull(nullif(charindex(@delimiter,@str,s),0)-s,4000) from s)

    insert into @results
    select rn as ItemNumber
            ,Item
    from(select row_number() over(order by s) as rn
                ,substring(@str,s,l) as item
        from l
        ) a
    where rn = @num
        or @num is null;

    return;

end

Using this function you can then create a set each for your Actual Material and Ideal Material columns, combine them to find the differences using a full join and then concatenate the results using stuff and for xml into one string value:

declare @t table(PONumber int, ActualMaterial nvarchar(50), IdealMaterial nvarchar(50));
insert into @t values (1000000,'Milk-Sugar-tea','Milk-Sugar-Coffee'),(1000001,'Milk-Water','Milk-Water-Ice-tea');

with a as
(
    select t.PONumber
        ,a.Item
    from @t t
        outer apply dbo.StringSplit(t.ActualMaterial,'-',null) a
), i as
(
    select t.PONumber
            ,i.Item
    from @t t
        outer apply dbo.StringSplit(t.IdealMaterial,'-',null) i
), m as
(
    select isnull(a.PONumber,i.PONumber) as PONumber
            ,isnull(a.Item,i.Item) as Item
    from a
        full join i
            on(a.PONumber = i.PONumber
                and a.Item = i.Item
                )
    where a.Item is null
        or i.Item is null
)
select t.PONumber
        ,t.ActualMaterial
        ,t.IdealMaterial
        ,stuff((select '-' + m.Item
                    from m
                    where t.PONumber = m.PONumber
                    order by m.Item
                    for xml path('')
                )
                ,1,1,'') as Mismatch
from @t t
order by PONumber;

Output:

+----------+----------------+--------------------+------------+
| PONumber | ActualMaterial |   IdealMaterial    | Mismatch   |
+----------+----------------+--------------------+------------+
|  1000000 | Milk-Sugar-tea | Milk-Sugar-Coffee  | Coffee-tea |
|  1000001 | Milk-Water     | Milk-Water-Ice-tea | Ice-tea    |
+----------+----------------+--------------------+------------+

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM