我读到有一个函数等同于SQL Server 2017下DB2下的标准函数TRANSLATE。但是在早期版本下如何做?
功能定义:here
编辑:
我感到愚蠢--MatBailie正确地指出我的原始解决方案是不正确的。我实际上一直认为TRANSLATE('abc', 'abc', 'bcd')
应该返回ddd但是,在测试SQL Server 2017的TRANSLATE后,我看到'bcd'将是正确的答案。您可以通过查看此帖子的历史记录来查看我的原始(不正确的版本)。这是使用ngrams8k的更新解决方案:
DECLARE
@string varchar(8000) = 'abc',
@fromChar varchar(100) = 'abc', -- note: no mutation
@toChar varchar(100) = 'bcd';
SELECT newString =
(
SELECT CASE WHEN x>z THEN '' WHEN x>0 THEN s ELSE t END+''
FROM dbo.ngrams8k(@string,1) ng
CROSS APPLY (VALUES (charindex(ng.token,@fromChar),len(@toChar),ng.token)) x(x,z,t)
CROSS APPLY (VALUES (ng.position, substring(@toChar,x.x,1))) xx(p,s)
ORDER BY xx.p
FOR XML PATH(''), TYPE
).value('(text())[1]', 'varchar(8000)');
返回> bcd
我提出我的翻译功能:
CREATE FUNCTION [dbo].[F_Translate]
(
@String varchar(8000),
@FromChar varchar(200),
@ToChar varchar(200)
)
RETURNS varchar(8000)
AS
BEGIN
DECLARE @result as varchar(8000) = NULL
DECLARE @currentChar as char(1) = NULL
DECLARE @CurrentIndexFounded as int = 0
DECLARE @CurrentIndexString as int = 0
IF(@FromChar IS NULL OR @ToChar IS NULL)
BEGIN
return cast('Parameters @FromChar and @ToChar must contains 1 caracter minimum' as int);
END
ELSE IF(DATALENGTH(@FromChar) <> DATALENGTH(@ToChar) OR DATALENGTH(@FromChar) = 0)
BEGIN
return cast('Parameters @FromChar and @ToChar must contain the same number of characters (at least 1 character)' as int);
END
IF(@String IS NOT NULL)
BEGIN
SET @result = '';
WHILE(@CurrentIndexString < DATALENGTH(@String))
BEGIN
SET @CurrentIndexString = @CurrentIndexString + 1;
SET @currentChar = SUBSTRING(@String, @CurrentIndexString, 1);
SET @CurrentIndexFounded = CHARINDEX(@currentChar COLLATE Latin1_General_CS_AS, @FromChar COLLATE Latin1_General_CS_AS);
IF(@CurrentIndexFounded > 0)
BEGIN
SET @result = CONCAT(@result, SUBSTRING(@ToChar, @CurrentIndexFounded, 1)) ;
END
ELSE
BEGIN
SET @result = CONCAT(@result, @currentChar);
END
END
END
return @result
END
优于WHILE
循环 - 至少在我看来 - 包含在函数中的奇怪更新:
您可以在表中维护替换值。您可以添加一些分组键(例如,用于语言选择或主题焦点)并将其作为附加参数传递给函数:
CREATE TABLE ReplaceValues (FindChar NVARCHAR(100) NOT NULL
,ReplWith NVARCHAR(100) NOT NULL
,SortOrder INT NOT NULL);
INSERT INTO ReplaceValues VALUES('a','x',1) --all "a" will be "x"
,('test','yeah!',2) --"test" will be "yeah"
,('hello','ciao',3) --"hello" will be "ciao"
,('xxx','magic',4); --this is magic (see below)
GO
- 你不能使用内联的古怪更新,但你可以将它包装在标量函数中:
CREATE FUNCTION dbo.MultiReplace(@ReplaceTarget VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
--Quirky Update: One of the rare situations where this is a good idea
SELECT @ReplaceTarget=REPLACE(@ReplaceTarget,rv.FindChar,rv.ReplWith)
FROM ReplaceValues AS rv
ORDER BY rv.SortOrder;
RETURN @ReplaceTarget;
END
GO
- 包含测试数据的表格
declare @t table(TestString varchar(100))
insert into @t values('This string is without repls')
,('This is a test, hello, one more test')
,('See the cascade replace with aaa, which is converted to xxx, then to magic');
--...and the magic is going in here:
SELECT t.TestString
,dbo.MultiReplace(t.TestString) AS Translated
FROM @t AS t
GO
- 清理
DROP FUNCTION dbo.MultiReplace;
DROP TABLE ReplaceValues;
结果
This string is without repls
This is x yeah!, ciao, one more yeah!
See the cxscxde replxce with magic, which is converted to magic, then to mxgic
以为我也会把我的想法放进去。这避免了可怕的WHILE
循环,并且,也没有使用自引用变量(可能变得丑陋)。
注意首先使用Tally表,然后我使用表值函数(而不是标量,这很慢)来完成工作。
请注意,我已将其设置为如果您在右侧提供较少的参数,则该角色将被删除。因此,如果参数@FindChars
的值为'AB'
,@ReplaceChars
的值为'C'
,则'A'
将被替换为'C'
,'B'
将被替换为''
。我注意到使用TRANSLATE
这会产生错误The second and third arguments of the TRANSLATE built-in function must contain an equal number of characters.
然而,问题是,你不能使用像THROW
或RAISERROR
这样的东西。这意味着无法在函数内部实际产生错误。但是,您可以设置一些内容,以便在两个长度不匹配时返回NULL
,但(不幸的是)错误生成无法在函数内部执行。
CREATE VIEW dbo.Tally WITH SCHEMABINDING
AS
WITH C1 AS (SELECT 1 AS I UNION ALL SELECT 1),
C2 AS (SELECT 1 AS I FROM C1 AS L CROSS JOIN C1 AS R),
C3 AS (SELECT 1 AS I FROM C2 AS L CROSS JOIN C2 AS R),
C4 AS (SELECT 1 AS I FROM C3 AS L CROSS JOIN C3 AS R),
C5 AS (SELECT 1 AS I FROM C4 AS L CROSS JOIN C4 AS R),
C6 AS (SELECT 1 AS I FROM C5 AS L CROSS JOIN C5 AS R),
RN AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM C6)
SELECT TOP (1000000) N
FROM RN
ORDER BY N;
GO
CREATE FUNCTION dbo.OwnTranslate (@String varchar(8000),@FindChars varchar(8000), @ReplaceChars varchar(8000))
RETURNS TABLE
AS RETURN (
WITH ToBeReplaced AS (
SELECT @String AS String,
FC.N,
SUBSTRING(@FindChars, FC.N,1) AS FC,
ISNULL(SUBSTRING(@ReplaceChars, RC.N,1),'') AS RC
FROM (SELECT TOP (LEN(@FindChars)) N FROM Tally) FC
OUTER APPLY (SELECT TOP (LEN(@ReplaceChars)) T.N FROM Tally T WHERE T.N = FC.N AND T.N <= LEN(@ReplaceChars)) RC),
Replacing AS (
SELECT N, REPLACE(String, FC, RC) AS ReplacedString
FROM ToBeReplaced
WHERE N = 1
UNION ALL
SELECT R.N + 1, REPLACE(ReplacedString, TBR.FC, TBR.RC) AS ReplacedString
FROM ToBeReplaced TBR
JOIN Replacing R ON TBR.N = R.N + 1)
SELECT TOP 1 ReplacedString
FROM Replacing
ORDER BY N DESC);
GO
WITH VTE AS (
SELECT *
FROM (VALUES ('This is a string to be Translated.')) V(S))
SELECT VTE.S, OT.ReplacedString
FROM VTE
CROSS APPLY dbo.OwnTranslate (VTE.S, 'Ts ', 'qz_') OT;
GO
--Clean up
DROP FUNCTION dbo.OwnTranslate;
DROP VIEW Tally;
如有任何问题,请询问。
改编自@ Shnugo的答案。这更接近你想要的。你只需要确定你有一个dbo.numbers
表(它们真的很有用)。
http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=627828307504174dcf3f61313ba384a8
CREATE FUNCTION dbo.MultiReplace(@ReplaceTarget NVARCHAR(MAX), @from_chars NVARCHAR(MAX), @to_chars NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
--Quirky Update: One of the rare situations where this is a good idea
SELECT @ReplaceTarget=REPLACE(@ReplaceTarget,SUBSTRING(@from_chars, id+1, 1), SUBSTRING(@to_chars, id+1, 1))
FROM numbers
WHERE id < LEN(@from_chars) AND id < LEN(@to_chars)
ORDER BY id;
RETURN @ReplaceTarget;
END
并略微超过顶部的方式来满足您的要求,TRANSLATE('abc', 'abc', 'bcd') => 'bcd')
。
CREATE FUNCTION dbo.Translate(@ReplaceTarget NVARCHAR(MAX), @from_chars NVARCHAR(MAX), @to_chars NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE
@steps INT = LEN('_' + @from_chars + '_') - 2
;
WITH
dictionary(id, string_from, string_interim, string_to) AS
(
SELECT
id, string_from, N'<' + string_from + N'>', string_to
FROM
(
SELECT
id,
ROW_NUMBER() OVER (PARTITION BY string_from ORDER BY id) AS occurence,
string_from,
string_to
FROM
numbers
CROSS APPLY
(
SELECT
CAST(SUBSTRING(@from_chars, numbers.id, 1) AS NVARCHAR(5)) AS string_from,
CAST(SUBSTRING(@to_chars, numbers.id, 1) AS NVARCHAR(5)) AS string_to
)
chars
WHERE
numbers.id > 0
AND numbers.id <= @steps
)
sorted_dictionary
WHERE
occurence = 1
)
,
mapping_sequence(id, string_from, string_to) AS
(
SELECT 1, N'<', N'<<>' WHERE @from_chars LIKE N'%<%'
UNION ALL SELECT 2, N'>', N'<>>' WHERE @from_chars LIKE N'%>%'
UNION ALL SELECT 3, N'<<<>>', N'<<>' WHERE @from_chars LIKE N'%<%' AND @from_chars LIKE N'%>%'
UNION ALL SELECT 3 + id, string_from, string_interim FROM dictionary WHERE string_from NOT IN (N'<', N'>')
UNION ALL SELECT 3 + @steps + id, string_interim, string_to FROM dictionary
)
SELECT
@ReplaceTarget = REPLACE(@ReplaceTarget, string_from, string_to)
FROM
mapping_sequence
ORDER BY
id
;
RETURN @ReplaceTarget;
END
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=9dbe7214ac4b5bb00060686cfaa879c2
上面的一个可能的小优化(尽可能减少REPLACE调用的数量)......
CREATE FUNCTION dbo.Translate(
@ReplaceTarget NVARCHAR(MAX),
@from_chars NVARCHAR(MAX),
@to_chars NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE
@steps INT = LEN('_' + @from_chars + '_') - 2
;
WITH
dictionary AS
(
SELECT
id, string_from, string_to
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY string_from ) AS id,
ROW_NUMBER() OVER (PARTITION BY string_from ORDER BY id) AS occurence,
string_from,
string_to
FROM
numbers
CROSS APPLY
(
SELECT
CAST(SUBSTRING(@from_chars, numbers.id, 1) AS NVARCHAR(5)) AS string_from,
CAST(SUBSTRING(@to_chars, numbers.id, 1) AS NVARCHAR(5)) AS string_to
)
chars
WHERE
numbers.id > 0
AND numbers.id <= @steps
)
sorted_dictionary
WHERE
occurence = 1
),
two_stage AS
(
SELECT
map.*
FROM
dictionary dict
CROSS APPLY
(
SELECT COUNT(*) FROM dictionary WHERE dictionary.id > dict.id AND dictionary.string_from = dict.string_to
)
remap(hits)
CROSS APPLY
(
SELECT id, dict.string_from, dict.string_to WHERE remap.hits = 0 AND dict.string_from NOT IN (N'<', N'>')
UNION ALL
SELECT id, dict.string_from, N'<' + dict.string_from + N'>' WHERE remap.hits > 0 AND dict.string_from NOT IN (N'<', N'>')
UNION ALL
SELECT id + @steps, N'<' + dict.string_from + N'>', dict.string_to WHERE remap.hits > 0 AND dict.string_from NOT IN (N'<', N'>')
UNION ALL
SELECT id + @steps * 2, N'<' + dict.string_from + N'>', dict.string_to WHERE dict.string_from IN (N'<', N'>')
)
map
)
,
mapping_sequence(id, string_from, string_to) AS
(
SELECT 1, N'<', N'<<>' WHERE @from_chars LIKE N'%<%'
UNION ALL SELECT 2, N'>', N'<>>' WHERE @from_chars LIKE N'%>%'
UNION ALL SELECT 3, N'<<<>>', N'<<>' WHERE @from_chars LIKE N'%<%' AND @from_chars LIKE N'%>%'
UNION ALL SELECT 3 + id, string_from, string_to FROM two_stage
)
SELECT
@ReplaceTarget = REPLACE(@ReplaceTarget, string_from, string_to)
FROM
mapping_sequence
ORDER BY
id
;
RETURN @ReplaceTarget;
END
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=8af6ae050dc8d425521ae911b70a7968
要么...
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=1451aa88780463b1e7cfe15dd0071194
要么...
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=3079d4dd4289e8696072f6ee37be76ae