2009-05-22 48 views
34

Necesito un comparador en Java que tenga la misma semántica que el operador sql 'like'. Por ejemplo:¿Cómo implementar un SQL como el operador 'LIKE' en java?

myComparator.like("digital","%ital%"); 
myComparator.like("digital","%gi?a%"); 
myComparator.like("digital","digi%"); 

debe evaluar como true y

myComparator.like("digital","%cam%"); 
myComparator.like("digital","tal%"); 

debe evaluar a falso. ¿Alguna idea de cómo implementar dicho comparador o alguien sabe una implementación con la misma semántica? ¿Se puede hacer esto usando una expresión regular?

Respuesta

30

. * Coincidirá con cualquier carácter en las expresiones regulares

Creo que la sintaxis de Java sería

"digital".matches(".*ital.*"); 

Y para el partido de carácter individual sólo tiene que utilizar un solo punto.

"digital".matches(".*gi.a.*"); 

Y para que coincida con un punto real, escapar de ella como punto barra

\. 
+0

sí, gracias! Pero en caso de que la palabra no sea tan simple como "% dig%" y la cadena necesita algo de escpeo? ¿Hay algo que ya exista? Qué pasa con la '?' ? – Chris

+0

Edité mi respuesta para el operador del signo de interrogación. Aunque estoy un poco confundido por el resto de tu comentario. ¿Estás diciendo que la cadena viene hacia ti en sintaxis SQL y quieres evaluarla tal como está? Si ese es el caso, creo que deberá reemplazar manualmente la sintaxis sql. – Bob

+0

¿Qué sucede si la cadena que se utiliza como patrón de búsqueda contiene caracteres de agrupación como '(' o ')' también para escapar? ¿Cómo pueden escapar otros personajes? – Chris

2

cadenas de Java tienen .startsWith() y .contains() métodos que les permite conocer la mayor parte del camino. Para algo más complicado, tendrás que usar expresiones regulares o escribir tu propio método.

2

Se podría convertir a '%string%'contains(), 'string%' a startsWith() y '%string"' a endsWith().

También debe ejecutar toLowerCase() tanto en la cadena como en el patrón ya que LIKE es insensible a las mayúsculas y minúsculas.

No estoy seguro de cómo manejaría '%string%other%' excepto con una expresión regular.

Si está usando expresiones regulares:

+0

what abot "% this% string%"? dividir en el signo '%', iterar sobre la matriz y que comprobar cada entrada? Creo que esto podría hacerse mejor ... – Chris

18

Sí, esto podría hacerse con una expresión regular. Tenga en cuenta que las expresiones regulares de Java tienen una sintaxis diferente de "me gusta" de SQL. En lugar de "%", tendría ".*", y en lugar de "?", tendría ".".

Lo que lo hace un poco complicado es que también tendría que escapar de los caracteres que Java trata como especiales. Como intenta hacer esto análogo a SQL, supongo que ^$[]{}\ no debería aparecer en la cadena de expresiones regulares. Pero deberá reemplazar "." por "\\." antes de hacer cualquier otro reemplazo. (Editar:Pattern.quote(String) escapa a todo lo que rodea por la cadena con "\Q" y "\E", lo que hará que todo en la expresión de ser tratado como un literal (no hay comodines en absoluto) Así que definitivamente no desea. para usarlo)

Además, como dice Dave Webb, también debe ignorar el caso.

Con esto en mente, he aquí una muestra de lo que podría ser:

public static boolean like(String str, String expr) { 
    expr = expr.toLowerCase(); // ignoring locale for now 
    expr = expr.replace(".", "\\."); // "\\" is escaped to "\" (thanks, Alan M) 
    // ... escape any other potentially problematic characters here 
    expr = expr.replace("?", "."); 
    expr = expr.replace("%", ".*"); 
    str = str.toLowerCase(); 
    return str.matches(expr); 
} 
+0

existe allí un método, que escapa a cada charachter con un significado especial en java regex? – Chris

+1

Sí, Pattern.quote (http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#quote%28java.lang.String%29) lo hará. Por alguna razón, pensé que eso podría causar un problema, pero ahora no sé por qué no lo incluí en la respuesta. –

+0

Oh sí, ahora lo recuerdo. Eso es porque ? es un carácter especial de expresiones regulares, por lo que se escapó antes de que pudiéramos reemplazarlo. Supongo que podríamos usar Pattern.quote y luego expr = expr.replace ("\\?", "."); –

1

no sé exactamente sobre el tema codiciosos, pero intento esto si funciona para usted:

public boolean like(final String str, String expr) 
    { 
    final String[] parts = expr.split("%"); 
    final boolean traillingOp = expr.endsWith("%"); 
    expr = ""; 
    for (int i = 0, l = parts.length; i < l; ++i) 
    { 
     final String[] p = parts[i].split("\\\\\\?"); 
     if (p.length > 1) 
     { 
     for (int y = 0, l2 = p.length; y < l2; ++y) 
     { 
      expr += p[y]; 
      if (i + 1 < l2) expr += "."; 
     } 
     } 
     else 
     { 
     expr += parts[i]; 
     } 
     if (i + 1 < l) expr += "%"; 
    } 
    if (traillingOp) expr += "%"; 
    expr = expr.replace("?", "."); 
    expr = expr.replace("%", ".*"); 
    return str.matches(expr); 
} 
+0

Su división interna() y su loop reemplazan a cualquier \? secuencia con un punto - No entiendo eso. ¿Por qué seleccionar esa secuencia solo para reemplazarla por un punto como un único signo de interrogación? –

+0

reemplaza el '?' con un '.' porque '?' es un marcador de posición para un solo carácter arbitrario. Lo sé '\\\\\\?' parece extraño pero lo probé y para mis pruebas parece funcionar. – tommyL

12

Las expresiones regulares son las más versátiles. Sin embargo, algunas funciones LIKE se pueden formar sin expresiones regulares. p.ej.

String text = "digital"; 
text.startsWith("dig"); // like "dig%" 
text.endsWith("tal"); // like "%tal" 
text.contains("gita"); // like "%gita%" 
9

referencia Cada SQL dice que puedo encontrar el comodín "cualquier carácter individual" es el guión bajo (_), no es el signo de interrogación (?). Eso simplifica un poco las cosas, ya que el guión bajo no es un metacaracter regex. Sin embargo, todavía no puede usar Pattern.quote() por el motivo dado por mmyers. Aquí tengo otro método para escapar de las expresiones regulares cuando me gustaría editarlas después. Con eso fuera del camino, el método like() vuelve bastante simple:

public static boolean like(final String str, final String expr) 
{ 
    String regex = quotemeta(expr); 
    regex = regex.replace("_", ".").replace("%", ".*?"); 
    Pattern p = Pattern.compile(regex, 
     Pattern.CASE_INSENSITIVE | Pattern.DOTALL); 
    return p.matcher(str).matches(); 
} 

public static String quotemeta(String s) 
{ 
    if (s == null) 
    { 
    throw new IllegalArgumentException("String cannot be null"); 
    } 

    int len = s.length(); 
    if (len == 0) 
    { 
    return ""; 
    } 

    StringBuilder sb = new StringBuilder(len * 2); 
    for (int i = 0; i < len; i++) 
    { 
    char c = s.charAt(i); 
    if ("[](){}.*+?$^|#\\".indexOf(c) != -1) 
    { 
     sb.append("\\"); 
    } 
    sb.append(c); 
    } 
    return sb.toString(); 
} 

Si realmente desea utilizar ? por el comodín, lo mejor sería eliminar de la lista de metacaracteres en el método quotemeta(). Reemplazar su forma de escape - replace("\\?", ".") - no sería seguro porque podría haber barras invertidas en la expresión original.

Y eso nos lleva a los problemas reales: la mayoría de sabores de SQL parecen apoyar las clases de personajes en las formas [a-z] y [^j-m] o [!j-m], y todos ellos proporcionan una manera de escapar caracteres comodín. Este último generalmente se realiza mediante una palabra clave ESCAPE, que le permite definir un carácter de escape diferente cada vez. Como se puede imaginar, esto complica bastante las cosas. La conversión a una expresión regular probablemente sea todavía la mejor opción, pero el análisis de la expresión original será mucho más difícil; de hecho, lo primero que tendría que hacer es formalizar la sintaxis de las expresiones similares a LIKE.

+0

sí, tienes razón. me gusta tu solución mejor que la mía. – tommyL

+0

if (s == null) throw new IllegalArgumentException ("La cadena no puede ser nula"); else if (s.isEmpty()) return ""; – Leo

2

Apache Cayanne ORM tiene un "In memory evaluation"

puede que no funcione para el objeto no asignado, pero parece prometedor:

Expression exp = ExpressionFactory.likeExp("artistName", "A%"); 
List startWithA = exp.filterObjects(artists); 
+0

¿sabes si la hibernación admite esta función? quiero decir, para filtrar objetos actualmente en memoria usando tal expresión? – tommyL

1

Los Comparator y Comparable interfaces son probablemente inaplicable aquí. Se ocupan de la clasificación y devuelven números enteros de cualquiera de los signos, o 0. Su operación consiste en encontrar coincidencias y devolver verdadero/falso. Eso es diferente.

+0

le invitamos a sugerir un nombre mejor para el operador. No me gustan los críticos sin sugerencias de mejoras, por cierto. – Chris

1
public static boolean like(String toBeCompare, String by){ 
    if(by != null){ 
     if(toBeCompare != null){ 
      if(by.startsWith("%") && by.endsWith("%")){ 
       int index = toBeCompare.toLowerCase().indexOf(by.replace("%", "").toLowerCase()); 
       if(index < 0){ 
        return false; 
       } else { 
        return true; 
       } 
      } else if(by.startsWith("%")){ 
       return toBeCompare.endsWith(by.replace("%", "")); 
      } else if(by.endsWith("%")){ 
       return toBeCompare.startsWith(by.replace("%", "")); 
      } else { 
       return toBeCompare.equals(by.replace("%", "")); 
      } 
     } else { 
      return false; 
     } 
    } else { 
     return false; 
    } 
} 

puede ser ayuda que

0

He resuelto este problema utilizando Java 8, en el siguiente código de abajo

public List<String> search(String value) { 

    return listaPersonal.stream() 
         .filter(p->(p.toUpperCase()).startsWith(value.toUpperCase())) 
         .collect(Collectors.toList()); 
} 
2

Para implementar COMO funciones de SQL en Java no es necesario expresión regular en Se pueden obtener como:

String text = "apple"; 
text.startsWith("app"); // like "app%" 
text.endsWith("le"); // like "%le" 
text.contains("ppl"); // like "%ppl%" 
+1

Esto es esencialmente solo una repetición de [estas respuestas existentes publicadas hace muchos años] (https://stackoverflow.com/a/1149905). – Pang

Cuestiones relacionadas