我正在尝试编写一个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)
)
任何想法,如何做到这一点?
答案 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可能是一个真正的痛苦......