2009-09-16 46 views
0

我目前正在尝试编写一个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) 
) 

任何想法,这可以做什么?

+2

两个链接为你:http://www.sommarskog.se/arrays-in-sql-2005.html和http ://www.sommarskog.se/dynamic_sql.html(动态SQL将需要@Prec和@Scale的小数) – 2009-09-16 12:58:40

回答

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 
0

CAST和DYNAMIC SQL,尽管我不相信函数支持后者。我一直在想:

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

你不能在SQL中定义这个adhoc。

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

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 

可以说,你可能甚至不需要转换操作,因为SQL Server将隐式地试图当你插入转换您的VARCHAR(MAX)的表达DECIMAL列。

无论哪种方式,它不是漂亮,我建议在寻找解决您的问题,一些其他的方式的可能性,你诉诸使用动态SQL和所有它带来的麻烦了。

+0

@编程英雄说_a函数必须有一个具体的返回type_,这是不正确的,看到我的功能和示例代码。 – 2009-09-16 13:33:40

+0

sql_variant是一个具体的返回类型,可以保存多种类型的值。它似乎不适合这个问题,因为它不允许你返回一个已知类型值的表。 – 2009-09-16 13:54:05

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在你的代码主体的所有脑干,但是,可能是一个真正的痛苦......

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 @[email protected] 
    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)