【问题标题】:Emulating SQL LIKE in JavaScript在 JavaScript 中模拟 SQL LIKE
【发布时间】:2010-11-21 19:00:37
【问题描述】:

如何在 JavaScript 中模拟 SQL 关键字 LIKE

对于那些不知道LIKE 是什么的人来说,这是一个非常简单的正则表达式,它只支持匹配0 个或多个字符的通配符% 和只匹配一个字符的_

但是,不仅仅是可以做这样的事情:

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

...因为该模式可能包含点、星号和任何其他特殊的正则表达式字符。

【问题讨论】:

  • fyi- 在您的示例代码中,您在“.exec”之前缺少一个右括号
  • @C.barlow:编辑时不要犹豫删除旧的“编辑:..等等”标记。以“最好的独立问题”为目标;编辑历史可供所有人查看,因此无需保留评论。 :)
  • @sarnold 明白了!感谢您的指导!
  • 不能直接得到你想要的;但是,如果您只是为了匹配任何文本,最简单的方法是使用includes 语法:if(evalText.includes(searchText){ ... }

标签: javascript sql regex sql-like


【解决方案1】:

我最终根据这里的一些答案编写了一个对我来说效果很好的函数。我需要一些可以保留“startswith%”和“%endswith”语法的东西,并且不返回空搜索字符串的匹配项。

function sqlLIKE(target, likeExp) {
  let regex = likeExp
    .replaceAll(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
    .replaceAll("%", ".*")
    .replaceAll("_", ".");

  if (likeExp.charAt(0) !== '%' || !likeExp.includes('%')) regex = `^${regex}`;

  if (likeExp.charAt(likeExp.length - 1) !== '%' || !likeExp.includes('%')) regex = `${regex}$`;

  return new RegExp(regex).exec(target) !== null;
}

【讨论】:

    【解决方案2】:

    我需要这个,在 Safari 中转义和工作(没有负面的后视)。这是我想出的:

    /**
     * Quotes a string following the same rules as https://www.php.net/manual/function.preg-quote.php
     *
     * Sourced from https://locutus.io/php/preg_quote/
     *
     * @param {string} str String to quote.
     * @param {?string} [delimiter] Delimiter to also quote.
     * @returns {string} The quoted string.
     */
    function regexQuote(str, delimiter) {
        return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
    }
    
    /**
     * Removes the diacritical marks from a string.
     *
     * Diacritical marks: {@link https://unicode-table.com/blocks/combining-diacritical-marks/}
     *
     * @param {string} str The string from which to strip the diacritical marks.
     * @returns {string} Stripped string.
     */
    function stripDiacriticalMarks(str) {
        return unorm.nfkd(str).replaceAll(/[\u0300-\u036f]+/g, '');
    }
    
    /**
     * Checks if the string `haystack` is like `needle`, `needle` can contain '%' and '_'
     * characters which will behave as if used in a SQL LIKE condition. Character escaping
     * is supported with '\'.
     *
     * @param {string} haystack The string to check if it is like `needle`.
     * @param {string} needle The string used to check if `haystack` is like it.
     * @param {boolean} [ai] Whether to check likeness in an accent-insensitive manner.
     * @param {boolean} [ci] Whether to check likeness in a case-insensitive manner.
     * @returns {boolean} True if `haystack` is like `needle`, otherwise, false.
     */
    function strLike(haystack, needle, ai = true, ci = true) {
        if (ai) {
            haystack = stripDiacriticalMarks(haystack);
            needle = stripDiacriticalMarks(needle);
        }
    
        needle = regexQuote(needle, '/');
    
        let tokens = [];
    
        for (let i = 0; i < needle.length; ) {
            if (needle[i] === '\\') {
                i += 2;
                if (i < needle.length) {
                    if (needle[i] === '\\') {
                        tokens.push('\\\\');
                        i += 2;
                    } else {
                        tokens.push(needle[i]);
                        ++i;
                    }
                } else {
                    tokens.push('\\\\');
                }
            } else {
                switch (needle[i]) {
                    case '_':
                        tokens.push('.')
                        break;
                    case '%':
                        tokens.push('.*')
                        break;
                    default:
                        tokens.push(needle[i]);
                        break;
                }
                ++i;
            }
        }
    
        return new RegExp(`^${tokens.join('')}$`, `u${ci ? 'i' : ''}`).test(haystack);
    }
    
    /**
     * Escapes a string in a way that `strLike` will match it as-is, thus '%' and '_'
     * would match a literal '%' and '_' respectively (and not behave as in a SQL LIKE
     * condition).
     *
     * @param {string} str The string to escape.
     * @returns {string} The escaped string.
     */
    function escapeStrLike(str) {
        let tokens = [];
    
        for (let i = 0; i < str.length; i++) {
            switch (str[i]) {
                case '\\':
                    tokens.push('\\\\');
                    break;
                case '%':
                    tokens.push('\\%')
                    break;
                case '_':
                    tokens.push('\\_')
                    break;
                default:
                    tokens.push(str[i]);
            }
        }
    
        return tokens.join('');
    }
    

    上面的代码依赖于unorm,并且是 unicode 感知的,能够捕获如下情况:

    strLike('Hello ?', 'Hello _'); // true
    strLike('Hello ?', '_e%o__');  // true
    strLike('asdfas \\?H\\\\%?É\\l\\_?\\l\\o asdfasf', '%' . escapeStrLike('\\?h\\\\%?e\\l\\_?\\l\\o') . '%'); // true
    

    【讨论】:

      【解决方案3】:

      我想要的东西还可以处理使用\%\_ 转义通配符%_

      这是我使用否定后视的解决方案:

      // escapes RegExp special characters
      const escapePattern = s => s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
      
      // converts ILIKE pattern to a RegExp object
      const ilikeToRegExp = pattern =>
        new RegExp(
          `^${escapePattern(pattern)}$`
            // convert ILIKE wildcards, don't match escaped
            .replace(/(?<![\\])%/g, '.*')
            .replace(/(?<![\\])_/g, '.')
            // replace ILIKE escapes
            .replace(/\\%/g, '%')
            .replace(/\\_/g, '_'),
          'i'
        );
      

      用法:

      ilikeToRegExp('%eLlo WoR%').test('hello world')  
      // true
      
      ilikeToRegExp('ello wor').test('hello world')  
      // false
      
      ilikeToRegExp('%90\%%').test('...90%...') 
      // true
      

      【讨论】:

      • 如果你需要 LIKE,只需删除 'i' 标志
      【解决方案4】:

      一个老问题,但这里实际上没有好的答案。 TSQL LIKE 表达式可以包含方括号转义部分,这些部分已经是几乎有效的正则表达式,并允许匹配%_。例如:

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

      这是我将 LIKE 表达式转换为 RegExp 的函数。输入分为方括号和非方括号部分。方括号部分只需要反斜杠转义,非方括号部分完全转义,而%_ 指令转换为正则表达式。

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

      【讨论】:

      • 任何正在寻找从 sql LIKE 语法转换为 javascript RegExp 的解决方案的人,这是要走的路。它会吐出一个正则表达式,你可以在上面进行 .test() 等操作。
      【解决方案5】:

      约翰尼最近才来这里,但这对我有用,我将它用于我的 spa 页面以避免某些页面在默认页面之后显示结果:

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

      用法是 - 在这里我不想在工具、联系人或主页上显示任何结果 - results() 是我不在这里显示的函数:

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

      【讨论】:

        【解决方案6】:

        在 Chris Van Opstal 的回答中,您应该使用 replaceAll 而不是 replace 替换所有出现的“%”和“_”。 参考replaceAll怎么做-here

        【讨论】:

          【解决方案7】:

          我正在寻找相同问题的答案,并在阅读了 Kip 的回复后想出了这个:

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

          然后您可以按如下方式使用它(注意它忽略了大写/小写):

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

          注意 29/11/2013:根据下面的 Lucios 评论更新了 RegExp.test() 性能改进。

          【讨论】:

          • 而不是 !!this.match(...,您不应该使用 this.test(... 吗?
          • 嗨@LucioM.Tato,x.test() 方法适用于RegExp 实例,不适用于String 对象。http://www.w3schools.com/jsref/jsref_regexp_test.asp
          • 对不起,我不清楚。你得到一个匹配数组(昂贵的正则表达式)而不是使用 .test (更好的性能)。应该是:return new RegExp(...).test(this);而不是通过双重否定将数组或 null 转换为布尔值。stackoverflow.com/questions/10940137/…
          • +1 点赞。我已将您的解决方案与我的更改一起使用( return RegExp('^' + search + '$', 'gi').test(this);
          • 很好的解决方案。代码更干净也更快。我已根据您的建议进行了更新。
          【解决方案8】:

          只要您首先转义模式中的正则表达式字符,您所拥有的就会起作用。以下是来自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');
          }
          

          然后您可以将代码实现为:

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

          【讨论】:

          • 假设您已经拥有 - RegExp.like = function (text) { return new RegExp("^"+(RegExp.escape(text).replace(/%/g, ".*").replace(/_/g, "."))+"$"); } 可能对他来说更可重用。
          • Regex.escape 的实现有点矫枉过正。首先,arguments.callee 的出现阻止了现代浏览器中的一些优化(并且在 ES5-strict 中已弃用),因此最好尽可能避免它。其次,有一个不必要的字符转义,而是可以将它们放在字符类中。这是我们在 Prototype.js 中使用的更小(并且很可能更快)的版本 - RegExp.escape = function(str) { return str.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); };
          【解决方案9】:

          这是我使用的一个函数,基于PHP's preg_quote function

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

          所以你的行现在是:

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

          【讨论】:

            【解决方案10】:

            如果要使用正则表达式,可以将字符串的每个字符括在方括号中。那么你就只有几个字符可以转义了。

            但更好的选择可能是截断目标字符串,以便长度与您的搜索字符串匹配并检查是否相等。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-10-28
              • 1970-01-01
              • 2015-09-27
              • 2012-10-11
              • 1970-01-01
              • 2011-06-15
              • 1970-01-01
              相关资源
              最近更新 更多