Emulating SQL LIKE in JavaScript

2019-01-07 13:51发布

问题:

How can I emulate the SQL keyword LIKE in JavaScript?

For those of you who don't know what LIKE is, it's a very simple regex which only supports the wildcards %, which matches 0 or more characters, and _ which matches exactly one character.

However, it's not just possible to do something like:

var match = new RegEx(likeExpr.replace("%", ".*").replace("_", ".")).exec(str) != null;

...because the pattern might contain dots, stars and any other special regex characters.

回答1:

What you have will work as long as you first escape the regex characters in your pattern. Below is one example from Simon Willison’s blog:

RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}

You could then implement your code as:

likeExpr = RegExp.escape(likeExpr);
var match = new RegEx(likeExpr.replace("%", ".*").replace("_", ".")).exec(str) != null;


回答2:

I was looking for an answer the same question and came up with this after reading Kip's reply:

String.prototype.like = function(search) {
    if (typeof search !== 'string' || this === null) {return false; }
    // Remove special chars
    search = search.replace(new RegExp("([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-])", "g"), "\\$1");
    // Replace % and _ with equivalent regex
    search = search.replace(/%/g, '.*').replace(/_/g, '.');
    // Check matches
    return RegExp('^' + search + '$', 'gi').test(this);
}

You can then use it as follows (note that it ignores UPPER/lower case):

var url = 'http://www.mydomain.com/page1.aspx';
console.log(url.like('%mydomain.com/page_.asp%')); // true

NOTE 29/11/2013: Updated with RegExp.test() performance improvement as per Lucios comment below.



回答3:

Here's a function I use, based on PHP's preg_quote function:

function regex_quote(str) {
  return str.replace(new RegExp("([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-])", "g"), "\\$1");
}

So your line would now be:

var match = new RegEx(regex_quote(likeExpr).replace("%", ".*").replace("_", ".")).exec(str) != null;


回答4:

If you want to use a regex, you can wrap each character of the string in square-brackets. Then you only have a few characters to escape.

But a better option might be to truncate the target strings so the length matches your search string and check for equality.



回答5:

An old question but there are actually no good answers here. TSQL LIKE expressions can contain square-bracket escaped sections that are already almost valid regular expressions and allow for matching % and _. E.g.:

'75%' LIKE '75[%]'
'[foo]' LIKE '[[]foo]' -- ugh

Here's my function to convert a LIKE expression into a RegExp. The input is split into square-bracket and non-square-bracket sections. The square-bracket sections just need backslash escaping and the non-square-bracket sections are fully escaped while the % and _ directives are converted to regular expressions.

const likeRegExp = (expression, caseSensitive = false) =>
    new RegExp(`^${
        expression.split(/(\[.+?\])/g)
        .map((s, i) => i % 2 ?
            s.replace(/\\/g, '\\\\') :
            s.replace(/[-\/\\^$*+?.()|[\]{}%_]/g, m => {
                switch(m) {
                    case '%': return '.*';
                    case '_': return '.';
                    default: return `\\${m}`;
                }
            })
        ).join('')
    }$`, caseSensitive ? '' : 'i');


回答6:

In Chris Van Opstal's answer you should use replaceAll instead of replace to replace all occurrances of '%' and '_'. Reference to how to do replaceAll - here



回答7:

Johnny come lately here but this works for me I use it for my spa pages to avoid certain pages showing results after the default page:

function like(haystack,needle){
    needle = needle.split(','); 
    var str = haystack.toLowerCase();
    var n = -1;
    for(var i=0;i<needle.length;i++){
        n = str.search(needle[i]);
        if(n > -1){
            return n;
        }
    }
return n;
}

usage is - here I want to not show any results on the tools,contact or home pages - results() is a function I do not show here:

var n = like($data,'tools,contact,home');
//~ alert(n);
if(n < 0){// does not match anything in the above string
  results($data);
}