/ / Tenha alguém para uma função do SQL Server que, devido a um padrão e um valor, possa retornar um valor normalizado - sql, função, padrões de design, sql-server-2008-r2

Ter alguém para uma função do SQL Server que, devido a um padrão e um valor, possa retornar um valor normalizado - sql, função, padrões de design, sql-server-2008-r2

Estou programando uma função no SQL 2008R2 que eupoderia fornecer alguns parâmetros, como um valor varchar, um padrão varchar, um separador char e um filler também char. Então eu gostaria de dar o valor "22687" com o padrão "000.000.000.000", um separador "." e o preenchimento seria "0", gostaria de esperar que a função retorne "000.000.022.687", alguém já possui uma função que possa fazer isso?

Algo assim:

DECLARE @valor VARCHAR(30)
DECLARE @formato VARCHAR(30)
DECLARE @separador CHAR(1)
DECLARE @rellenarcon CHAR(1)

SELECT @valor       = "22959"
SELECT @formato     = "000.000.000.000"
SELECT @separador   = "."
SELECT @rellenarcon = "0"

DECLARE @n  INTEGER
DECLARE @m  INTEGER
DECLARE @i  INTEGER
DECLARE @j  INTEGER

SELECT @n   = LEN(@formato)
SELECT @m   = LEN(@valor)
SELECT @i   = 1
SELECT @j   = 1

DECLARE @res2 varchar(30)

SELECT @res2 = ""
SELECT @valor = REVERSE(@valor)

WHILE @i<=@n
BEGIN
if SUBSTRING(@formato,@i,1) <> @separador
begin
IF @j<=@m
BEGIN
SELECT @res2 = @res2 + SUBSTRING(@valor,@j,1)
SELECT @i=@i+1
SELECT @j=@j+1
END
ELSE
BEGIN
SELECT @res2 = @res2 + @rellenarcon
SELECT @i=@i+1
END
end
else
BEGIN
SELECT @res2 = @res2 + @separador
SELECT @i=@i+1
END
END

print reverse(@res2)

É um código cruzado de java para tsql, o código original em java é:

    public static String formatear(String valor, String formato, char separator,
char fillWith, Map<Integer, String> params) {

int n = formato.length() - 1;
int m = valor.length() - 1;
int i = n;
int j = m;

StringBuilder res = new StringBuilder(formato);

for(; i >= 0; i--) {
if(res.charAt(i) != separator) {
if(j >= 0) {
res.deleteCharAt(i);
res.insert(i, valor.charAt(j--));
} else {
res.deleteCharAt(i);
res.insert(i, fillWith);
}
}
}
if(params != null) {
Set<Integer> keys = params.keySet();

for(Integer key : keys) {
i = key;
res.deleteCharAt(i);
res.insert(i, params.get(key));
}
}

return res.toString();
}

Respostas:

0 para resposta № 1

O seguinte assume entradas bem formadas, p. o valor não é maior que o padrão.

declare @Pattern as VarChar(64) = "000.000.000.000";
declare @Fill as Char = "0";
declare @Value as VarChar(64) = "22687";
declare @False as Bit = 0;
declare @True as Bit = 1;

with Gargoyle as (
select @Pattern as Pattern, @Value as Value, Cast( "" as VarChar(64) ) as Buffer,
case when Right( @Pattern, 1 ) = @Fill then @True else @False end as Fill
union all
select
-- Always consume a character from the pattern.
Left( Pattern, Len( Pattern ) - 1 ),
-- Consume a character from the value if the pattern contains fill at the current position.
case
when Fill = @True and Value != "" then Left( Value, Len( Value ) - 1 )
else Value end,
-- Add the correct character to the buffer.
Cast( case when Fill = @True and Value != "" then Right( Value, 1 ) else Right( Pattern, 1 ) end + Buffer as VarChar(64) ),
-- Check the next pattern character for fill.
case
when Len( Pattern ) = 1 then @False
when Substring( Pattern, Len( Pattern ) - 1, 1 ) = @Fill then @True
else @False end
from Gargoyle
where Pattern != ""
)
select Buffer
from Gargoyle
where Pattern = "";

Ou, como uma função:

create function dbo.PatternFill( @Pattern as VarChar(64), @Fill as Char, @Value as VarChar(64) )
returns VarChar(64)
as
begin
declare @Buffer as VarChar(64) = ""
declare @PatternChar as Char = Right( @Pattern, 1 )
declare @ValueChar as Char = Right( @Value, 1 )
while @Pattern != ""
begin
if @PatternChar = @Fill and @ValueChar != ""
begin
-- Replace a fill character with a value character.
select @Buffer = @ValueChar + @Buffer
if Len( @Value ) > 1
select @Value = Left( @Value, Len( @Value ) - 1 ), @ValueChar = Right( @Value, 1 )
else
select @ValueChar = "", @Value = ""
end
else
begin
-- Copy the pattern character.
select @Buffer = @PatternChar + @Buffer
end
if Len( @Pattern ) > 1
select @Pattern = Left( @Pattern, Len( @Pattern ) - 1 ), @PatternChar = Right( @Pattern, 1 )
else
select @PatternChar = "", @Pattern = ""
end
return @Buffer
end
go

declare @Result as VarChar(64)
declare @Count as Int = 1000000
declare @Start as DateTime = GetDate()
while @Count > 0
select @Result = dbo.PatternFill( "000.000.000.000", "0", "22687" ), @Count = @Count - 1
select @Result as [Result], DateDiff( ms, @Start, GetDate() ) as [Total ms]

1.000.000 de iterações no meu notebook levaram 151.656ms, mas está ocupado BOINCing. Esse é um timing simples, sem correção do tempo consumido por um loop vazio ou chamando uma função vazia.


0 para resposta № 2

Esta consulta fará isso:

;with cteZeroPadded(Num) as
(
Select  Right("000000000000" + "22687", 12)
)
,cteSplit as
(
Select  SUBSTRING(Num, 1, 3) Col1
,SUBSTRING(Num, 4, 3) Col2
,SUBSTRING(Num, 7, 3) Col3
,SUBSTRING(Num, 10, 3) Col4
From    cteZeroPadded
)
Select  Col1 + "." + Col2 + "." + Col3 + "." + Col4
From    cteSplit

0 para resposta № 3

No SQLServer2005 + você pode usar a opção com recurso recursivo CTE

DECLARE @valor varchar(30) = "22959",
@formato varchar(30) = "000000000000000",
@text varchar(30),
@result varchar(30) = N""

SET @text = REVERSE(RIGHT(@formato + @valor, 15))

;WITH cte AS
(
SELECT 1 AS Number, SUBSTRING(@text, 1, 1) AS Num
UNION ALL
SELECT c.Number + 1,
CASE WHEN c.Number IN(3, 7, 11) THEN "." ELSE
SUBSTRING(@text, CASE WHEN c.Number > 11 THEN c.Number - 2
WHEN c.Number > 7 THEN c.Number - 1
WHEN c.Number > 3 THEN c.Number
ELSE c.Number + 1
END, 1)
END
FROM cte c
WHERE Number < LEN(@text)
)
SELECT @result += c.Num
FROM cte c
ORDER BY Number DESC

SELECT @result

Ver demo em SQLFiddle