2009-05-22 14 views
34

Potrzebuję kompilatora w języku Java, który ma tę samą semantykę, co operator "podobny do sql". Na przykład:Jak zaimplementować SQL, taki jak operator "LIKE" w java?

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

powinien ocenić wartość true, a

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

należy oceniać na false. Jakieś pomysły, jak wdrożyć taki komparator lub czy ktoś zna implementację z tą samą semantyką? Czy można to zrobić za pomocą wyrażenia regularnego?

Odpowiedz

30

. * Będzie pasować do żadnych znaków w wyrażeniach regularnych

myślę składnia java byłoby

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

I za pojedynczy mecz charakter wystarczy użyć jeden punkt.

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

I dopasować rzeczywistą kropkę, uciec jak slash kropką

\. 
+0

tak, dzięki! Ale na wypadek, gdyby słowo nie było takie proste jak "% dig%", a ciąg wymaga ucieczki? Czy coś już się exsiting? A co z '?' ? – Chris

+0

Edytowałem swoją odpowiedź dla operatora znaku zapytania. Trochę jestem jednak zdezorientowany przez resztę twojego komentarza. Czy mówisz, że ciąg przychodzi do ciebie w składni sql i chcesz go ocenić tak jak jest? Jeśli tak jest, myślę, że będziesz musiał ręcznie zastąpić składnię sql. – Bob

+0

co się stanie, jeśli ciąg znaków, który jest używany jako wzorzec wyszukiwania, zawiera znaki grupujące, takie jak "(" lub ")"? w jaki sposób inne postacie potrzebują ucieczki? – Chris

2

ciągi Java mieć .startsWith() i .contains() metod, które będzie Ci najbardziej z drogi. Dla czegoś bardziej skomplikowanego trzeba użyć regex lub napisać własną metodę.

2

Można włączyć '%string%' do contains(), 'string%' do startsWith() i '%string"' do endsWith().

Powinieneś również uruchomić toLowerCase() zarówno na łańcuchu, jak i wzorze, ponieważ LIKE jest niewidoczny.

Nie jestem pewien, jak poradzisz sobie z '%string%other%', ale z wyjątkiem Wyrażenia regularnego.

Jeśli używasz wyrażeń regularnych:

+0

co to jest "% this% string%"? podzielić na znak "%", iterować po tablicy i sprawdzać przy każdym wpisie? myślę, że można to zrobić lepiej ... – Chris

18

Tak, można to zrobić za pomocą wyrażenia regularnego. Należy pamiętać, że wyrażenia regularne języka Java mają inną składnię niż "podobny" język SQL. Zamiast "%", masz ".*", a zamiast "?", masz ".".

To, co czyni nieco trudnym, jest to, że musiałbyś uciec od znaków, które Java traktuje jako wyjątkowe. Ponieważ próbujesz zrobić to analogicznie do SQL, domyślam się, że ^$[]{}\ nie powinien pojawić się w ciągu regex. Ale musisz zastąpić "." "\\." przed wykonaniem jakichkolwiek innych zamienników. (Edit:Pattern.quote(String) ucieka wszystko przez otaczający łańcuch z „\Q” i „\E”, co spowoduje, że wszystko w wyrażeniu być traktowany jako literał (bez symboli wieloznacznych w ogóle) Więc zdecydowanie nie chcą. używać tego.)

Ponadto, jak mówi Dave Webb, należy również zignorować przypadek.

Mając to na uwadze, oto próbka tego, co to może wyglądać:

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

istnieje tam metoda, która ucieka od każdej cechy o specjalnym znaczeniu w regex java? – Chris

+1

Tak, Pattern.quote (http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#quote%28java.lang.String%29) zrobi to. Z jakiegoś powodu myślałem, że to może spowodować problem, ale teraz nie wiem, dlaczego nie uwzględniłem go w odpowiedzi. –

+0

O tak, teraz pamiętam. To dlatego, że ? jest specjalną postacią do wyraŜenia regularnego, więc uciekłby zanim moglibyśmy ją zastąpić. Przypuszczam, że moglibyśmy zamiast tego użyć Pattern.quote, a następnie expr = expr.replace ("\\?", "."); –

1

nie wiem dokładnie o chciwym problem, ale spróbuj tego, czy działa dla Ciebie:

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

Twój wewnętrzny podział() i pętla zastępują wszelkie \? sekwencja z kropką - nie rozumiem tego. Po co wyróżniać tę sekwencję, aby zastąpić ją kropką, tak jak samotny znak zapytania? –

+0

zastępuje znak "?" z "." bo '?' jest właścicielem miejsca dla pojedynczego dowolnego znaku. wiem '\\\\\\?' wygląda dziwnie, ale testowałem to i na moje testy wydaje się działać. – tommyL

12

Wyrażenia regularne są najbardziej uniwersalne. Jednak niektóre funkcje LIKE mogą być tworzone bez wyrażeń regularnych. na przykład

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

Każdy SQL Reference znajdę mówi „każdy pojedynczy znak” wieloznaczny jest podkreślenia (_), a nie znak zapytania (?). To trochę upraszcza, ponieważ podkreślenie nie jest metaznakiem regex. Jednak nadal nie możesz użyć Pattern.quote() z powodów podanych przez mmyers. Mam tutaj inną metodę dla uniknięcia wyrażeń regularnych, kiedy będę chciał je później edytować. Z tym z drogi, metoda like() staje się dość prosta:

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

Jeśli naprawdę chcesz używać ? dla zamiennika, najlepiej byłoby, aby usunąć go z listy metaznakami w metodzie quotemeta(). Zastąpienie jej formularza Escaped - replace("\\?", ".") - nie byłoby bezpieczne, ponieważ w oryginalnym wyrażeniu mogą występować ukośniki odwrotne.

A to prowadzi nas do rzeczywistych problemów: większość SQL smaki wydają się potwierdzać klas postaci w formach [a-z] i [^j-m] lub [!j-m], a wszystkie one stanowić drogę ucieczki symbole wieloznaczne. Ta ostatnia jest zwykle wykonywana za pomocą słowa kluczowego ESCAPE, która pozwala za każdym razem zdefiniować inną postać ucieczki. Jak możesz sobie wyobrazić, to trochę komplikuje sprawę. Konwertowanie do regexa jest prawdopodobnie najlepszą opcją, ale przeanalizowanie oryginalnego wyrażenia będzie znacznie trudniejsze - w rzeczywistości pierwszą rzeczą, którą musisz zrobić, to sformalizowanie składni samych wyrażeń podobnych do LIKE.

+0

tak, masz rację. lubię twoje rozwiązanie lepiej niż moje. – tommyL

+0

if (s == null) throw new IllegalArgumentException ("String nie może być pusty"); else if (s.isEmpty()) return ""; – Leo

2

Apache Cayanne ORM ma "In memory evaluation"

To nie może pracować dla niezmapowanego obiektu, ale wygląda obiecująco:

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

czy wiesz, że hibernacja obsługuje tę funkcję? mam na myśli, aby filtrować obiekty znajdujące się obecnie w pamięci przy użyciu takiego wyrażenia? – tommyL

1

W Comparator i Comparable interfejsy mogą tu zastosowania. Zajmują się sortowaniem i zwracają liczby całkowite któregokolwiek z tych znaków lub 0. Twoja operacja polega na znajdowaniu dopasowań i zwracaniu wartości true/false. To jest inne.

+0

zapraszamy do zasugerowania lepszej nazwy dla operatora. Nie lubię krytyków bez sugestii dotyczących ulepszeń, przy okazji. – 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; 
    } 
} 

może pomóc

0

I rozwiązać ten problem przy użyciu Java 8, w następnym kodzie poniżej

public List<String> search(String value) { 

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

Aby wdrożyć jak funkcje SQL w Javie nie trzeba wyrażenie regularne w Można je uzyskać jako:

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

Jest to w zasadzie powtórzeniem [istniejących odpowiedzi opublikowanych wiele lat temu] (https://stackoverflow.com/a/1149905). – Pang

Powiązane problemy