0

I have a LIKE query I'm running on a column of Android Uri which are formatted like such:

  1. content://com.android.externalstorage.documents/tree/0000-0000%3A_Issues/document/0000-0000%3A_Issues%2FParentB
  2. content://com.android.externalstorage.documents/tree/0000-0000%3A_Issues/document/0000-0000%3A_Issues%2FParentA
  3. content://com.android.externalstorage.documents/tree/0000-0000%3A_Issues/document/0000-0000%3A_Issues%2FParentA%2FParentB

These correspond to this file tree:

_Issues
|-ParentA
   |-ParentB
|-ParentB

I have attempted all sorts of manual queries escaping the wildcards, but I can't seem to get a proper match. I simplified it down to this for testing:

select name from meta where parent LIKE '%_Issues%2FParentB%';
  1. If I escape '%':

%_Issues\%2FParentB%'

I get no results at all.

  1. If I don't escape:

'%_Issues%2FParentB%'

I match both the shallow ParentB (desired) and the nested ParentB (undesired). I understand that's because I'm allowing anything between Issues and ParentB with the %.

What I don't understand is why query 1 does has no results. What am I missing?


Update

select name from meta where parent LIKE '%_Issues_2FParentB%';

worked, note the '_', so the '\' was clearly not escaping this db. Explicitly stating the escape character as @GordonLinoff suggested worked on my computer:

select name from meta where parent LIKE '%_Issues\%2FParentB%' escape '\';

Now to figure our how to accomplish the same in Android...

Anthony
  • 7,638
  • 3
  • 38
  • 71

2 Answers2

4

Yes. The simplest way is to use =, if that is appropriate.

Otherwise, the LIKE syntax includes an escape character. In your example, $ doesn't appear anywhere, so you could do:

where x like replace(y, '%', '$%') escape '$'

I believe that in all databases, the default is \, so you could also do:

where x like replace(y, '%', '\%') 

However, I prefer using the escape keyword so the intention is really clear.

Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • 1
    Should be: `where x like replace(y, '%', '\%')`, right? Match % not \. – Anthony Feb 10 '17 at 12:14
  • MySQL & PostgreSQL got a default, but at least in Oracle, SQL Server and Teradata there's no default escape character (compliant with Standard SQL). SQL is not a programming language and imho a default is stupid because it adds a 3rd character besides `_`and `%` with special meaning, you couldn't search for `\` without escaping it or changing the escape char.. – dnoeth Feb 10 '17 at 13:28
  • @dnoeth . . . I prefer the explicit form anyway, if I need to use `escape`. – Gordon Linoff Feb 10 '17 at 13:31
  • The escape was necessary in this case. Android didn't seem to have the default. I added the necessary Android code below. – Anthony Feb 10 '17 at 15:43
  • Great suggestion on using the explicit `escape`! That is definitely more readable, and also allows you to choose a character that is slightly more guaranteed to be unique and not be part of your specific data. – JakeMc Feb 15 '22 at 19:23
0

To expand on the answer by @GordonLinoff, here is specifically how I did it in Android since it's slightly more convoluted:

/***
 * Creates a LIKE selection statement with all of the given arguments
 * @param column column to select on
 * @param likes array of arguments to select on
 * @param selectionArgs out: formatted arguments to pass to query
 * @param joiner joiner between individual LIKE
 * @param NOT true to set NOT LIKE for all selection arguments
 * @param argStart set a wildcard before every selection argument
 * @param argEnd set a wildcard after every selection argument
 * @return selection statement to query
 */
public static String createLike(String column, String[] likes, List<String> selectionArgs,
                                String joiner, boolean NOT,
                                @Nullable String argStart, @Nullable String argEnd,
                                @Nullable String escapeChar)
{
    StringBuilder selection = new StringBuilder();
    for (int i = 0; i < likes.length; i++)
    {
        if (i > 0) selection.append(joiner);

        if (argStart == null)
            argStart = "";
        if (argEnd == null)
            argEnd = "";

        selection.append(column)
                .append(NOT ? " NOT LIKE ?" : " LIKE ?");

        if (escapeChar != null)
            selection.append(" ESCAPE '\\'");

        String argument = likes[i];
        if (escapeChar != null)
            argument = argument.replace(escapeChar, "\\" + escapeChar);
        argument = argStart + argument + argEnd;
        selectionArgs.add(argument);
    }

    return selection.toString();
}

and call with:

DbUtil.createLike(Meta.PARENT,
                    filter.hiddenFolders.toArray(new String[filter.hiddenFolders.size()]),
                    selectionArgs,
                    AND,    // Requires AND so multiple hides don't negate each other
                    true,   // NOT
                    null,   // No wild to start, matches path exactly
                    "%",    // Wildcard end to match all children
                    "%"));  // Uri contain '%' which means match any so escape them
Anthony
  • 7,638
  • 3
  • 38
  • 71