Skip to content

Instantly share code, notes, and snippets.

@ahiipsa
Last active August 29, 2015 13:58
Show Gist options
  • Select an option

  • Save ahiipsa/9972235 to your computer and use it in GitHub Desktop.

Select an option

Save ahiipsa/9972235 to your computer and use it in GitHub Desktop.
How to find a records with two or more relationships.

I have the following JS code:

db.models
    .Vobject
    .findAndCountAll({
    where: [{type: 1 }],
    include: [{model: db.models.Tag, where: {name: ['iron', 'tefal']}}],
    limit: 10
})

The problem is that sequelize generates the following query:

select object where type = 2 limit 10 after find tags iron or tefal for this object

For example:

SELECT attributes
FROM   (SELECT `vobject`.*
        FROM   `vobject`
        WHERE  ( `vobject`.`type` = 1 )
        LIMIT  10) AS `vobject`
       INNER JOIN `vobject_tag_to_vobject` AS
                  `vobject_tags.vobject_tag_to_vobject`
               ON `vobject`.`uuid` =
       `vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`
       INNER JOIN `vobject_tag` AS `vobject_tags`
               ON `vobject_tags`.`id` =
`vobject_tags.vobject_tag_to_vobject`.`tag_id`
                  AND `vobject_tags`.`name` IN ( 'iron', 'tefal' );

But I need the generated SQL query to look like this:

select object where type = 2 and tags in (iron,tefal) limit 10

For example:

SELECT attributes
FROM   `vobject`
       INNER JOIN `vobject_tag_to_vobject` AS
                  `vobject_tags.vobject_tag_to_vobject`
               ON `vobject`.`uuid` =
       `vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`
       INNER JOIN `vobject_tag` AS `vobject_tags`
               ON `vobject_tags`.`id` =
`vobject_tags.vobject_tag_to_vobject`.`tag_id`
                  AND `vobject_tags`.`name` IN ( 'iron', 'tefal' )
WHERE  ( `vobject`.`type` = 1 )
LIMIT  10;

This happens because of the subquery requirement check in QueryGenerator.selectQuery

, subQuery = limit && (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation )

After, I created the option.filterByInclude and added it to the condition, everything started to work fine :)

, subQuery = limit && !option.filterByInclude (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation )

Now it returns objects with either 'iron' or 'tefal' tag.

The next step is to find object with both 'iron' and 'tefal' tags. I modified my query as follows:

  • added group by object uuid clause
  • added having where objects >= 2 clause
db.models
    .Vobject
    .findAndCountAll({
    where: [{type: 1 }],
        include: [{model: db.models.Tag, where: {name: ['iron', 'tefal']}}],
        limit: 10,
        filterByInclude: true,
        group: [{raw: '`vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`'}],
        group: ['`vobject`.`uuid`'],
        having: ['COUNT(?) >= ?', '`vobject`.`uuid`', 2]
})
SELECT attributes...
FROM   `vobject`
       INNER JOIN `vobject_tag_to_vobject` AS
                  `vobject_tags.vobject_tag_to_vobject`
               ON `vobject`.`uuid` =
       `vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`
       INNER JOIN `vobject_tag` AS `vobject_tags`
               ON `vobject_tags`.`id` =
`vobject_tags.vobject_tag_to_vobject`.`tag_id`
                  AND `vobject_tags`.`name` IN ( 'iron', 'tefal' )
WHERE  ( `vobject`.`type` = 1 )
GROUP  BY `vobject`.`uuid`
HAVING Count('`vobject`.`uuid`') >= 2
LIMIT  10;

Hurray, it works! But the count is broken now, it has a value of '2'. This query returns 35 rows (the previous query returns the same number of rows) and each row has count equal to '2'.

This happens because of the GROUP BY clause:

SELECT Count(`vobject`.`uuid`) AS `count`
FROM   `vobject`
       INNER JOIN `vobject_tag_to_vobject` AS
                  `vobject_tags.vobject_tag_to_vobject`
               ON `vobject`.`uuid` =
       `vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`
       INNER JOIN `vobject_tag` AS `vobject_tags`
               ON `vobject_tags`.`id` =
`vobject_tags.vobject_tag_to_vobject`.`tag_id`
                  AND `vobject_tags`.`name` IN ( 'iron', 'tefal' )
WHERE  ( `vobject`.`type` = 1 )
GROUP  BY `vobject`.`uuid`
HAVING Count('`vobject`.`uuid`') >= 2;

To make this work we need to move the main query into subquery:

SELECT COUNT(*) as count FROM (
    SELECT `vobject`.`uuid` AS `count`
    FROM   `vobject`
           INNER JOIN `vobject_tag_to_vobject` AS
                      `vobject_tags.vobject_tag_to_vobject`
                   ON `vobject`.`uuid` =
           `vobject_tags.vobject_tag_to_vobject`.`vobject_uuid`
           INNER JOIN `vobject_tag` AS `vobject_tags`
                   ON `vobject_tags`.`id` =
    `vobject_tags.vobject_tag_to_vobject`.`tag_id`
                      AND `vobject_tags`.`name` IN ( 'iron', 'tefal' )
    WHERE  ( `vobject`.`type` = 1 )
    GROUP  BY `vobject`.`uuid`
    HAVING Count('`vobject`.`uuid`') >= 2
) as tmp;

Modify DAOFactory.prototype.count

options.attributes = [
    [this.sequelize.fn('COUNT', col), 'count']
]
// change to 
options.count = true;
options.attributes = [col]

Modify QueryGenerator.selectQuery

// add before the end of query
if(options.count) {
    query = 'SELECT COUNT(*) as count FROM (' + query + ') as tmp';
} 

query += ";";

Now everything works as it should :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment