2011-08-08 24 views
13

He estado obteniendo el error "La conversión ha fallado al convertir de una cadena de caracteres a uniqueidentifier" y finalmente estoy al final de mi cuerda. Reduje mi problema a lo más pequeño posible, manteniendo el error intacto. Instalar el divisor de CSV desde aquí en primer lugar si se quiere reproducir:Conversión fallida al convertir de una cadena de caracteres a un error de identificador único en SQL Server

http://www.sqlservercentral.com/articles/Tally+Table/72993/

Aquí está el código de prueba. Estoy en SQL 2008R2 pero en una base de datos que es compatible con SQL 2005:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ZZZTESTTABLE]') AND type in (N'U')) 
DROP TABLE [dbo].[ZZZTESTTABLE] 
GO 

CREATE TABLE [dbo].[ZZZTESTTABLE](
    [Col1] [uniqueidentifier] NOT NULL, 
CONSTRAINT [PK_ZZZTESTTABLE] PRIMARY KEY CLUSTERED 
(
    [Col1] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

-- Test table that I would like to check my values against 
insert dbo.ZZZTESTTABLE(Col1) values('85B049B7-CDD0-4995-B582-5A74523039C0') 

-- Test string that will be split into table in the DelimitedSplit8k function 
declare @temp varchar(max) = '918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5' 

-- I'm trying to delete all data in the ZZZTESTTABLE that is not in my string but I get the error 
delete dbo.ZZZTESTTABLE 
where Col1 not in 
(
-- ERROR OCCURS HERE 
    select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
) 

aquí está la fuente de la función DelimitedSplit8K por lo que no tiene que ir y encontrarlo:

CREATE FUNCTION dbo.DelimitedSplit8K 
--===== Define I/O parameters 
     (@pString VARCHAR(8000), @pDelimiter CHAR(1)) 
RETURNS TABLE WITH SCHEMABINDING AS 
RETURN 
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000... 
    -- enough to cover VARCHAR(8000) 
    WITH E1(N) AS (
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
       SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
       ),       --10E+1 or 10 rows 
     E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows 
     E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max 
cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front 
        -- for both a performance gain and prevention of accidental "overruns" 
       SELECT 0 UNION ALL 
       SELECT TOP (DATALENGTH(ISNULL(@pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4 
       ), 
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT t.N+1 
        FROM cteTally t 
        WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1), 
     Item  = SUBSTRING(@pString,s.N1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)) 
    FROM cteStart s 
; 
+0

Sospecho que la función de división ya que es donde está la complejidad. Si agrega un elemento DONDE no es nulo y LEN (Elemento)> 1 a la selección dividida, ¿todavía da un error? – hatchet

+0

"Acabo de encontrar un script aleatorio en Internet, no entiendo cómo funciona, pero planeo usarlo, ¿alguien puede depurarlo por mí?" –

+0

La función de división parece correcta. También me gustaría saber por qué tu eliminación no funciona como está escrita. Falla de la misma manera si se reescribe como EXISTO. – hatchet

Respuesta

5

No está seguro de lo que está pasando aquí, pero el problema no parece ser el formato de los GUID o la salida de la función. La ejecución de este funciona:

declare @temp varchar(max) = '918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5'  
select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 

Tal vez el procesador de consultas está mirando el esquema de retorno de la función y diciendo que no se puede convertir a uniqueidentifier? Con suerte, alguien más puede proporcionar una respuesta específica a eso.

Selección de la salida de la función de división en una tabla temporal funcionará:

select cast(Item as uniqueidentifier) as Item into #temp from dbo.DelimitedSplit8K(@temp, ',') 

-- I'm trying to delete all data in the ZZZTESTTABLE that is not in my string but I get the error 
delete dbo.ZZZTESTTABLE 
where Col1 not in 
(
-- ERROR OCCURS HERE 
    --select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
    select Item from #temp 
) 
+1

Gracias, esa es una solución que se nos ocurrió y probablemente la que vamos a utilizar. Algunas cosas que noté que pueden estar relacionadas con son: Puedo sacar la declaración Select de la cláusula where y funciona bien. También puedo cambiar NOT IN a simplemente IN y no obtengo ningún error (aunque no obtengo el resultado que deseo). Parece estar relacionado con la forma en que la UDF (Función definida por el usuario) devuelve o utiliza un CTE (Common Table Expression). Si cambio el UDF para devolver una cadena codificada en una tabla, no obtendré el error. Creo que un error o característica de SQL. Muchas gracias por mirar esto !!! – CreativeJourney

1

miradas como si hubiera leído mal la pregunta la primera vez. Buen trabajo produciendo un script de prueba que reproduzca el error. Lo siguiente funciona para mí:

delete dbo.ZZZTESTTABLE 
WHERE Col1 in 
(
    select Z.Col1 
    from dbo.ZZZTESTTABLE Z 
    LEFT JOIN dbo.DelimitedSplit8K(@temp, ',') S on S.Item = Z.Col1 
    where S.Item is null 
) 
OPTION (force order) 
+0

Todos son válidos. Puedo sacar la instrucción Select de la cláusula where y funciona bien. También puedo cambiar NOT IN a simplemente IN y no obtengo ningún error (aunque no obtengo el resultado que deseo). Parece estar relacionado con la forma en que la UDF (Función definida por el usuario) devuelve o utiliza un CTE (Common Table Expression). Si cambio el UDF para devolver una cadena codificada, no obtendré el error. Parece un error o característica de SQL. – CreativeJourney

+0

@CreativeJourney - Esto funciona sin una tabla temporal – RichardTheKiwi

2

Por qué lanzar el elemento a uniqueidentifier cuando puede hacerlo al revés.

En lugar de

where Col1 not in 
(
-- ERROR OCCURS HERE 
    select cast(Item as uniqueidentifier) from dbo.DelimitedSplit8K(@temp, ',') 
) 

es posible que intente esto:

where cast(Col1 as varchar(64)) not in 
(
    select Item 
    from dbo.DelimitedSplit8K(@temp, ',') 
) 
+0

si la función split está escupiendo cadenas que no se pueden convertir a uniqueidentifer, entonces está destruyendo las cadenas de alguna manera. Evitar el CAST eliminaría el error, pero el resultado aún no sería correcto (es decir, algo se eliminaría y no debería, o viceversa). – hatchet

+0

siendo un optimista, simplemente asumí que la función DelimitedSplit8K hace bien su trabajo. Las pruebas que deberían ser realmente simples, es decir, ejecutar SELECT * FROM dbo.DelimitedSplit8K ('918E809E-EA7A-44B5-B230-776C42594D91,6F8DBB54-5159-4C22-9B0A-7842464360A5', ',') – Skorpioh

+0

la función de división divide las cadenas está bien, y su solución funciona alrededor de cualquier rareza que está causando que el lanzamiento falle en el código original. – hatchet

8

El uso de este UDF es de hecho hacer suposiciones acerca de procedimiento orden de ejecución. Asume que la cláusula WHERE dentro de la UDF se evaluará antes decast(item as uniqueidentifier). Esta suposición es errónea ya que el optimizador es libre de cambiar el plan para mover la cláusula WHERE sobre el elenco y el efecto neto es que al elenco se le pide que convierta un token parcial a un guid (es decir, una cadena como 18E809E-EA7A-44B5-B230-776C42594D91).

Para obtener una respuesta más detallada, lea T-SQL functions do no imply a certain order of execution.

Como solución alternativa se puede obligar a NULL en los valores proyectados de la UDF para las filas que no cumplen con la cláusula WHERE:

CREATE FUNCTION dbo.DelimitedSplit8K 
... 
cteStart(N1, nullify) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter) 
       SELECT t.N+1, 
        case when (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) then 1 else 0 end 
        FROM cteTally t 
        WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
       ) 
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found. 
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1), 
     Item  = case s.nullify 
      when 1 then SUBSTRING(@pString,s.N1,ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)) 
      else null 
      end 
    FROM cteStart s; 
go 

Debido a que la expresión CASE está garantizado para ser evaluados antes de que el CAST (dado que la entrada de CAST es la salida de CASE) el reordenamiento de la cláusula WHERE es seguro.

+0

Te envié un mensaje de LinkedIn con una mala suposición porque leí mal tu blog. Por favor, ignore mis humildes disculpas. –

Cuestiones relacionadas