T-SQL (SQL Server 2016), 519 bytes
with X as(select 32a,char(32)b union all select a+1,char(a+1)from X where a<126),E AS(SELECT n FROM(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)),F AS(SELECT a.n FROM E a,E b),G AS(SELECT a.n FROM F a,F b),T AS(SELECT ROW_NUMBER()OVER(ORDER BY(SELECT NULL))n FROM G),I as(SELECT ASCII(SUBSTRING(t,n,1))a FROM T JOIN(select @i as t)S on T.n<=LEN(S.t)where n=0),R as(select b from X where a not in(select a from I))select STUFF((SELECT''+b FROM R FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)'),1,1,'')
... but this assumes the input string is in @i
, therefore a runnable script will look like:
declare @i varchar(101) = 'Test';
with X as(select 32a,char(32)b union all select a+1,char(a+1)from X where a<126),E AS(SELECT n FROM(VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)),F AS(SELECT a.n FROM E a,E b),G AS(SELECT a.n FROM F a,F b),T AS(SELECT ROW_NUMBER()OVER(ORDER BY(SELECT NULL))n FROM G),I as(SELECT ASCII(SUBSTRING(t,n,1))a FROM T JOIN(select @i as t)S on T.n<=LEN(S.t)where n=0),R as(select b from X where a not in(select a from I))select STUFF((SELECT''+b FROM R FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)'),1,1,'')
Try it online!
How it works
Use CTEs to split the input string into individual characters on separate rows in a result set; use a recursive CTE to generate a result set having individual characters on separate rows for all characters between ASCII values 32 and 126; select all rows from the latter where characters are not found in the former; use STUFF
with FOR XML PATH
to aggregate those characters (in lieu of the STRING_AGG
function, which only arrived in SQL Server 2017).
Legible version
declare @input varchar(101) = 'Test';
with X as (
select 32a, char(32)b
union all
select a+1, char(a+1) from X where a < 126
),
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteTally(n) AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) n
FROM E4
),
INPUT_STRING as (
SELECT ASCII( SUBSTRING(term, n, 1)) as [ASCII Code]
FROM cteTally t
JOIN (select @input as term) S on t.n <= LEN(S.term)
where n = 0
),
DATA_RESULTS as (
select b from X where a not in (select [ASCII Code] from INPUT_STRING)
)
select STUFF( (SELECT '' + b FROM DATA_RESULTS FOR XML PATH (''),TYPE).value('(./text())[1]','VARCHAR(MAX)'), 1, 1, '' )
;
Why?
Because I can.
Can it be shorter?
Almost certainly!
set( 'a', 'b', 'c' )
\$\endgroup\$