2009-09-16 12 views
0

J'essaye actuellement d'écrire une fonction de SQL Server 2005, qui obtient une chaîne comme paramètre et crée une table avec des valeurs décimales dehors.Fonction diviser la chaîne en décimales?

Le problème est que je dois définir le type décimal basé sur les paramètres. Cet extrait ne fonctionne pas devrait démontrer l'idée:

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

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

Des idées, comment cela pourrait-il être fait?

+2

Deux liens pour vous: http http://www.sommarskog.se/arrays-in-sql-2005.html et : //www.sommarskog.se/dynamic_sql.html (SQL dynamique serait nécessaire pour la décimale de @Prec et @Scale) –

Répondre

1

Ceci est une fonction générique pour analyser toute chaîne de texte dans une table de valeurs ... Vous pouvez facilement l'utiliser pour faire ce que vous essayez d'accomplir:

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 et DYNAMIC SQL, bien que je ne crois pas que les fonctions supportent bien ce dernier. Je pensais dans le sens de:

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

Vous ne pouvez pas définir ce adhoc dans SQL. Le mieux que vous puissiez faire est de créer une table temporaire globale (##) en utilisant le SQL dynamique. Ensuite, il peut être utilisé par la suite.

1

En T-SQL, une fonction doit avoir un type de retour concret. Vous ne pourrez pas renvoyer une table contenant des types de données différents, sauf si vous les convertissez en quelque chose de basique à interpréter par un autre processus (par exemple un VARCHAR), mais cela semblerait renverser le but de votre fonction.

Ce que vous pouvez faire est de créer une table en utilisant SQL dynamique, qui vous permettra de spécifier la précision et de l'échelle dans la définition de la table:

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 

Avec la table définie, vous devriez être en mesure d'insérer dans les lignes en utilisant l'opérateur CAST pour convertir les données d'une manière similaire:

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 

on peut dire que, vous pouvez même pas besoin de l'opération CAST, comme SQL Server tentera implicitement de convertir votre expression VARCHAR (MAX) lorsque vous insérez dans la Colonne DECIMAL.

Quoi qu'il en soit, ce n'est pas joli et je suggérerais d'examiner la possibilité de résoudre votre problème d'une autre manière, avant d'avoir recours à SQL dynamique et tous les maux de tête qu'il apporte.

+0

@Programming Hero a déclaré que _a fonction doit avoir un type de retour concret, ce qui n'est pas vrai, voir mon fonction et exemple de code. –

+0

sql_variant est un type de retour concret, qui peut contenir plusieurs types de valeur. Il ne semble pas adapté à ce problème car il ne vous permet pas de renvoyer une table de valeurs de type connu. –

0

Comme mentionné par d'autres, les fonctions définies par l'utilisateur de la table doivent avoir un type de retour spécifique pour chaque champ.

La façon dont je contournerais cela est de changer légèrement la conception. Avoir la fonction casser la [séquence] dans une table de chaînes. Ne pas faire la conversion encore ...

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) 
) 

Ensuite, une fois que vous avez une table de chaînes, appliquer la conversion dont vous avez besoin. Comme mentionné par d'autres, ce serait probablement par SQL dynamique.

L'existence du SQL dynamique dans votre corps principal de code, cependant, peut être une vraie douleur ...

1

essayer, je ne codé pour soutenir décimaux jusqu'à une précision de 5, mais vous pouvez l'augmenter si nécessaire:

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 

ce code exemple utilise la fonction:

 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) 

SORTIE à partir de l'exemple de code:

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

(3 row(s) affected) 
Questions connexes