web-dev-qa-db-ja.com

Javaで「LIKE」演算子のようなSQLを実装する方法は?

私はJavaにsql 'like'演算子と同じセマンティクスを持つコンパレーターが必要です。例えば:

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

真に評価する必要があり、

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

falseと評価する必要があります。そのようなコンパレータを実装する方法はありますか、または同じセマンティクスを持つ実装を知っている人はいますか?これは正規表現を使用して実行できますか?

37
Chris

。*は正規表現の任意の文字と一致します

Java構文は

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

そして、単一の文字の一致には単一のドットを使用します。

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

実際のドットと一致させるには、スラッシュドットとしてエスケープします

\.
33
Bob

はい、これは正規表現で実行できます。 Javaの正規表現は、SQLの「いいね」とは異なる構文を持っていることに注意してください。 「_%_」の代わりに「_.*_」があり、「_?_」の代わりに「_._」があります。

多少トリッキーなのは、Javaが特別なものとして扱う文字もエスケープする必要があることです。これをSQLに類似させようとしているので、_^$[]{}\_は正規表現文字列には表示されませんが、他の置換を行う前に、「_._」を「_\\._」に置き換える必要があります。(Edit: Pattern.quote(String) は、文字列を「_\Q_」と「_\E_」で囲むことですべてをエスケープします。リテラルとして扱われる式では(ワイルドカードはまったく使用されません)。したがって、間違いなくは使用したくありません。)

さらに、Dave Webbが言うように、大文字と小文字を無視する必要もあります。

それを念頭に置いて、次のようなサンプルを次に示します。

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

正規表現は最も汎用性があります。ただし、一部のLIKE関数は、正規表現なしで形成できます。例えば.

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

私が見つけることができるすべてのSQLリファレンスは、「任意の1文字」ワイルドカードはアンダースコア(___)であり、疑問符(_?_)ではないことを示しています。アンダースコアは正規表現のメタ文字ではないため、これにより物事が少し簡単になります。ただし、mmyersによって指定された理由により、Pattern.quote()を使用することはできません。後で編集したいときに正規表現をエスケープする別の方法があります。これにより、like()メソッドは非常に簡単になります。

_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();
}
_

ワイルドカードに_?_を本当に使用したい場合は、quotemeta()メソッドのメタキャラクターのリストから削除するのが最善の策です。元の式にバックスラッシュが含まれている可能性があるため、エスケープされた形式-replace("\\?", ".")-を置き換えることは安全ではありません。

そして、それは本当の問題に私たちをもたらします:ほとんどのSQLフレーバーは、フォーム_[a-z]_および_[^j-m]_または_[!j-m]_の文字クラスをサポートするようであり、それらはすべてワイルドカード文字をエスケープする方法を提供します。後者は通常ESCAPEキーワードを使用して行われ、毎回異なるエスケープ文字を定義できます。ご想像のとおり、これは事態をかなり複雑にします。おそらく正規表現への変換は依然として最良のオプションですが、元の式の解析ははるかに困難になります。実際、最初にやらなければならないことは、LIKEのような式自体の構文を形式化することです。

11
Alan Moore

SqlのLIKE関数をJavaで実装するには、正規表現は必要ありません。次のように取得できます。

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

_'%string%'_を contains() 、_'string%'_を startsWith() および_'%string"'_に変えることができますto endsWith()

LIKEは大文字と小文字を区別しないため、文字列とパターンの両方で toLowerCase() も実行する必要があります。

ただし、正規表現を除いて_'%string%other%'_をどのように処理するかはわかりません。

正規表現を使用している場合:

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;
    }
}

あなたを助けるかもしれません

2
Krishnendu

Java文字列には.startsWith()メソッドと.contains()メソッドがあり、ほとんどの方法で取得できます。より複雑なものについては、正規表現を使用するか、独自のメソッドを記述する必要があります。

2
job

http://josql.sourceforge.net/ に必要なものがあります。 org.josql.expressions.LikeExpressionを探します。

2
Rich MacDonald

Apache Cayanne ORMには、「 メモリ評価 」があります

マップされていないオブジェクトでは機能しないかもしれませんが、有望に見えます:

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

私は貪欲な問題について正確に知りませんが、それがあなたのために働くならこれを試してください:

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

Comparator および Comparable インターフェイスは、ここでは適用できない可能性があります。これらはソートを処理し、符号または0の整数を返します。操作は一致を見つけ、true/falseを返すことです。それは違う。

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