1

In my application, I am using sequelize ORM. There are several entities: A Tool can have Tags and Categories.

Now I want to search for all Tools, that have a specific Tag, but I want to include all relating Tags of that tool (not just the specific one). If I now place a where statement into the include, only specified Tags are included into the result set (see [2]). I tried to limit the Tags in the outer where statement (see [1]), but this does not help either.

Example

Tool A has Tags t1, t2 and t3. Now I want to search all Tools that have the Tag t3, but the result set shall contain all three tags.

Expected result:

Tool A
\
 - Tag t1
 - Tag t2
 - Tag t3
db.Tool.scope('published').findAll({
    where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
    include: [
        {
            model: db.User,
            attributes: ['id', 'username']
        },
        {
            model: db.Tag,
            attributes: ['name'],
            through: { attributes: [] },
            // [2] Would limit the result specified tag
            // where: {
            //     name: {
            //         [Op.and]: filter.tag
            //     }
            // }
        },
        {
            model: db.Category,
            attributes: ['id', 'name', 'views'],
            through: { attributes: ['relevance'] },
            where: {
                id: {
                    [Op.and]: filter.category
                }
            }
        }
    ],
    where: {
        title: {
            [Op.like]: `%${filter.term}%`,
        }
    },
    attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
    order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})

I know I could perform this by performing a select via the Tag in the first place (db.Tag.findAll() instead of db.Tool.findAll(); I've already done this elsewhere in my project), but at the same time I also want to be able to filter by another entity (Category) the same way. So the Tool.findAll() should be the starting point.

Any help appreciated!

Gottlieb Notschnabel
  • 9,408
  • 18
  • 74
  • 116

1 Answers1

1

First off, you have two where clauses in your top-level query:

 where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
 // ...
 where: {
        title: {
            [Op.like]: `%${filter.term}%`,
        }
    },

I think your best approach is going to be with a literal subquery in the WHERE clause. Basically we want to find the ids of all of the tools that have the right tag and that contain the filter.term.

The subquery part for the WHERE looks something like...

SELECT ToolId FROM ToolTags WHERE TagId='t2';

Inspired by the subquery solution from this post Sequelize - subquery in where clause

// assuming your join table is named 'ToolTags' in the database--we need the real table name not the model name 
const tempSQL = sequelize.dialect.QueryGenerator.selectQuery('ToolTags',{
    attributes: ['ToolId'],
    where: {
          TagId: filter.tag
    }})
    .slice(0,-1); // to remove the ';' from the end of the SQL

db.Tool.scope('published').findAll({
    where: {
        title: {
            [Op.like]: `%${filter.term}%`,
        },
        id: {
            [Op.In]: sequelize.literal(`(${tempSQL})`)
        }
    },
    include: [
        {
            model: db.User,
            attributes: ['id', 'username']
        },
        {
            model: db.Tag,
            attributes: ['name'],
            through: { attributes: [] },
        },
       // {
       //     model: db.Category,
       //     attributes: ['id', 'name', 'views'],
       //     through: { attributes: ['relevance'] },
       //     where: {
       //         id: {
       //             [Op.and]: filter.category
       //         }
       //     }
       // }
    ],
    attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
    order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})

I commented out your category join for now. I think you should try to isolate the solution for the tags before adding more onto the query.

type
  • 366
  • 3
  • 8