Search code examples
mysqlsqlsubquerysequelize.jswhere-clause

Where with SQL subquery


I have the following query:

SELECT 
`id`, `firstName`, `lastName`, `nickName`, `email`, 
`verificationCode`, `roleId`, `profileImageId`, `statusId`, `createdAt`, 
(SELECT COUNT(*) FROM RecipeStores WHERE RecipeStores.userId = User.id) AS `countStoredRecipes`, 
(SELECT COUNT(*) FROM RecipeFavorites WHERE RecipeFavorites.userId = User.id) AS `countLikedRecipes`, 
(SELECT COUNT(*) FROM Recipes WHERE Recipes.createdById = User.id) AS `countCreatedRecipes` 
FROM `Users` AS `User` WHERE `User`.`countStoredRecipes` >= 2;

However, it doesn't work. I would like to filter by the result of the subquery.

I understand that the subquery cannot be worked as one more column, so I would expect some other way to do it.


Solution

  • MySQL implements an extension to standard SQL that lets you refer to an alias in the HAVING clause:

    SELECT 
        `id`, `firstName`, `lastName`, `nickName`, `email`, 
        `verificationCode`, `roleId`, `profileImageId`, `statusId`, `createdAt`, 
        (SELECT COUNT(*) FROM RecipeStores rs WHERE rs.userId = u.id) AS `countStoredRecipes`, 
        (SELECT COUNT(*) FROM RecipeFavorites rf WHERE rf.userId = u.id) AS `countLikedRecipes`, 
        (SELECT COUNT(*) FROM Recipes r WHERE r.createdById = u.id) AS `countCreatedRecipes` 
    FROM `Users` u
    HAVING `User`.`countStoredRecipes` >= 2;
    

    I am not a big fan of this technique: this really is bespoke MySQL syntax, that wouldn't run in any other database.

    A more standard option is to turn the existing query to a subquery, and filter in the outer scope:

    SELECT *
    FROM (
        SELECT 
            `id`, `firstName`, `lastName`, `nickName`, `email`, 
            `verificationCode`, `roleId`, `profileImageId`, `statusId`, `createdAt`, 
            (SELECT COUNT(*) FROM RecipeStores rs WHERE rs.userId = u.id) AS `countStoredRecipes`, 
            (SELECT COUNT(*) FROM RecipeFavorites rf WHERE rf.userId = u.id) AS `countLikedRecipes`, 
            (SELECT COUNT(*) FROM Recipes r WHERE r.createdById = u.id) AS `countCreatedRecipes` 
        FROM `Users` u
    ) t
    WHERE `countStoredRecipes` >= 2;
    

    In very recent versions of MySQL, you can use lateral joins:

    SELECT 
        `id`, `firstName`, `lastName`, `nickName`, `email`, 
        `verificationCode`, `roleId`, `profileImageId`, `statusId`, `createdAt`, 
        rs.*, rf.*, r.* 
        () AS  
    FROM `Users` u
    INNER JOIN LATERAL (
        SELECT COUNT(*) AS `countStoredRecipes` 
        FROM RecipeStores rs 
        WHERE rs.userId = u.id
    ) rs ON rs.`countStoredRecipes`>= 2
    LEFT JOIN LATERAL (
        SELECT COUNT(*) AS `countLikedRecipes` 
        FROM RecipeFavorites rf 
        WHERE rf.userId = u.id
    ) rf ON true
    LEFT JOIN LATERAL (
        SELECT COUNT(*) AS `countCreatedRecipes` 
        FROM Recipes r 
        WHERE r.createdById = u.id
    ) r ON true