Vista con modifica tipo campo

di il
12 risposte

Vista con modifica tipo campo

Buongiorno, mi servirebbe creare una vista dove, oltre ad avere delle semplici select su campi esistenti dovrei "duplicare" uno di questi e convertirlo da varchar a numerico
Ad esempio:
SELECT IDLavorazioneOdP, IDFase, Valore, CONVERT(float, Valore) AS valorenew
FROM dbo.LavorazioniOdP
Messaggio: error converting datatype varchar to float
per dare un idea il campo valore contiene questi dati
3,5669444444444440
0,0255555555555556
e cosi via
immagino che la colpa sia della virgola, sbaglio?
IN qualunque caso come potrei fare a fare questa conversione?
GRazie

12 Risposte

  • Re: Vista con modifica tipo campo

    Ciao

    Sicuramente la virgola può essere un problema, ma dipende da un insieme di fattori.
    Hai provato a cambiare la lingua di default per vedere se risolve il problema?

    https://docs.microsoft.com/it-it/sql/t-sql/statements/set-language-transact-sql?view=sql-server-ver16
  • Re: Vista con modifica tipo campo

    Grazie per la risposta
    ho provato in questo modo e ha funzionato
    CAST(REPLACE(Valore, ',', '.') AS float) AS ValoreNumerico,
  • Re: Vista con modifica tipo campo

    Ciao.
    La tua soluzione lascia spazio ad errori a runtime qualora sulla base dati provenissero stringhe non convertibili in un numero.
    Per esempio, se al campo Valore vi fosse per errore la stringa '2,a5', otterresti lo stesso errore lamentato inizialmente.
    In altre parole, la tua soluzione non pare robusta.

    Per ovviare a ciò, una possibile soluzione potrebbe essere quella di analizzare ciascun valore del campo Valore per escludere quelli non convertibili.

    A tale scopo, ti mostro una function che provvede proprio a questo.

    Naturalmente, il codice che ho scritto è una stesura generica che tu, se vorrai, potrai adattare ed adottare ai tuoi scopi.
    Il dimensionamento dei campi e delle variabili è puramente esemplificativo.
    
    create function [dbo].[ufn_Valore](@valore varchar(100))
    returns float as
    begin
    	declare @i tinyint = 0
    	declare @l tinyint = len(@valore)
    	declare @c char(1)
    	declare @Ret varchar(max) = ''
    
    	while @i < @l
    		begin
    			set @i = @i + 1
    			set @c = substring(@valore, @i, 1)
    			if @c = '0' set @Ret = @Ret + @c else		
    			if @c = '1' set @Ret = @Ret + @c else
    			if @c = '2' set @Ret = @Ret + @c else
    			if @c = '3' set @Ret = @Ret + @c else
    			if @c = '4' set @Ret = @Ret + @c else
    			if @c = '5' set @Ret = @Ret + @c else
    			if @c = '6' set @Ret = @Ret + @c else
    			if @c = '7' set @Ret = @Ret + @c else
    			if @c = '8' set @Ret = @Ret + @c else
    			if @c = '9' set @Ret = @Ret + @c else
    			if @c = ',' set @Ret = @Ret + '.' else
    			begin
    				set @Ret = '';
    				set @i = @l
    			end
    		end
    	;
    	if @Ret = '' set @Ret = NULL
    	return @Ret
    end
    
    Creata la funzione, potresti richiamarla dalla query principale come di seguito.

    Posto di avere una tabella del tipo:
    
    declare @TABLE as table
    (
    	Valore varchar(100)
    )
    
    e così popolata:
    
    insert into @TABLE values ('3,5669444444444440'), ('0,0255555555555556'), ('7;23456'), (''), ('2,a5'), ('1')
    
    la query principale potrebbe essere del tipo:
    
    select 
    	Valore
    	, dbo.ufn_Valore(Valore) as ValoreNew
    from
    	@TABLE
    
    
  • Re: Vista con modifica tipo campo

    Mi sono accorto di aver postato una function mancante di alcune istruzioni.

    La function corretta è questa:
    
    create function [dbo].[ufn_Valore](@valore varchar(100))
    returns float as
    begin
    	declare @i tinyint = 0
    	declare @l tinyint = len(@valore)
    	declare @c char(1)
    	declare @Ret varchar(max) = ''
    	declare @comma tinyint=0
    	
    	while @i < @l
    		begin
    			set @i = @i + 1
    			set @c = substring(@valore, @i, 1)
    			if @c = '0' set @Ret = @Ret + @c else		
    			if @c = '1' set @Ret = @Ret + @c else
    			if @c = '2' set @Ret = @Ret + @c else
    			if @c = '3' set @Ret = @Ret + @c else
    			if @c = '4' set @Ret = @Ret + @c else
    			if @c = '5' set @Ret = @Ret + @c else
    			if @c = '6' set @Ret = @Ret + @c else
    			if @c = '7' set @Ret = @Ret + @c else
    			if @c = '8' set @Ret = @Ret + @c else
    			if @c = '9' set @Ret = @Ret + @c else
    			if @c = ',' and @comma = 0
    			begin
    				set @Ret = @Ret + '.'
    				set @comma = @comma + 1
    			end else
    			if @c = '.' and @comma = 0
    			begin
    				set @Ret = @Ret + @c
    				set @comma = @comma + 1
    			end else 
    			begin
    				set @Ret = '';
    				set @i = @l
    			end
    		end
    	;
    	if @Ret = '' set @Ret = NULL
    	return @Ret
    end
    
  • Re: Vista con modifica tipo campo

    Soldino ha scritto:


    La tua soluzione lascia spazio ad errori a runtime qualora sulla base dati provenissero stringhe non convertibili in un numero.
    Per verificare se una stringa è un numero oppure no, basta la , che per altro gestisce anche i numeri negativi, cosa che la tua non fa.

    Io presumo che lui abbia già la sicurezza di avere dei numeri in quella colonna e la semplice esigenza di convertirli in numero, quindi il CONVERT+REPLACE è già sufficiente.
  • Re: Vista con modifica tipo campo

    Ciao.

    Si, in linea di principio, sarei anch'io d'accordo con te. Tuttavia ISNUMERIC presenta delle particolarità operative per cui tendo a non usarla.

    Sempre in linea generale, il fatto che ISNUMERIC ritorni 1 non significa necessariamente che si possa fare sul suo argomento ogni tipo di casting, sia pure lecito dal punto di vista concettuale.

    A titolo di esempio, ISNUMERIC supporta argomenti del tipo '1e3', ossia il numero 1000 espresso in notazione esponenziale.

    Tuttavia, mentre l'istruzione CAST('1000' AS INT) ritorna correttamente l'intero 1000, l'espressione CAST('1e3' AS INT) produce un errore.
    Al contrario, CAST('1e3' AS FLOAT) funziona correttamente.

    In generale, quindi, il ricorrere a ISNUMERIC non ci mette al riparo da possibili errori a run-time.

    Voglio dire, insomma, che in ogni caso si è chiamati ad un adattamento in base al contesto e allora tanto vale scriversi delle funzioni proprie, se ritenuto conveniente, per ogni specifica esigenza.

    Io lo faccio anche per una migliore scalabilità del codice dal momento che, a seconda delle versioni di SQL Server, non tutte le funzioni sono disponibili.

    Ci sono da segnalare, poi, i comportamenti di ISNUMERIC in presenza dei segni '+', '-' e di valuta e non è detto che siano sempre desiderabili in ogni caso.

    Hai ragione, la mia funzione non supporta i numeri negativi!
    Anzi, dirò di più, nemmeno i positivi se sono preceduti dal segno '+' )

    E' chiaro che ho voluto scrivere una soluzione venuta "di getto", senza voler avere la benché minima pretesa di presentare una soluzione elegante e/o esaustiva.

    Anzi, a guardarla meglio, la soluzione è proprio un abbozzo che si potrebbe riscrivere senz'altro in modo migliore.
    D'altra parte, il compito del Forum è piuttosto quello di "mettere la pulce nell'orecchio" che quello di fornire soluzioni complete e pronte all'uso.

    Certamente è come dici tu, che i dati sul db sono già corretti dal punto di vista delle conversioni. Il mio era solo un modo di dire che, nel caso vi fossero stringhe sui generis, la query sarebbe andata comunque in errore.

    Così, giusto perché lo hai notato e poiché mi ritengo anche un appassionato di soluzione in SQL, ho colto l'occasione per riscriverla in modo che sia più semplice e che supporti anche i segni '+' e '-', per quello che vale naturalmente e senza nessuna pretesa di completezza.

    Chiedo scusa per la mia prolissità davvero spropositata.
    
    create function [dbo].[ufn_Valore](@valore varchar(100))
    returns float as
    begin
    	declare @Ret varchar(100) = (case when @valore='' then NULL else replace(ltrim(@valore), ',', '.') end)	
    	declare @comma tinyint = 0
    	declare @segno varchar(1)
    	declare @i tinyint = 0
    	declare @l tinyint
    	declare @c char(1)		
    
    	select @segno = left(@Ret, 1) where left(@Ret, 1) in ('+', '-')
    	if @segno is not null set @Ret = stuff(@Ret, 1, 1, '') else set @segno=''
    	set @l = len(@Ret)
    
    	while @i < @l
    		begin
    			if @comma > 1 set @Ret = null else
    			begin
    				set @i = @i + 1
    				set @c = substring(@Ret, @i, 1)			
    				if @c = '.' 
    					set @comma = @comma + 1 
    				else 
    					if (ascii(@c) < ascii('0') or ascii(@c) > ascii('9')) set @Ret = null
    				;
    			end
    			if @Ret is null set @i = @l
    		end
    	;
    	return cast(@segno + @Ret as float)
    end
    
  • Re: Vista con modifica tipo campo

    Soldino ha scritto:


    A titolo di esempio, ISNUMERIC supporta argomenti del tipo '1e3', ossia il numero 1000 espresso in notazione esponenziale.

    Tuttavia, mentre l'istruzione CAST('1000' AS INT) ritorna correttamente l'intero 1000, l'espressione CAST('1e3' AS INT) produce un errore.
    Al contrario, CAST('1e3' AS FLOAT) funziona correttamente. Queste chicche mi mancavano... Avevo tatto una prova da esadecimale ad int e non andava, ma alla notazione esponenziale non avevo pensato. Grazie della info.

    Soldino ha scritto:


    Voglio dire, insomma, che in ogni caso si è chiamati ad un adattamento in base al contesto e allora tanto vale scriversi delle funzioni proprie, se ritenuto conveniente, per ogni specifica esigenza.
    Concordo, anche se alle volte mettersi a reinventare la ruota non è il miglior modo di passare il tempo.

    Soldino ha scritto:


    ...il compito del Forum è piuttosto quello di "mettere la pulce nell'orecchio" che quello di fornire soluzioni complete e pronte all'uso.
    Amen

    Soldino ha scritto:


    Chiedo scusa per la mia prolissità davvero spropositata.
    Sarò strano, ma a me i post prolissi ma argomentati piacciono.

    Non te ne avere a male, però resto convito del fatto che "semplice è bello". Se ho già gli strumenti per fare una cosa, a rifarli mi espongo a bug che non ho preventivato. Quindi lunga vita al CONVERT+REPLACE

    P.S. Si, penso si possa efficientare anche la seconda versione, ma finiamo OT
  • Re: Vista con modifica tipo campo

    Sgrubak ha scritto:


    Sarò strano, ma a me i post prolissi ma argomentati piacciono.

    Sgrubak ha scritto:


    Non te ne avere a male, però resto convito del fatto che "semplice è bello". Se ho già gli strumenti per fare una cosa, a rifarli mi espongo a bug che non ho preventivato. Quindi lunga vita al CONVERT+REPLACE
    Ma figurati! Condivido.

    Sgrubak ha scritto:


    P.S. Si, penso si possa efficientare anche la seconda versione, ma finiamo OT
    Non ne dubito! In generale, io ci metto molto più tempo a scrivere codice beta che codice stabile!
  • Re: Vista con modifica tipo campo

    Salve,
    giusto per....

    a me la soluzione di @soldino non piace per il potenziale forte impatto... e' una funzione utente che lavora sulle stringhe, e sappiamo tutti molto bene quanto poco sia performante in questo senso T-SQL e SQL Server... tecnicamente la scriverei allora in c# o in altro linguaggio supportato dal runtime di SQL Server... probabilmente le performance sarebbero migliori...

    mi spaventa una proiezione
    SELECT [dbo].[ufn_Valore](colonna) FROM tabella_da_100krighe
    e al di la' che tutti sappiamo che non vada MAI eseguita una select senza filtri, sappiamo tutti altrettanto bene che proiezioni pesantissime vengono sempre e sicuramente eseguite...
    l'altro giorno da noi e' stata eseguita una proiezione di documenti contabili per un cliente, noto travel agency, dove i documenti riepilogati erano 12k... e ogni documento ha ovviamente n righe di dettaglio... la join recuperava sicuramente 100k righe... e una funzione di questo tipo, innestata nel codice, mi spaventerebbe molto...

    il TRY_CAST(...) o TRY_CONVERT(...), nativi di T-SQL, sicuramente risultano prestazionalmente piu' validi, e sono disponibili dalla versione 2012, quindi, personalmente, NON cercherei una soluzione scritta a mano, sicuramente efficace, ma altrettanto sicuramente in questo caso NON efficiente, per "reinventare" la ruota...
    in questo appoggio completamente @sgrubak

    nel caso invece debba gestire un processo ETL, allora si che cercherei di mettere piu' logica possibile nella pulizia del dato "in ingresso", ovviamente per prevenire problematiche di conversione nel dominio rappresentato nell'entita'...

    [OT]
    e se parliamo di dominio, al di la' che sicuramente "l'abbiamo fatto tutti", ci sarebbe da investigare/discutere il come mai una colonna varchar ospiti "un numero", perdipiu' espresso con separatore decimale "non standard" ma dipendente dalla locale, come anche eventualmente sempre un varchar che ospiti una data, o quant'altro...
    si, lo so... l'abbiamo fatto tutti... cio' non toglie che il non utilizzare il corretto dominio fa perdere molto senso ad una base dati relazionale, relegandola per certi versi ad un simil spazio di storage generico

    mi e' comunque piaciuto "il tentativo"

    salutoni romagnoli
    --
    Andrea
  • Re: Vista con modifica tipo campo

    Nella tua realtà, quali sono i numeri che possono esserci nella tabella?

    Giusto per sapere, poichè se ci sono numeri "normali" solo che hanno la vigola al posto del punto, puoi fare tutto con una sintassi standard.

    Prendendo ad esempio i numeri che ho visto nei vari post, con una TRY_PARSE sei già a posto.

    Eccoti tutte le query necessarie per fare le prove:
    
    
    CREATE TABLE [Lavorazioni] 
    (
    	[Codice] NVARCHAR(10),
    	[Valore] NVARCHAR(20)
    )
    GO
    
    INSERT INTO [Lavorazioni] ([Codice], [Valore])
    VALUES
    ('Lav1', '3,5669444444444440'),
    ('Lav2', '+3,5669444444444440'),
    ('Lav3', '-3,5669444444444440'),
    ('Lav4', '0,0255555555555556'),
    ('Lav5', '+0,0255555555555556'),
    ('Lav6', '-0,0255555555555556'),
    ('Lav7', '1e3'),
    ('Lav8', 'chi102')
    GO
    
    SELECT 
    	  [Codice] 
    	, [Valore]
    	, TRY_PARSE([Valore] as float USING 'it-IT') as [ValoreConvertito]
    FROM [Lavorazioni]
    GO
    
    

    Aggiungo anche il risultato.
    Ovviamente a video il valore convertito, quando la conversione riesce, spesso è uguale a Valore, la differenza è solo nel tipo di dato che da varchar diventa float:
    
    Codice	Valore			ValoreConvertito
    Lav1	3,5669444444444440	3,56694444444444
    Lav2	+3,5669444444444440	3,56694444444444
    Lav3	-3,5669444444444440	-3,56694444444444
    Lav4	0,0255555555555556	0,0255555555555556
    Lav5	+0,0255555555555556	0,0255555555555556
    Lav6	-0,0255555555555556	-0,0255555555555556
    Lav7	1e3			1000
    Lav8	chi102			NULL
    

    Scusa "asql", ho visto il tuo post dopo che avevo già scritto, per cui avendo messo degli esempi, ho lasciato il post.
    Spero non ti offenderai se anche io suggerisco di usare la TRY_PARSE
  • Re: Vista con modifica tipo campo

    Hai fatto benissimo !!
    salutoni romagnoli
    --
    Andrea
  • Re: Vista con modifica tipo campo

    asql ha scritto:


    Salve,
    giusto per....

    a me la soluzione di @soldino non piace per il potenziale forte impatto...
    Da quel punto di vista concordo, non piace nemmeno a me

    asql ha scritto:


    mi e' comunque piaciuto "il tentativo"
    Ti ringrazio.
    Saluti.
Devi accedere o registrarti per scrivere nel forum
12 risposte