web-dev-qa-db-fra.com

Comment implémenter un SQL tel que l'opérateur 'LIKE' en Java?

J'ai besoin d'un comparateur en Java qui a la même sémantique que l'opérateur sql 'like' . Par exemple:

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

devrait évaluer à vrai, et

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

devrait évaluer à faux. Avez-vous des idées sur la manière de mettre en œuvre un tel comparateur ou connaissez-vous une mise en œuvre avec la même sémantique? Cela peut-il être fait en utilisant une expression régulière?

35
Chris

. * correspondra à tous les caractères des expressions régulières

Je pense que la syntaxe Java serait

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

Et pour la correspondance de caractère unique, utilisez un seul point.

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

Et pour faire correspondre un point réel, échappez-le en tant que point oblique

\.
31
Bob

Oui, cela pourrait être fait avec une expression régulière. N'oubliez pas que les expressions régulières de Java ont une syntaxe différente de celle de SQL "like". Au lieu de "%", vous auriez ".*" et au lieu de "?", vous auriez ".".

Ce qui rend la tâche difficile est que vous devez également échapper à tous les caractères que Java considère comme spéciaux. Puisque vous essayez de rendre cela analogue à SQL, je suppose que ^$[]{}\ ne devrait pas apparaître dans la chaîne regex. Mais vous devrez remplacer "." par "\\." avant de procéder à tout autre remplacement. (Edit:Pattern.quote(String) échappe à tout en entourant la chaîne de caractères avec "\Q" et "\E", ce qui entraînera le traitement de tous les éléments de l'expression comme des littéraux (aucun caractère générique).définitivement ne veut pas l'utiliser.)

En outre, comme le dit Dave Webb, vous devez également ignorer la casse.

Dans cet esprit, voici un exemple de ce à quoi cela pourrait ressembler:

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);
}
20
Michael Myers

Les expressions régulières sont les plus polyvalentes. Cependant, certaines fonctions LIKE peuvent être formées sans expressions régulières. par exemple.

String text = "digital";
text.startsWith("Dig"); // like "Dig%"
text.endsWith("tal"); // like "%tal"
text.contains("gita"); // like "%gita%"
16
Peter Lawrey

Chaque référence SQL que je peux trouver indique que le caractère générique "tout caractère unique" est le trait de soulignement (_), pas le point d'interrogation (?). Cela simplifie un peu les choses, car le trait de soulignement n'est pas un métacaractère regex. Cependant, vous ne pouvez toujours pas utiliser Pattern.quote() pour la raison donnée par mmyers. J'ai une autre méthode ici pour échapper aux expressions rationnelles lorsque je pourrais vouloir les éditer par la suite. Une fois cela fait, la méthode like() devient assez 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 vous voulez vraiment utiliser ? pour le caractère générique, le mieux est de le supprimer de la liste des métacaractères de la méthode quotemeta(). Remplacer son formulaire échappé - replace("\\?", ".") - ne serait pas sûr, car il pourrait y avoir des barres obliques inverses dans l'expression d'origine.

Et cela nous amène aux véritables problèmes: la plupart des versions de SQL semblent prendre en charge les classes de caractères dans les formes [a-z] et [^j-m] ou [!j-m], et elles fournissent toutes un moyen d'échapper aux caractères génériques. Ce dernier est généralement effectué à l'aide d'un mot clé ESCAPE, qui vous permet de définir un caractère d'échappement différent à chaque fois. Comme vous pouvez l’imaginer, cela complique un peu les choses. La conversion en une expression régulière est probablement toujours la meilleure option, mais l'analyse de l'expression originale sera beaucoup plus difficile. En fait, la première chose à faire est de formaliser la syntaxe des expressions de type LIKE- elles-mêmes.

11
Alan Moore

Pour implémenter les fonctions LIKE de SQL en Java, vous n'avez pas besoin d'expression régulière dans Vous pouvez les obtenir de la manière suivante:

String text = "Apple";
text.startsWith("app"); // like "app%"
text.endsWith("le"); // like "%le"
text.contains("ppl"); // like "%ppl%"
4
Mithun Adhikari

Vous pouvez transformer '%string%' en contains() , 'string%' en startsWith() et '%string"' en endsWith() .

Vous devez également exécuter toLowerCase() à la fois sur la chaîne et sur le modèle, car LIKE est insensible à la casse.

Vous ne savez pas comment gérer '%string%other%' sauf avec une expression régulière.

Si vous utilisez des expressions régulières:

3
Dave Webb
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;
    }
}

peut être vous aider

2
Krishnendu

Les chaînes de caractères Java ont des méthodes .startsWith () et .contains () qui vous mèneront presque jusqu'au bout. Pour tout ce qui est plus compliqué, vous devrez utiliser regex ou écrire votre propre méthode.

2
job

http://josql.sourceforge.net/ a ce dont vous avez besoin. Recherchez org.josql.expressions.LikeExpression.

2
Rich MacDonald

Apache Cayanne ORM a un " évaluation de la mémoire

Cela peut ne pas fonctionner pour un objet non mappé, mais semble prometteur:

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

je ne sais pas exactement à propos de la question gourmande, mais essayez ceci si cela fonctionne pour vous:

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);
}
1
tommyL

Les interfaces Comparator et Comparable sont probablement inapplicables ici. Ils traitent du tri et renvoient des entiers de l'un ou l'autre signe ou 0. Votre opération consiste à rechercher des correspondances et à renvoyer vrai/faux. C'est différent.

1
John O
public static boolean like(String source, String exp) {
        if (source == null || exp == null) {
            return false;
        }

        int sourceLength = source.length();
        int expLength = exp.length();

        if (sourceLength == 0 || expLength == 0) {
            return false;
        }

        boolean fuzzy = false;
        char lastCharOfExp = 0;
        int positionOfSource = 0;

        for (int i = 0; i < expLength; i++) {
            char ch = exp.charAt(i);

            // 是否转义
            boolean escape = false;
            if (lastCharOfExp == '\\') {
                if (ch == '%' || ch == '_') {
                    escape = true;
                    // System.out.println("escape " + ch);
                }
            }

            if (!escape && ch == '%') {
                fuzzy = true;
            } else if (!escape && ch == '_') {
                if (positionOfSource >= sourceLength) {
                    return false;
                }

                positionOfSource++;// <<<----- 往后加1
            } else if (ch != '\\') {// 其他字符,但是排查了转义字符
                if (positionOfSource >= sourceLength) {// 已经超过了source的长度了
                    return false;
                }

                if (lastCharOfExp == '%') { // 上一个字符是%,要特别对待
                    int tp = source.indexOf(ch);
                    // System.out.println("上一个字符=%,当前字符是=" + ch + ",position=" + position + ",tp=" + tp);

                    if (tp == -1) { // 匹配不到这个字符,直接退出
                        return false;
                    }

                    if (tp >= positionOfSource) {
                        positionOfSource = tp + 1;// <<<----- 往下继续

                        if (i == expLength - 1 && positionOfSource < sourceLength) { // exp已经是最后一个字符了,此刻检查source是不是最后一个字符
                            return false;
                        }
                    } else {
                        return false;
                    }
                } else if (source.charAt(positionOfSource) == ch) {// 在这个位置找到了ch字符
                    positionOfSource++;
                } else {
                    return false;
                }
            }

            lastCharOfExp = ch;// <<<----- 赋值
            // System.out.println("当前字符是=" + ch + ",position=" + position);
        }

        // expr的字符循环完了,如果不是模糊的,看在source里匹配的位置是否到达了source的末尾
        if (!fuzzy && positionOfSource < sourceLength) {
            // System.out.println("上一个字符=" + lastChar + ",position=" + position );

            return false;
        }

        return true;// 这里返回true
    }
Assert.assertEquals(true, like("abc_d", "abc\\_d"));
        Assert.assertEquals(true, like("abc%d", "abc\\%%d"));
        Assert.assertEquals(false, like("abcd", "abc\\_d"));

        String source = "1abcd";
        Assert.assertEquals(true, like(source, "_%d"));
        Assert.assertEquals(false, like(source, "%%a"));
        Assert.assertEquals(false, like(source, "1"));
        Assert.assertEquals(true, like(source, "%d"));
        Assert.assertEquals(true, like(source, "%%%%"));
        Assert.assertEquals(true, like(source, "1%_"));
        Assert.assertEquals(false, like(source, "1%_2"));
        Assert.assertEquals(false, like(source, "1abcdef"));
        Assert.assertEquals(true, like(source, "1abcd"));
        Assert.assertEquals(false, like(source, "1abcde"));

        // 下面几个case很有代表性
        Assert.assertEquals(true, like(source, "_%_"));
        Assert.assertEquals(true, like(source, "_%____"));
        Assert.assertEquals(true, like(source, "_____"));// 5个
        Assert.assertEquals(false, like(source, "___"));// 3个
        Assert.assertEquals(false, like(source, "__%____"));// 6个
        Assert.assertEquals(false, like(source, "1"));

        Assert.assertEquals(false, like(source, "a_%b"));
        Assert.assertEquals(true, like(source, "1%"));
        Assert.assertEquals(false, like(source, "d%"));
        Assert.assertEquals(true, like(source, "_%"));
        Assert.assertEquals(true, like(source, "_abc%"));
        Assert.assertEquals(true, like(source, "%d"));
        Assert.assertEquals(true, like(source, "%abc%"));
        Assert.assertEquals(false, like(source, "ab_%"));

        Assert.assertEquals(true, like(source, "1ab__"));
        Assert.assertEquals(true, like(source, "1ab__%"));
        Assert.assertEquals(false, like(source, "1ab___"));
        Assert.assertEquals(true, like(source, "%"));

        Assert.assertEquals(false, like(null, "1ab___"));
        Assert.assertEquals(false, like(source, null));
        Assert.assertEquals(false, like(source, ""));
0
test love