2009-09-16 45 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)