2010-02-24 34 views
6

Necesito encontrar el valor más alto de la base de datos que satisfaga una cierta convención de formato. En concreto, me gustaría encontrar el valor más alto que se parece aT-SQL IsNumeric() y Linq-to-SQL

EU999999 ('9' ser cualquier dígito)

select max (col) devolverá algo como 'EUZ ...' para instancia que quiero excluir. La siguiente consulta hace el truco, pero no puedo producir esto a través de Linq-to-SQL. Parece que no hay traducción para la función isnumeric() en SQL Server.

select max(col) from table where col like 'EU%' 
    and 1=isnumeric(replace(col, 'EU', '')) 

Escribir una función de base de datos, procedimientos almacenados, o cualquier otra cosa de esa naturaleza es muy abajo en la lista de mis soluciones preferidas, ya que esta tabla es central en mi aplicación y no puede reemplazar fácilmente el objeto de tabla con otra cosa .

¿Cuál es la siguiente mejor solución?

Respuesta

10

Aunque ISNUMERIC no se encuentra, siempre se puede probar con el casi equivalente NOT LIKE '%[^0-9]%, es decir, no hay no sea un dígito en la cadena, o alternativamente, la cadena está vacía o consiste únicamente de dígitos:

from x in table 
where SqlMethods.Like(x.col, 'EU[0-9]%') // starts with EU and at least one digit 
    && !SqlMethods.Like(x.col, '__%[^0-9]%') // and no non-digits 
select x; 

por supuesto, si se sabe que el número de dígitos es fija, esto se puede simplificar a

from x in table 
where SqlMethods.Like(x.col, 'EU[0-9][0-9][0-9][0-9][0-9][0-9]') 
select x; 
+0

Me gusta su sugerencia y la aceptaré, pero mi prefijo (y la longitud) no es una constante, por desgracia, así que tendría que generar la expresión de forma dinámica. Eso no me gusta, y estoy pensando en formas de cambiar el esquema para poder evitar esto. – cdonner

+0

Una de las ventajas de utilizar un 'LIKE' sin comodín en la parte delantera es que es muy amigable con el índice (se puede realizar una comprobación de rango contra el índice). – Ruben

+0

Una alternativa muy inteligente a IsNumeric en expresiones de longitud variable. Busque caracteres no numéricos en cualquier lugar de la cadena. ¡Gracias por el consejo! – BlueMonkMN

1

Como usted ha dicho, no hay traducción para IsNumeric de LINQ a SQL. Hay algunas opciones, ya escribió la función de base de datos y el procedimiento almacenado. Me gusta agregar dos más.

Opción 1: Usted puede hacer esto mediante la mezcla de LINQ to SQL con LINQ a objetos, pero cuando se tiene una gran base de datos, no se puede esperar un gran rendimiento:

var cols = (from c in db.Table where c.StartsWith("EU") select c).ToList(); 
var stripped = from c in cols select int.Parse(c.Replace("EU", "")); 
var max = stripped.Max(); 

Opción 2: cambiar su esquema de base de datos :-)

+0

no es una opción, demasiados datos en esta tabla. – cdonner

+0

todavía, +1 por la sugerencia de cambiar mi esquema. – cdonner

10

Puede utilizar la función ISNUMERIC agregando un método a una clase parcial para el DataContext. Sería similar a usar un UDF.

En su clase parcial de DataContext añadir lo siguiente:

partial class MyDataContext 
{ 
    [Function(Name = "ISNUMERIC", IsComposable = true)] 
    public int IsNumeric(string input) 
    { 
     throw new NotImplementedException(); // this won't get called 
    } 
} 

A continuación, el código podría usarlo de esta manera:

var query = dc.TableName 
       .Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") }) 
       .Where(p => SqlMethods.Like(p.Col, "EU%") 
         && dc.IsNumeric(p.ReplacedText) == 1) 
       .OrderByDescending(p => p.ReplacedText) 
       .First() 
       .Col; 

Console.WriteLine(query); 

O usted podría hacer uso MAX:

var query = dc.TableName 
       .Select(p => new { p.Col, ReplacedText = p.Col.Replace("EU", "") }) 
       .Where(p => SqlMethods.Like(p.Col, "EU%") 
        && dc.IsNumeric(p.ReplacedText) == 1); 

var result = query.Where(p => p.ReplacedText == query.Max(p => p.ReplacedText)) 
        .First() 
        .Col; 

Console.WriteLine("Max: {0}, Result: {1}", max, result); 

Dependiendo de su objetivo final, es posible detenerse en la variable max y anteponerla al " EU "texto para evitar la segunda consulta que recibe el nombre de la columna.

EDIT: como se menciona en los comentarios, el inconveniente de este enfoque es que el pedido se realiza en texto en lugar de valores numéricos y no hay actualmente ninguna traducción de Int32.Parse en SQL.

+0

+1. Estoy bastante sorprendido de que esto sea posible. No olvides que ordenar * OrderByDescending (p => p.ReplacedText) * está basado en texto (ReplacedText es una cadena/varchar), por lo que es posible que esto no arroje el resultado correcto. Tiene que ser lanzado a un número entero antes de la clasificación. – Steven

+0

@Steven gracias, es una solución ordenada para funciones simples. Tienes razón sobre el orden. Lamentablemente, no hay ninguna solución para eso y 'Int32.Parse' no tiene traducción a SQL, por lo que para la clasificación numérica es mejor utilizar una UDF/SPROC real. –

+0

Lo mismo aquí, esta es una de las cosas más resbaladizas que he visto en mucho tiempo. – cdonner

0

Mi sugerencia es volver al SQL en línea y usar el método DataContext.ExecuteQuery(). Utilizaría la consulta SQL que publicó al principio.

Esto es lo que he hecho en situaciones similares. No es ideal, se otorga, debido a la falta de verificación de tipo y los posibles errores de sintaxis, sino que simplemente se asegura de que esté incluido en las pruebas unitarias. No todas las consultas posibles están cubiertas por la sintaxis de Linq, de ahí la existencia de ExecuteQuery en primer lugar.