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
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;
+----------+----------------+--------------------+------------+
| 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.