簡體   English   中英

如何在 java 中實現 SQL like 'LIKE' 運算符?

[英]How to implement a SQL like 'LIKE' operator in java?

我需要 java 中的比較器,它與 sql 的“like”運算符具有相同的語義。 例如:

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

應該評估為真,並且

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

應該評估為假。 任何想法如何實現這樣的比較器或者有人知道具有相同語義的實現嗎? 這可以使用正則表達式來完成嗎?

.* 將匹配正則表達式中的任何字符

我認為java語法是

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

對於單字符匹配,只需使用一個點。

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

並匹配一個實際的點,將其轉義為斜線點

\.

是的,這可以用正則表達式來完成。 請記住,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);
}

正則表達式是最通用的。 但是,可以在沒有正則表達式的情況下形成某些 LIKE 函數。 例如

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

我能找到的每個 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 風格似乎支持[az][^jm][!jm]形式的字符類,它們都提供了一種轉義通配符的方法。 后者通常通過ESCAPE關鍵字完成,它允許您每次定義不同的轉義字符。 可以想象,這使事情變得相當復雜。 轉換為正則表達式可能仍然是最好的選擇,但解析原始表達式會困難得多——事實上,你必須做的第一件事是形式化LIKE類表達式本身的語法。

要在java中實現sql的LIKE函數,您不需要在它們中使用正則表達式它們可以通過以下方式獲得:

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

Java 字符串具有 .startsWith() 和 .contains() 方法,它們可以幫助您完成大部分工作。 對於任何更復雜的事情,您必須使用正則表達式或編寫自己的方法。

您可以將'%string%'contains() ,將'string%'startsWith()並將'%string"'endsWith()

您還應該在字符串和模式上運行toLowerCase() ,因為LIKE不區分大小寫。

不知道你會如何處理'%string%other%'除了使用正則表達式。

如果您使用正則表達式:

Apache Cayanne ORM 有一個“內存評估

它可能不適用於未映射的對象,但看起來很有希望:

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

ComparatorComparable接口在這里可能不適用。 它們處理排序,並返回任一符號或 0 的整數。您的操作是關於查找匹配項,並返回真/假。 那不一樣。

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

可能會幫助你

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

我不完全知道貪婪的問題,但如果它適合你,試試這個:

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

我有一個類似的要求,這可能會有所幫助,經過一些修改,這是代碼:

package codeSamplesWithoutMaven;

公共 class TestLikeInJava {

public static void main(String[] args) {
    String fromDb = "erick@gmail.com";
    String str1 = "*gmail*";
    String str2 = "*erick";
    String str3 = "*rick";
    String str4 = "*.com";
    String str5 = "erick*";
    String str6 = "ck@gmail*";
    System.out.println(belongsToStringWithWildcards(str1, fromDb));
    System.out.println(belongsToStringWithWildcards(str2, fromDb));
    System.out.println(belongsToStringWithWildcards(str3, fromDb));
    System.out.println(belongsToStringWithWildcards(str4, fromDb));
    System.out.println(belongsToStringWithWildcards(str5, fromDb));
    System.out.println(belongsToStringWithWildcards(str6, fromDb));
}

private static Boolean belongsToStringWithWildcards(String strToTest, String targetStr) {
    Boolean result = Boolean.FALSE;
    int type = 0; //1:start, 2:end, 3:both
    if (strToTest.startsWith("*") && strToTest.endsWith("*")) {
        type = 3;
    } else {
        if (strToTest.startsWith("*")) {
            type = 1;
        } else if (strToTest.endsWith("*")) {
            type = 2;
        }
    }
    System.out.println("strToTest " + strToTest + " into " + targetStr + " type " + type);
    strToTest = strToTest.replaceAll("[*]", "");
    System.out.println("strToTest " + strToTest + " into " + targetStr + " type " + type);
    switch (type) {
        case 1: result = targetStr.endsWith(strToTest);  
                break;
        case 2: result = targetStr.startsWith(strToTest);
                break;
        case 3: result = targetStr.contains(strToTest);
                break;
    }
    return result;
}

}

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

查看https://github.com/hrakaroo/glob-library-java

它是 Java 中的一個零依賴庫,用於進行 glob(和類似 sql)類型的比較。 在大型數據集上,它比轉換為正則表達式要快。

基本語法

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

這是我對此的看法,它在 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)
}

它可能不是這里所有解決方案中性能最高的,但它可能是最准確的。

它會忽略除 % 和 _ 之外的所有其他 Regex 元字符,並且還支持用斜線對它們進行轉義。

來自https://www.tutorialspoint.com/java/java_string_matches.htm

import java.io.*;
public class Test {

   public static void main(String args[]) {
      String Str = new String("Welcome to Tutorialspoint.com");

      System.out.print("Return Value :" );
      System.out.println(Str.matches("(.*)Tutorials(.*)"));

      System.out.print("Return Value :" );
      System.out.println(Str.matches("Tutorials"));

      System.out.print("Return Value :" );
      System.out.println(Str.matches("Welcome(.*)"));
   }
}

好的,這是一個有點奇怪的解決方案,但我認為仍然應該提到它。

我們可以利用任何數據庫中已經可用的現有實現,而不是重新創建類似機制!

(唯一的要求是,您的應用程序必須能夠訪問任何數據庫)。

每次只運行一個非常簡單的查詢,根據類似比較的結果返回真或假。 然后執行查詢,直接從數據庫中讀取答案!

對於 Oracle 數據庫:

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

對於 MS SQL 服務器

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

您所要做的就是用綁定參數替換“StringToSearch”和“LikeSequence”並設置要檢查的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM