Когда-то давным-давно я писал о том, как организовал примитивный парсинг для "умного" поиска по тегам. С тех пор много воды утекло. Во-первых - я переписал сайт с рельс на ноду, а во-вторых я научился-таки нормально общаться с СУБД. И так, вот как примерно сейчас на этом сайте происходит выборка по нескольким тегам:

    view: function(req, res) {
      var tags_plus = [];  // тут будут лежать теги, по которым ищем
      var tags_minus = []; // а здесь те - по которым исключаем

      var tmp1 = req.params.id.replace("'", "''"); // не самое хорошее решение для предотвращения инъекций
      tmp1 = tmp1.split('+');
      var tmp2 = [];

      var i, j;

      var s_tags_plus;
      var s_tags_minus;
      var where = [];

      for (i = 0; i < tmp1.length; i = i + 1) {
        tmp2 = tmp1[i].split('-');
        tags_plus.push(tmp2[0].toLowerCase());
        for (j = 1; j < tmp2.length; j = j + 1) {
          tags_minus.push(tmp2[j].toLowerCase());
        }
      }
      tags_plus  = tags_plus.uniq(); // убираем повторения, в идеале - регистронезависимо
      tags_minus = tags_minus.uniq(); // в массиве теперь каждый элемент уникален - дублей нет

      if (tags_plus.length > 0) {
        s_tags_plus = "'" + tags_plus.join("','") + "'";
        // ниже имеется два способа обработки знака сложения. Один из них надо будет повесить на символ "|"
        // where.push(' p.id_post in ( select v.id_post from v_tags_by_post v where lower(v.tag_name) in (' + s_tags_plus + '))');
        where.push( ' ( ' + (tags_plus.map(function(tname){return " exists ( select 1 from v_tags_by_post v where p.id_post=v.id_post and lower(v.tag_name)=lower('" + tname + "'))"}).join(' AND ')) + ' ) ' );
      }
      if (tags_minus.length > 0) {
        s_tags_minus = "'" + tags_minus.join("','") + "'";
        where.push(' p.id_post NOT in ( select v.id_post from v_tags_by_post v where lower(v.tag_name) in (' + s_tags_minus + '))');
      }
      db.query("select p.* from v_posts_by_blog p where " + where.join(' AND ') + " order by post_rate_damped desc, post_rate desc, post_created_at desc", function(err, rez) {
        if(rez.rows.length == 1) res.redirect('/'+rez.rows[0].blog_permalink+'/'+rez.rows[0].id_post); // если найден один-единственный пост - переходим сразу к нему
        else res.render('tags/view', {data: rez.rows});
      });
    }
Примерно, - потому что в действительности происходит разбиение по страницам, выборка из некоторых попутных таблиц сопутствующей информации. Да и вообще развиваюсь =)) добалвяю обработку символа "|" Но принцип именно такой - т.е. в отличие от ранее существовавшего "детского" варианта - тут я формирую необходимый запрос. Что из себя представляют используемые представления (view) - думаю понятно из их названий и без дополнительных пояснений. Полина Ростова