【问题标题】:Sequelize: multiple where clauseSequelize:多个where子句
【发布时间】:2017-07-26 18:06:57
【问题描述】:

我有以下表格:

文章 - 用户 - 标签 - 关注者 - 订阅者

文章属于用户(fk:文章表中的userId)

文章可以有很多标签。这是生成的tagarticle表:

这是关注者表:

还有 Suscribes 表:

一个用户可以关注多个用户并订阅一个国家(payId)、标签或文章(用于通知)。

如何查询关注用户的所有文章以及特定用户的订阅国家或标签?

【问题讨论】:

    标签: mysql node.js express sequelize.js


    【解决方案1】:

    通过断言获取文章的最小可运行示例

    https://stackoverflow.com/a/42634024/895245 是正确的,这是它的可运行版本,还涵盖了一些其他相关功能,例如限制和排序。更多感兴趣的示例在:How to implement many to many association in sequelize 测试于:

    npm install sequelize@6.5.1 sqlite3@5.0.2
    

    来源:

    #!/usr/bin/env node
    
    const assert = require('assert');
    const path = require('path');
    
    const { Sequelize, DataTypes } = require('sequelize');
    
    const sequelize = new Sequelize({
      dialect: 'sqlite',
      storage: 'db.sqlite3',
    });
    
    (async () => {
    
    // Create the tables.
    const User = sequelize.define('User', {
      name: { type: DataTypes.STRING },
    }, {});
    const Post = sequelize.define('Post', {
      body: { type: DataTypes.STRING },
    }, {});
    User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
    User.hasMany(Post);
    Post.belongsTo(User);
    await sequelize.sync({force: true});
    
    // Create data.
    const users = await User.bulkCreate([
      {name: 'user0'},
      {name: 'user1'},
      {name: 'user2'},
      {name: 'user3'},
    ])
    
    const posts = await Post.bulkCreate([
      {body: 'body00', UserId: users[0].id},
      {body: 'body11', UserId: users[0].id},
      {body: 'body10', UserId: users[1].id},
      {body: 'body11', UserId: users[1].id},
      {body: 'body20', UserId: users[2].id},
      {body: 'body21', UserId: users[2].id},
      {body: 'body30', UserId: users[3].id},
      {body: 'body31', UserId: users[3].id},
    ])
    
    await users[0].addFollows([users[1], users[2]])
    
    // Get all posts by authors that user0 follows.
    // The posts are placed inside their respetive authors under .Posts
    // so we loop to gather all of them.
    {
      const user0Follows = (await User.findByPk(users[0].id, {
        include: [
          {
            model: User,
            as: 'Follows',
            include: [
              {
                model: Post,
              }
            ],
          },
        ],
      })).Follows
      const postsFound = []
      for (const followedUser of user0Follows) {
        postsFound.push(...followedUser.Posts)
      }
      postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
      assert(postsFound[0].body === 'body10')
      assert(postsFound[1].body === 'body11')
      assert(postsFound[2].body === 'body20')
      assert(postsFound[3].body === 'body21')
      assert(postsFound.length === 4)
    }
    
    // With ordering, offset and limit.
    // The posts are placed inside their respetive authors under .Posts
    // The only difference is that posts that we didn't select got removed.
    
    {
      const user0Follows = (await User.findByPk(users[0].id, {
        offset: 1,
        limit: 2,
        // TODO why is this needed? It does try to make a subquery otherwise, and then it doesn't work.
        // https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize-
        subQuery: false,
        include: [
          {
            model: User,
            as: 'Follows',
            include: [
              {
                model: Post,
              }
            ],
          },
        ],
      })).Follows
      assert(user0Follows[0].name === 'user1')
      assert(user0Follows[1].name === 'user2')
      assert(user0Follows.length === 2)
      const postsFound = []
      for (const followedUser of user0Follows) {
        postsFound.push(...followedUser.Posts)
      }
      postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
      // Note that what happens is that some of the
      assert(postsFound[0].body === 'body11')
      assert(postsFound[1].body === 'body20')
      assert(postsFound.length === 2)
    
      // Same as above, but now with DESC ordering.
      {
        const user0Follows = (await User.findByPk(users[0].id, {
          order: [[
            {model: User, as: 'Follows'},
            Post,
            'body',
            'DESC'
          ]],
          offset: 1,
          limit: 2,
          subQuery: false,
          include: [
            {
              model: User,
              as: 'Follows',
              include: [
                {
                  model: Post,
                }
              ],
            },
          ],
        })).Follows
        // Note how user ordering is also reversed from an ASC.
        // it likely takes the use that has the first post.
        assert(user0Follows[0].name === 'user2')
        assert(user0Follows[1].name === 'user1')
        assert(user0Follows.length === 2)
        const postsFound = []
        for (const followedUser of user0Follows) {
          postsFound.push(...followedUser.Posts)
        }
        // In this very specific data case, this would not be needed.
        // because user2 has the second post body and user1 has the first
        // alphabetically.
        postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
        // Note that what happens is that some of the
        assert(postsFound[0].body === 'body20')
        assert(postsFound[1].body === 'body11')
        assert(postsFound.length === 2)
      }
    
      // Here user2 would have no post hits due to the limit,
      // so it is entirely pruned from the user list as desired.
      // Otherwise we would fetch a lot of unwanted user data
      // in a large database.
      const user0FollowsLimit2 = (await User.findByPk(users[0].id, {
        limit: 2,
        subQuery: false,
        include: [
          {
            model: User,
            as: 'Follows',
            include: [ { model: Post } ],
          },
        ],
      })).Follows
      assert(user0FollowsLimit2[0].name === 'user1')
      assert(user0FollowsLimit2.length === 1)
    
      // Case in which our post-sorting is needed.
      // TODO: possible to get sequelize to do this for us by returning
      // a flat array directly?
      // It's not big deal since the LIMITed result should be small,
      // but feels wasteful.
      // https://stackoverflow.com/questions/41502699/return-flat-object-from-sequelize-with-association
      // https://github.com/sequelize/sequelize/issues/4419
      {
        await Post.truncate({restartIdentity: true})
        const posts = await Post.bulkCreate([
          {body: 'body0', UserId: users[0].id},
          {body: 'body1', UserId: users[1].id},
          {body: 'body2', UserId: users[2].id},
          {body: 'body3', UserId: users[3].id},
          {body: 'body4', UserId: users[0].id},
          {body: 'body5', UserId: users[1].id},
          {body: 'body6', UserId: users[2].id},
          {body: 'body7', UserId: users[3].id},
        ])
        const user0Follows = (await User.findByPk(users[0].id, {
          order: [[
            {model: User, as: 'Follows'},
            Post,
            'body',
            'DESC'
          ]],
          subQuery: false,
          include: [
            {
              model: User,
              as: 'Follows',
              include: [
                {
                  model: Post,
                }
              ],
            },
          ],
        })).Follows
        assert(user0Follows[0].name === 'user2')
        assert(user0Follows[1].name === 'user1')
        assert(user0Follows.length === 2)
        const postsFound = []
        for (const followedUser of user0Follows) {
          postsFound.push(...followedUser.Posts)
        }
        // We need this here, otherwise we would get all user2 posts first:
        // body6, body2, body5, body1
        postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
        assert(postsFound[0].body === 'body6')
        assert(postsFound[1].body === 'body5')
        assert(postsFound[2].body === 'body2')
        assert(postsFound[3].body === 'body1')
        assert(postsFound.length === 4)
      }
    }
    
    await sequelize.close();
    })();
    

    超级多对多做“关注用户的帖子”查询,无需后期处理

    Super many to many 表示除了每个模型的belongsToMany 之外,还要在每个模型和直通表之间显式设置belongsTo/hasMany

    这是我发现在不进行后期处理的情况下很好地进行“关注用户的帖子”查询的唯一方法。

    const assert = require('assert');
    const path = require('path');
    
    const { Sequelize, DataTypes, Op } = require('sequelize');
    
    const sequelize = new Sequelize({
      dialect: 'sqlite',
      storage: 'tmp.' + path.basename(__filename) + '.sqlite',
      define: {
        timestamps: false
      },
    });
    
    (async () => {
    
    // Create the tables.
    const User = sequelize.define('User', {
      name: { type: DataTypes.STRING },
    });
    const Post = sequelize.define('Post', {
      body: { type: DataTypes.STRING },
    });
    const UserFollowUser = sequelize.define('UserFollowUser', {
        UserId: {
          type: DataTypes.INTEGER,
          references: {
            model: User,
            key: 'id'
          }
        },
        FollowId: {
          type: DataTypes.INTEGER,
          references: {
            model: User,
            key: 'id'
          }
        },
      }
    );
    
    // Super many to many.
    User.belongsToMany(User, {through: UserFollowUser, as: 'Follows'});
    UserFollowUser.belongsTo(User)
    User.hasMany(UserFollowUser)
    
    User.hasMany(Post);
    Post.belongsTo(User);
    
    await sequelize.sync({force: true});
    
    // Create data.
    const users = await User.bulkCreate([
      {name: 'user0'},
      {name: 'user1'},
      {name: 'user2'},
      {name: 'user3'},
    ])
    const posts = await Post.bulkCreate([
      {body: 'body0', UserId: users[0].id},
      {body: 'body1', UserId: users[1].id},
      {body: 'body2', UserId: users[2].id},
      {body: 'body3', UserId: users[3].id},
      {body: 'body4', UserId: users[0].id},
      {body: 'body5', UserId: users[1].id},
      {body: 'body6', UserId: users[2].id},
      {body: 'body7', UserId: users[3].id},
    ])
    await users[0].addFollows([users[1], users[2]])
    
    // Get all the posts by authors that user0 follows.
    // without any post process sorting. We only managed to to this
    // with a super many to many, because that allows us to specify
    // a reversed order in the through table with `on`, since we need to
    // match with `FollowId` and not `UserId`.
    {
      const postsFound = await Post.findAll({
        order: [[
          'body',
          'DESC'
        ]],
        include: [
          {
            model: User,
            attributes: [],
            required: true,
            include: [
              {
                model: UserFollowUser,
                on: {
                  FollowId: {[Op.col]: 'User.id' },
                },
                attributes: [],
                where: {UserId: users[0].id},
              }
            ],
          },
        ],
      })
      assert.strictEqual(postsFound[0].body, 'body6')
      assert.strictEqual(postsFound[1].body, 'body5')
      assert.strictEqual(postsFound[2].body, 'body2')
      assert.strictEqual(postsFound[3].body, 'body1')
      assert.strictEqual(postsFound.length, 4)
    }
    
    await sequelize.close();
    })();
    

    【讨论】:

      【解决方案2】:

      我假设您询问 Sequelize 的查询方式。 我不确定我是否正确理解了您的问题。您正在寻找两个查询:

      • 查询关注用户的所有文章,
      • 查询特定用户的订阅国家/标签/文章,

      让我从模型之间的关联开始。

      // in User model definition
      User.belongsToMany(User, { as: 'Followers', through: 'Followers', foreignKey: 'userId', otherKey: 'followId' });
      User.hasMany(Subscribe, { foreignKey: 'userId' });
      User.hasMany(Article, { foreignKey: 'userId' });
      

      使用上述关联,我们现在可以查询所有关注用户的文章

      models.User.findByPrimary(1, {
          include: [
              {
                  model: models.User,
                  as: 'Followers',
                  include: [ models.Article ]
              }
          ]
      }).then(function(user){
          // here you have user with his followers and their articles
      });
      

      上面的查询会产生类似于

      的结果
      {
          id: 1,
          Followers: [
              {
                  id: 4,
                  Articles: [
                      {
                          id: 1,
                          title: 'article title' // some example field of Article model
                      }
                  ]
              }
          ]
      }
      

      如果要查询特定用户订阅的国家/标签/文章,则必须在Subscribe模型中进行另一个关联

      // in Subscribe model definition
      Subscribe.belongsTo(Tag, { foreignKey: 'tagId' });
      Subscribe.belongsTo(Article, { foreignKey: 'articleId' });
      Subscribe.belongsTo(Country, { foreignKey: 'payId' });
      

      现在我们拥有执行您要求的第二个查询所需的所有关联

      models.User.findByPrimary(1, {
          include: [
              {
                  model: models.Subscribe,
                  include: [ models.Tag, models.Country, models.Article ]
              }
          ]
      }).then(function(user){
          // here you get user with his subscriptions
      });
      

      在此示例中,您将通过user.Subscribes 获得所有订阅的用户,这将具有嵌套属性TagCountryArticle。如果用户订阅了Tag,在这种情况下CountryArticle 都将是NULL

      【讨论】:

      • 谢谢。这正是我所需要的
      猜你喜欢
      • 2016-06-09
      • 1970-01-01
      • 2018-01-08
      • 2019-12-23
      • 2016-07-09
      • 2017-08-27
      • 2019-08-18
      • 2018-10-06
      • 2014-11-07
      相关资源
      最近更新 更多