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