如何在java中实现类似'LIKE'运算符的SQL?

时间:2009-05-22 15:14:29

标签: java sql regex string wildcard

我需要一个java中的比较器,它与sql'like'运算符具有相同的语义。 例如:

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

应评估为true,

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

应评估为false。任何想法如何实现这样的比较器或任何人都知道具有相同语义的实现?可以使用正则表达式完成吗?

16 个答案:

答案 0 :(得分:32)

。*将匹配正则表达式中的任何字符

我认为java语法是

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

对于单个字符匹配,只需使用一个点。

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

要匹配实际的点,请将其作为斜线点

\.

答案 1 :(得分:21)

是的,这可以使用正则表达式完成。请记住,Java的正则表达式与SQL的“like”具有不同的语法。不是“%”,而是“.*”,而不是“?”,您将拥有“.”。

让它变得有点棘手的是你还必须逃避Java认为特殊的任何字符。由于您试图使其类似于SQL,我猜测^$[]{}\不应出现在正则表达式字符串中。但在进行任何其他替换之前,您必须将“.”替换为“\\.”。 (修改: 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);
}

答案 2 :(得分:17)

正则表达式是最通用的。但是,可以在没有正则表达式的情况下形成一些LIKE函数。 e.g。

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

答案 3 :(得分:11)

我能找到的每个SQL引用都说“任何单个字符”通配符是下划线(_),而不是问号(?)。这简化了一些事情,因为下划线不是正则表达式元字符。但是,由于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表达式的语法形式化。< / p>

答案 4 :(得分:4)

要在java中实现sql的LIKE函数,你不需要正则表达式 它们可以通过以下方式获得:

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

答案 5 :(得分:3)

您可以将'%string%'转为contains(),将'string%'转为startsWith(),将'%string"'转为endsWith()

您还应该对字符串和模式运行toLowerCase(),因为LIKE不区分大小写。

不知道如何使用正则表达式处理'%string%other%'

如果您使用正则表达式:

答案 6 :(得分:2)

Java字符串具有.startsWith()和.contains()方法,它们可以帮助您完成大部分工作。对于任何更复杂的事情,你必须使用正则表达式或编写自己的方法。

答案 7 :(得分:2)

Apache Cayanne ORM有一个“In memory evaluation

它可能不适用于未映射的对象,但看起来很有希望:

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

答案 8 :(得分:2)

http://josql.sourceforge.net/有你需要的东西。寻找org.josql.expressions.LikeExpression。

答案 9 :(得分:2)

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

可能会帮到你

答案 10 :(得分:1)

我不确切地知道这个贪婪的问题,但如果它适合你,请试试这个:

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

答案 11 :(得分:1)

ComparatorComparable接口可能不适用于此处。它们处理排序,并返回任一符号或0的整数。您的操作是关于查找匹配,并返回true / false。那是不同的。

答案 12 :(得分:0)

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, ""));

答案 13 :(得分:0)

签出https://github.com/hrakaroo/glob-library-java

这是Java中的零依赖库,用于执行glob(和类似sql的)类型的比较。对于大型数据集,它比转换为正则表达式要快。

基本语法

MatchingEngine m = GlobPattern.compile("dog%cat\%goat_", '%', '_', GlobPattern.HANDLE_ESCAPES);
if (m.matches(str)) { ... }

答案 14 :(得分:0)

这是我的看法,它在 Kotlin 中,但可以毫不费力地转换为 Java:

val percentageRegex = Regex("""(?<!\\)%""")
val underscoreRegex = Regex("""(?<!\\)_""")

infix fun String.like(predicate: String): Boolean {

    //Split the text by every % not preceded by a slash.
    //We transform each slice before joining them with .* as a separator.
    return predicate.split(percentageRegex).joinToString(".*") { percentageSlice ->

        //Split the text by every _ not preceded by a slash.
        //We transform each slice before joining them with . as a separator.
        percentageSlice.split(underscoreRegex).joinToString(".") { underscoreSlice ->

            //Each slice is wrapped in "Regex quotes" to ignore all
            // the metacharacters they contain.
            //We also remove the slashes from the escape sequences
            // since they are now treated literally.
            Pattern.quote(
                underscoreSlice.replace("\\_", "_").replace("\\%", "%")
            )
        }

    }.let { "^$it$" }.toRegex().matches(this@like)
}

它可能不是这里所有解决方案中性能最高的,但可能是最准确的。

它会忽略除 % 和 _ 之外的所有其他正则表达式元字符,并且还支持使用斜杠对它们进行转义。

答案 15 :(得分:-1)

好的,这是一个奇怪的解决方案,但我认为它仍然应该被提及。

我们可以利用任何数据库中已有的现有实现,而不是重新创建类似的机制!

(只有要求是,您的应用程序必须能够访问任何数据库)。

每次只运行一个非常简单的查询,根据类似比较的结果返回true或false。然后执行查询,直接从数据库中读取答案!

对于Oracle db:

SELECT
CASE 
     WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
     ELSE 'false'
 END test
FROM dual 

对于MS SQL Server

SELECT
CASE 
     WHEN 'StringToSearch' LIKE 'LikeSequence' THEN 'true'
     ELSE 'false'
END test

你所要做的就是替换&#34; StringToSearch&#34;和#34; LikeSequence&#34;使用绑定参数并设置要检查的值。