函数将字符串拆分成小数?

时间:2009-09-16 12:22:12

标签: sql sql-server sql-server-2005 tsql

我正在尝试编写一个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 个答案:

答案 0 :(得分:1)

您无法在SQL中定义此特殊内容。

您可以做的最好的事情是使用动态SQL创建全局临时表(##)。然后可以随后使用。

答案 1 :(得分:1)

在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操作,因为当您插入DECIMAL列时,SQL Server将隐式尝试转换VARCHAR(MAX)表达式。

无论哪种方式,它都不是很漂亮,我建议您在使用动态SQL以及它带来的所有麻烦之前,先考虑以其他方式解决问题的可能性。

答案 2 :(得分:1)

尝试这个,我只编码支持精度为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) 

示例代码中的OUTPUT:

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

(3 row(s) affected)

答案 3 :(得分:1)

这是一个通用函数,用于将任何文本字符串解析为值表...您可以轻松地使用它来完成您要完成的任务:

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

答案 4 :(得分:0)

CAST和DYNAMIC SQL,虽然我不相信函数能够很好地支持后者。我在思考:

EXEC 'SELECT 
  CAST(''' +  
         SUBSTRING(@SEQUENCE, 1, @Prec - @Scale) + 
         @Delim + 
         SUBSTRING(@SEQUENCE, @Prec - @Scale + 1) + 
       "'' 
       AS DECIMAL(' + @Prec + ', ' + @Scale + ')'

答案 5 :(得分:0)

正如其他人所提到的,表值用户定义函数必须为每个字段都有特定的返回类型。

我想解决的方法是稍微改变设计。让函数将[sequence]分解为一个字符串表。不要进行转换......

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可能是一个真正的痛苦......