nodejs + sequelize :: 在需要的地方包含 false

Posted

技术标签:

【中文标题】nodejs + sequelize :: 在需要的地方包含 false【英文标题】:nodejs + sequelize :: include where required false 【发布时间】:2015-09-14 11:37:30 【问题描述】:

我有以下对象层次结构:

    用户 父母(hasOne) 儿童 (hasMany)

一旦我有了一个用户对象,我就会尝试加载关联的父对象及其一些子对象。以下作品:

user.getParent(
  include: [
    model: Child
  ]
).then(function(parent) 
  var children = parent.children;
);

但如果我想选择性地加载 一些 父母的孩子,像这样:

user.getParent(
  include: [
    model: Child,
    where:  gender: 'FEMALE' 
  ]
).then(function(parent) 
  var daughters = parent.daughters;
);

如果父母有一个或多个女儿,则查询有效,我会得到父母数据以及所有女儿的数据。 但是,如果父对象只有儿子,则返回的父对象是null..

我希望如果父对象没有女儿,父对象仍应使用children = nullchildren = [].. 解析。

另外,如果我想在加载父级时简单地加载子级计数,我该怎么做呢?

谢谢

【问题讨论】:

【参考方案1】:

原来是我自己的代码有问题。如果嵌套包含有一个where 子句不返回任何模型,sequelize 无法加载模型。为确保它不会完全失败,您可以设置一个 @ 987654323@ 子句和 where 子句..这使得 sequelize 返回一个空白数组而不是完全失败加载..

更多信息https://github.com/sequelize/sequelize/issues/4019

【讨论】:

【参考方案2】:

required: false + where 在 sequelize 5.0.2 SQLite vs PostgreSQL 中不一致/有问题

这是 Sequelize 关联错误/不一致的众多问题之一,这让我想在某个时候尝试找到另一个 ORM。报道于:https://github.com/sequelize/sequelize/issues/13809

在我们想要的地方考虑以下示例:

列出所有标签

此外,确定每个标签是否已分配给选定的动物

我们对动物的 where 条件是一个简单的“检查动物是否是特定动物”,因此只能返回 0 或 1 个条目。但同样适用于任何其他更复杂的条件。

main.js

#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const  DataTypes, Sequelize  = require('sequelize')
let sequelize
if (process.argv[2] === 'p') 
  sequelize = new Sequelize('tmp', undefined, undefined, 
    dialect: 'postgres',
    host: '/var/run/postgresql',
  )
 else 
  sequelize = new Sequelize(
    dialect: 'sqlite',
    storage: 'tmp.sqlite'
  )

;(async () => 
const Animal = sequelize.define('Animal', 
  species:  type: DataTypes.STRING ,
)
const Tag = sequelize.define('Tag', 
  name:  type: DataTypes.STRING ,
)
Animal.belongsToMany(Tag,  through: 'AnimalTag' )
Tag.belongsToMany(Animal,  through: 'AnimalTag' )
await sequelize.sync(force: true)
const animal0 = await Animal.create( species: 'bat' )
const animal1 = await Animal.create( species: 'ant' )
const animal2 = await Animal.create( species: 'dog' )
const tag0 = await Tag.create( name: 'mammal' )
const tag1 = await Tag.create( name: 'flying' )
const tag2 = await Tag.create( name: 'aquatic' )
await animal0.addTag(tag0)
await animal0.addTag(tag1)
await animal2.addTag(tag1)
const animals = [animal0, animal1, animal2]
for (let animal of animals) 
  let rows
  rows = await Tag.findAll(
    include: [
      model: Animal,
      where:  id: animal.id ,
      // No effect.
      //on:  id: animal.id ,
      through: 
        // Same as putting the where outside of through.
        //where:  AnimalId: animal.id ,
      ,
      required: false,
    ],
    order: [['name', 'ASC']],
  )
  console.error(animal.species);
  console.error(rows.map(row =>  return 
    name: row.name,
    animals: row.Animals.map(animal => animal.species),
   ))

)().finally(() =>  return sequelize.close() )

package.json


  "name": "tmp",
  "private": true,
  "version": "1.0.0",
  "dependencies": 
    "pg": "8.5.1",
    "pg-hstore": "2.3.3",
    "sequelize": "6.5.1",
    "sqlite3": "5.0.2"
  

运行:

npm install
./main.js p
./main.js

PotsgreSQL 结果符合预期:

bat
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [ 'bat' ] ,
   name: 'mammal', animals: [ 'bat' ] 
]

ant
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [] ,
   name: 'mammal', animals: [] 
]

dog
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [] ,
   name: 'mammal', animals: [ 'dog' ] 
]

但是 SQLite 的结果完全是错误的:

bat
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [ 'bat' ] ,
   name: 'mammal', animals: [ 'bat', null ] 
]

ant
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [] ,
   name: 'mammal', animals: [] 
]

dog
[
   name: 'aquatic', animals: [] ,
   name: 'flying', animals: [] ,
   name: 'mammal', animals: [] 
]

我不太介意null,我可以解决这个问题。

但是dog 不是mammal,我无法解决。

基础查询

我们可以查看底层查询来猜测发生了什么。

PostgreSQL 有以下类型的查询:

SELECT
  "Tag"."name",
  "Animals"."species" AS "Animals.species",
FROM
  "Tags" AS "Tag"
  LEFT OUTER JOIN (
    "AnimalTag" AS "Animals->AnimalTag"
    INNER JOIN "Animals" AS "Animals" ON "Animals"."id" = "Animals->AnimalTag"."AnimalId"
  ) ON "Tag"."id" = "Animals->AnimalTag"."TagId"
  AND "Animals"."id" = 1
ORDER BY
  "Tag"."name" ASC;

SQLite 具有以下类型的查询:

SELECT
 `Tag`.`name`,
 `Animals`.`species` AS `Animals.species`
FROM `Tags` AS `Tag`
LEFT OUTER JOIN `AnimalTag` AS `Animals->AnimalTag`
  ON `Tag`.`id` = `Animals->AnimalTag`.`TagId`
LEFT OUTER JOIN `Animals` AS `Animals`
  ON `Animals`.`id` = `Animals->AnimalTag`.`AnimalId`
  AND `Animals`.`id`= 3
ORDER BY
 `Tag`.`name` ASC;

因此,由于它使用子查询的方式,因此只有 PostgreSQL 查询是所需的查询,因此:

ON "Tag"."id" = "Animals->AnimalTag"."TagId"
  AND "Animals"."id" = 1

仅应用于外部LEFT OUTER JOIN

SQLite 查询产生了不希望的结果,为我们得到输出的每只动物手动运行它:

bat

aquatic|
flying|bat
mammal|bat
mammal|

ant

aquatic|
flying|
mammal|
mammal|

dot

aquatic|
flying|
mammal|
mammal|dog

所以似乎发生的情况是动物的第一个结果为空,然后 sequelize 在返回时忽略它,这可以解释我们看到的原因:

   name: 'mammal', animals: [ 'bat', null ] 

对于蝙蝠但是:

   name: 'mammal', animals: [] 

给狗。

所需的语句将在第一个 LEFT OUTER JOIN 上包含 AND

SELECT
 `Tag`.`name`,
 `Animals`.`species` AS `Animals.species`
FROM `Tags` AS `Tag`
LEFT OUTER JOIN `AnimalTag` AS `Animals->AnimalTag`
  ON `Tag`.`id` = `Animals->AnimalTag`.`TagId`
  AND `Animals->AnimalTag`.`AnimalId`= 1
LEFT OUTER JOIN `Animals` AS `Animals`
  ON `Animals`.`id` = `Animals->AnimalTag`.`AnimalId`
ORDER BY
 `Tag`.`name` ASC;

解决方法:超级多对多

到目前为止,我能想到的最佳解决方法是使用超级多对多,当问题是我们需要更好地控制 ON 条件时,这通常是一个不错的选择:

#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const  DataTypes, Sequelize  = require('sequelize')
let sequelize
let logging = process.argv[3] === '1'
if (process.argv[2] === 'p') 
  sequelize = new Sequelize('tmp', undefined, undefined, 
    dialect: 'postgres',
    host: '/var/run/postgresql',
    logging,
  )
 else 
  sequelize = new Sequelize(
    dialect: 'sqlite',
    storage: 'tmp.sqlite',
    logging,
  )

;(async () => 
const AnimalTag = sequelize.define('AnimalTag')
const Animal = sequelize.define('Animal', 
  species:  type: DataTypes.STRING ,
)
const Tag = sequelize.define('Tag', 
  name:  type: DataTypes.STRING ,
)
AnimalTag.belongsTo(Animal)
Animal.hasMany(AnimalTag)
AnimalTag.belongsTo(Tag)
Tag.hasMany(AnimalTag)
Animal.belongsToMany(Tag,  through: AnimalTag )
Tag.belongsToMany(Animal,  through: AnimalTag )

await sequelize.sync(force: true)
const animal0 = await Animal.create( species: 'bat' )
const animal1 = await Animal.create( species: 'ant' )
const animal2 = await Animal.create( species: 'dog' )
const tag0 = await Tag.create( name: 'flying' )
const tag1 = await Tag.create( name: 'mammal' )
const tag2 = await Tag.create( name: 'aquatic' )
await animal0.addTag(tag0)
await animal0.addTag(tag1)
await animal2.addTag(tag1)
const animals = [animal0, animal1, animal2]
for (let animal of animals) 
  let rows
  rows = await Tag.findAll(
    include: [
      model: AnimalTag,
      where:  AnimalId: animal.id ,
      required: false,
      include: [
        model: Animal,
      ]
    ],
    order: [['name', 'ASC']],
  )
  console.error(rows.map(row =>  return 
    name: row.name,
    animals: row.AnimalTags.map(animalTag => animalTag.Animal.species)
   ))
  console.error();

)().finally(() =>  return sequelize.close() )

为 PostgreSQL 和 Sequelize 生成正确的输出。生成的查询正是我们想要的:

SELECT
  "Tag"."id",
  "Tag"."name",
  "Tag"."createdAt",
  "Tag"."updatedAt",
  "AnimalTags"."createdAt" AS "AnimalTags.createdAt",
  "AnimalTags"."updatedAt" AS "AnimalTags.updatedAt",
  "AnimalTags"."AnimalId" AS "AnimalTags.AnimalId",
  "AnimalTags"."TagId" AS "AnimalTags.TagId",
  "AnimalTags->Animal"."id" AS "AnimalTags.Animal.id",
  "AnimalTags->Animal"."species" AS "AnimalTags.Animal.species",
  "AnimalTags->Animal"."createdAt" AS "AnimalTags.Animal.createdAt",
  "AnimalTags->Animal"."updatedAt" AS "AnimalTags.Animal.updatedAt"
FROM
  "Tags" AS "Tag"
  LEFT OUTER JOIN "AnimalTags" AS "AnimalTags" ON "Tag"."id" = "AnimalTags"."TagId"
  AND "AnimalTags"."AnimalId" = 3
  LEFT OUTER JOIN "Animals" AS "AnimalTags->Animal" ON "AnimalTags"."AnimalId" = "AnimalTags->Animal"."id"
ORDER BY
  "Tag"."name" ASC;

在 Ubuntu 21.10、PostgreSQL 13.5 上测试。

【讨论】:

以上是关于nodejs + sequelize :: 在需要的地方包含 false的主要内容,如果未能解决你的问题,请参考以下文章

我需要了解如何防止 NodeJS 上的 sql 注入 sequelize ORM

如何使用nodejs sequelize执行此SQL查询?

nodejs利用sequelize-auto 根据数据库的table 生成model

如何在 SEQUELIZE (nodeJS) 中创建触发器?

在nodejs sequelize中循环获取异步数据

NodeJS sequelize 自动生成模型并运行迁移 SQL 语法错误