Функция разбивает строку на десятичные дроби? - PullRequest
0 голосов
/ 16 сентября 2009

В настоящее время я пытаюсь написать функцию SQL Server 2005, которая получает строку в качестве параметра и создает из нее таблицу с десятичными значениями.

Проблема в том, что я должен определить десятичный тип на основе параметров. Этот не работающий фрагмент должен продемонстрировать идею:

CREATE FUNCTION [dbo].[ufn_ParseDecimal]
(
    @Sequence VARCHAR(max),
    @Delim CHAR(1),
    @Prec INT,
    @Scale INT
)

RETURNS @DecimalList TABLE (
fValue decimal(@Prec, @Scale)
)

Есть идеи, как это можно сделать?

Ответы [ 6 ]

1 голос
/ 16 сентября 2009

Это универсальная функция для разбора любой текстовой строки в таблицу значений ... Вы можете легко использовать ее для выполнения того, что вы пытаетесь выполнить:

ALTER FUNCTION [dbo].[ParseTextString] (@S Text, @delim VarChar(5))
Returns @tOut Table 
    (ValNum Integer Identity Primary Key, 
     sVal VarChar(8000))
As
Begin 
Declare @dLLen TinyInt       -- Length of delimiter
Declare @sWin  VarChar(8000) -- Will Contain Window into text string
Declare @wLen  Integer       -- Length of Window
Declare @wLast TinyInt     -- Boolean to indicate processing Last Window
Declare @wPos  Integer     -- Start Position of Window within Text String
Declare @sVal  VarChar(8000) -- String Data to insert into output Table
Declare @BtchSiz Integer     -- Maximum Size of Window
    Set @BtchSiz = 7900      -- (Reset to smaller values to test routine)
Declare @dPos Integer        -- Position within Window of next Delimiter
Declare @Strt Integer        -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
If @delim is Null Set @delim = '|'
If DataLength(@S) = 0 Or
      Substring(@S, 1, @BtchSiz) = @delim Return
-- ---------------------------
Select @dLLen = Len(@delim),
       @Strt = 1, @wPos = 1,
       @sWin = Substring(@S, 1, @BtchSiz)
Select @wLen = Len(@sWin),
       @wLast = Case When Len(@sWin) = @BtchSiz
           Then 0 Else 1 End,
       @dPos = CharIndex(@delim, @sWin, @Strt)
-- ------------------------------------
  While @Strt <= @wLen
  Begin
      If @dPos = 0 -- No More delimiters in window
      Begin                      
          If @wLast = 1 Set @dPos = @wLen + 1 
          Else 
          Begin
              Set @wPos = @wPos + @Strt - 1
              Set @sWin = Substring(@S, @wPos, @BtchSiz)
              -- ----------------------------------------
              Select @wLen = Len(@sWin), @Strt = 1,
                     @wLast = Case When Len(@sWin) = @BtchSiz
                              Then 0 Else 1 End,
                     @dPos = CharIndex(@delim, @sWin, 1)
              If @dPos = 0 Set @dPos = @wLen + 1 
          End
      End
      -- -------------------------------
      Set @sVal = LTrim(Substring(@sWin, @Strt, @dPos - @Strt))
      Insert @tOut (sVal) Values (@sVal)
      -- -------------------------------
      -- Move @Strt to char after last delimiter
      Set @Strt = @dPos + @dLLen 
      Set @dPos = CharIndex(@delim, @sWin, @Strt)
   End
   Return
End
1 голос
/ 16 сентября 2009

попробуйте, я только кодировал, чтобы поддерживать десятичные числа с точностью до 5, но вы можете увеличить его при необходимости:

CREATE FUNCTION [dbo].[ufn_ParseDecimal]
(
    @Sequence VARCHAR(max),
    @Delim CHAR(1),
    @Prec INT,
    @Scale INT
 )
RETURNS sql_variant
AS

BEGIN

DECLARE @L  VARCHAR(max)
DECLARE @R  VARCHAR(max)

IF CHARINDEX(@Delim,@Sequence)>0
BEGIN
    SET @L=LEFT(@Sequence,CHARINDEX(@Delim,@Sequence)-1)
    SET @R=RIGHT(@Sequence,LEN(@Sequence)-CHARINDEX(@Delim,@Sequence))
END
ELSE
BEGIN
    SET @L=@Sequence
    SET @R=''
END

DECLARE @1_0 decimal(1,0)
DECLARE @1_1 decimal(1,1)

DECLARE @2_0 decimal(2,0)
DECLARE @2_1 decimal(2,1)
DECLARE @2_2 decimal(2,2)

DECLARE @3_0 decimal(3,0)
DECLARE @3_1 decimal(3,1)
DECLARE @3_2 decimal(3,2)
DECLARE @3_3 decimal(3,3)

DECLARE @4_0 decimal(4,0)
DECLARE @4_1 decimal(4,1)
DECLARE @4_2 decimal(4,2)
DECLARE @4_3 decimal(4,3)
DECLARE @4_4 decimal(4,4)

DECLARE @5_0 decimal(5,0)
DECLARE @5_1 decimal(5,1)
DECLARE @5_2 decimal(5,2)
DECLARE @5_3 decimal(5,3)
DECLARE @5_4 decimal(5,4)
DECLARE @5_5 decimal(5,5)

DECLARE @v sql_variant

IF @Prec=1 
BEGIN
    IF @Scale=0      BEGIN  SET @1_0=RIGHT(@L,1)     SET @v= @1_0 END
    ELSE IF @Scale=1 BEGIN  SET @1_1='0.'+LEFT(@R,1) SET @v= @1_1 END
END
ELSE IF @Prec=2 
BEGIN
    IF @Scale=0      BEGIN  SET @2_0=RIGHT(@L,2)                SET @v= @2_0 END
    ELSE IF @Scale=1 BEGIN  SET @2_1=RIGHT(@L,1)+'.'+LEFT(@R,1) SET @v= @2_1 END
    ELSE IF @Scale=2 BEGIN  SET @2_2=           '0.'+LEFT(@R,2) SET @v= @2_2 END
END
ELSE IF @Prec=3 
BEGIN
    IF @Scale=0      BEGIN  SET @3_0=RIGHT(@L,3)                SET @v= @3_0 END
    ELSE IF @Scale=1 BEGIN  SET @3_1=RIGHT(@L,2)+'.'+LEFT(@R,1) SET @v= @3_1 END
    ELSE IF @Scale=2 BEGIN  SET @3_2=RIGHT(@L,1)+'.'+LEFT(@R,2) SET @v= @3_2 END
    ELSE IF @Scale=3 BEGIN  SET @3_3=           '0.'+LEFT(@R,3) SET @v= @3_3 END
END
ELSE IF @Prec=4 
BEGIN
    IF @Scale=0      BEGIN  SET @4_0=RIGHT(@L,4)                SET @v= @4_0 END
    ELSE IF @Scale=1 BEGIN  SET @4_1=RIGHT(@L,3)+'.'+LEFT(@R,1) SET @v= @4_1 END
    ELSE IF @Scale=2 BEGIN  SET @4_2=RIGHT(@L,2)+'.'+LEFT(@R,2) SET @v= @4_2 END
    ELSE IF @Scale=3 BEGIN  SET @4_3=RIGHT(@L,1)+'.'+LEFT(@R,3) SET @v= @4_3 END
    ELSE IF @Scale=4 BEGIN  SET @4_4=           '0.'+LEFT(@R,4) SET @v= @4_4 END
END
ELSE IF @Prec=5 
BEGIN
    IF @Scale=0      BEGIN SET @5_0=RIGHT(@L,5)                SET @v= @5_0 END
    ELSE IF @Scale=1 BEGIN SET @5_1=RIGHT(@L,4)+'.'+LEFT(@R,1) SET @v= @5_1 END
    ELSE IF @Scale=2 BEGIN SET @5_2=RIGHT(@L,3)+'.'+LEFT(@R,2) SET @v= @5_2 END
    ELSE IF @Scale=3 BEGIN SET @5_3=RIGHT(@L,2)+'.'+LEFT(@R,3) SET @v= @5_3 END
    ELSE IF @Scale=4 BEGIN SET @5_4=RIGHT(@L,1)+'.'+LEFT(@R,4) SET @v= @5_4 END
    ELSE IF @Scale=5 BEGIN SET @5_5=           '0.'+LEFT(@R,5) SET @v= @5_5 END
END

RETURN @v

END

этот пример кода использует функцию:

      SELECT CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.4','.',4,1) , 'BaseType')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.4','.',4,1) , 'Precision')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.4','.',4,1) , 'Scale'))  ,dbo.ufn_ParseDecimal('123.4','.',4,1) 
UNION SELECT CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.45','.',5,2), 'BaseType')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.45','.',5,2), 'Precision')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('123.45','.',5,2), 'Scale'))  ,dbo.ufn_ParseDecimal('123.45','.',5,2)
UNION SELECT CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('1.234','.',5,4) , 'BaseType')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('1.234','.',5,4) , 'Precision')),CONVERT(varchar(10),SQL_VARIANT_PROPERTY(dbo.ufn_ParseDecimal('1.234','.',5,4) , 'Scale'))  ,dbo.ufn_ParseDecimal('1.234','.',5,4) 

ВЫХОД из образца кода:

---------- ---------- ---------- ---------
decimal    4          1          123.4
decimal    5          2          123.45
decimal    5          4          1.2340

(3 row(s) affected)
1 голос
/ 16 сентября 2009

В T-SQL функция должна иметь конкретный тип возвращаемого значения. Вы не сможете вернуть таблицу, которая содержит переменные типы данных, если вы не преобразуете их в нечто базовое для интерпретации другим процессом (например, VARCHAR), но это может подорвать назначение вашей функции.

Что вы можете сделать, это создать таблицу с использованием динамического SQL, которая позволит вам указать точность и масштаб в определении таблицы:

DECLARE @table NVARCHAR(MAX)
SET @table = '#DecimalTable'

DECLARE @sql NVARCHAR(MAX)
DECLARE @params NVARCHAR(MAX)

SET @sql = N'CREATE TABLE ' + @table 
        + '([fValue] DECIMAL (' + @Prec + ',' + @Scale + '))'

EXEC @sql

Определив таблицу, вы сможете вставлять в строки оператор CAST для преобразования данных аналогичным образом:

SET @sql = N'INSERT INTO ' + @table
        + 'VALUES (CAST(@Seq AS DECIMAL(' + @Prec + ',' @Scale + '))'

SET @params = N'@Seq VARCHAR(MAX)'

EXEC sp_executesql @sql, @params, @Sequence

Возможно, вам даже может не понадобиться операция CAST, поскольку SQL Server будет неявно пытаться преобразовать выражение VARCHAR (MAX) при вставке в столбец DECIMAL.

В любом случае, это нехорошо, и я бы посоветовал взглянуть на возможность решения вашей проблемы другим способом, прежде чем прибегать к использованию динамического SQL и всех причиняющих ему головную боль.

1 голос
/ 16 сентября 2009

Вы не можете определить этот adhoc в SQL.

Лучшее, что вы можете сделать, - это создать глобальную временную таблицу (##) с использованием динамического SQL. Тогда это может быть использовано впоследствии.

0 голосов
/ 16 сентября 2009

Как уже упоминалось другими, пользовательские функции с табличными значениями должны иметь определенный тип возврата для каждого поля.

Я бы хотел обойти это, слегка изменив дизайн. Пусть функция разбивает [последовательность] на таблицу строк. Не делайте преобразование еще ...

CREATE FUNCTION [dbo].[ufn_ParseList] (
    @Sequence VARCHAR(MAX),
    @Delim CHAR(1)
)

RETURNS @List TABLE (
    id INT IDENTITY(1,1),
    item VARCHAR(MAX)   -- You may want to use something smaller than (MAX)
)

Затем, когда у вас есть таблица строк, примените необходимое преобразование. Как уже упоминалось другими, это, вероятно, из-за динамического SQL.

Однако наличие динамического SQL в основной части кода может быть настоящей болью ...

0 голосов
/ 16 сентября 2009

CAST и ДИНАМИЧЕСКИЙ SQL, хотя я не верю, что функции все равно поддерживают последние Я думал так:

EXEC 'SELECT 
  CAST(''' +  
         SUBSTRING(@SEQUENCE, 1, @Prec - @Scale) + 
         @Delim + 
         SUBSTRING(@SEQUENCE, @Prec - @Scale + 1) + 
       "'' 
       AS DECIMAL(' + @Prec + ', ' + @Scale + ')'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...