【问题标题】:Sequelize query with a where clause on an include of an include在包含的包含上使用 where 子句对查询进行续集
【发布时间】:2020-07-06 21:04:57
【问题描述】:

我正在努力使用 sequelize 创建查询。

一些背景

我有以下型号:

  • Manifestation 可以有 [0..n] Event
  • 一个Event 属于一个Manifestation(没有Manifestation 就不能存在Event
  • Place 可以有 [0..n] Event
  • Event 属于一个 Place(如果没有 PlaceEvent 就不能存在)
  • Manifestation 可以有 [1..n] Place
  • Place 可以有 [0..n] Manifestation

我将关系建模如下:

Manifestation.hasMany(Event, { onDelete: 'CASCADE', hooks: true })
Event.belongsTo(Manifestation)

Place.hasMany(Event, { onDelete: 'CASCADE', hooks: true })
Event.belongsTo(Place)

Manifestation.belongsToMany(Place, { through: 'manifestation_place' })
Place.belongsToMany(Manifestation, { through: 'manifestation_place' })

对我来说似乎比较正确,但如果您有意见,请不要犹豫。

问题

我正在尝试查询Place,以便在给定的Place 中获取所有ManifestationEvent。但是对于Event ,我想将它们包含在他们的Manifestation 中,即使Manifestation 没有出现在给定的Place 中。

下面是我想要实现的“JSON”结构:

{
  id: 1,
  name: "Place Name",
  address: "Place address",
  latitude: 47.00000,
  longitude: -1.540000,
  manifestations: [
    {
      id: 10,
      title: "Manifestation one",
      placeId: 1,
      events: []
    },
    {
      id: 11,
      title: "Manifestation two",
      placeId: 3,
      events: [
        id: 5,
        title: "3333",
        manifestationId: 11,
        placeId: 1
      ]
    }
  ]
}

所以我想将Manifestation 包含在id: 11 中,因为它的Event 之一出现在给定的Place 中(id: 1)

更新 (04/06/20):现在我依靠 javascript 来获得预期的结果

我想如果我在询问之前发布我当前的解决方案会很好。

router.get('/test', async (req, res) => {
  try {
    const placesPromise = place.findAll()
    const manifestationsPromise = manifestation.findAll({
      include: [
        { model: event },
        {
          model: place,
          attributes: ['id'],
        },
      ],
    })

    const [places, untransformedManifestations] = await Promise.all([
      placesPromise,
      manifestationsPromise,
    ])

    const manifestations = untransformedManifestations.map(m => {
      const values = m.toJSON()
      const places = values.places.map(p => p.id)
      return { ...values, places }
    })

    const result = places
      .map(p => {
        const values = p.toJSON()
        const relatedManifestations = manifestations
          .filter(m => {
            const eventsPlaceId = m.events.map(e => e.placeId)
            return (
              m.places.includes(values.id) ||
              eventsPlaceId.includes(values.id)
            )
          })
          .map(m => {
            const filteredEvents = m.events.filter(
              e => e.placeId === values.id
            )
            return { ...m, events: filteredEvents }
          })
        return { ...values, manifestations: relatedManifestations }
      })
      .filter(p => p.manifestations.length)

    return res.status(200).json(result)
  } catch (err) {
    console.log(err)
    return res.status(500).send()
  }
})

但我很确定我可以直接使用 sequelize 做到这一点。有什么想法或建议吗?

谢谢

【问题讨论】:

    标签: sql node.js sequelize.js


    【解决方案1】:

    这不是最优的。不过你可以试试看:

    const findPlace = (id) => {
        return new Promise(resolve => {
            db.Place.findOne({
                where: {
                    id: id
                }
            }).then(place => {
                db.Manefestation.findAll({
                    include: [{
                        model: db.Event,
                        where: {
                            placeId: id
                        }
                    }]
    
                }).then(manifestations => {
                    const out = Object.assign({}, {
                        id: place.id,
                        name: place.name,
                        address: place.address,
                        latitude: place.latitude,
                        longitude: place.longitude,
                        manifestations: manifestations.reduce((res, manifestation) => {
                            if (manifestation.placeId === place.id || manifestation.Event.length > 0) {
                                res.push({
                                    id: manifestation.id,
                                    title: manifestation.id,
                                    placeId: manifestation.placeId,
                                    events: manifestation.Event
                                })
                            }
                            return res;
                        }, [])
                    })
                })
                resolve(out);
            })
        })
    }
    

    由此,您可以得到所有指定地点或任何指定事件的表现形式。显化中包含的所有事件都分配到该地点。

    编辑: 您也可以使用以下一种:

    const findPlace = (id) => {
        return new Promise(resolve => {
            db.Place.findOne({
                include: [{
                    model: db.Manefestation,
                    include: [{
                        model: db.Event,
                        where: {
                            placeId: id
                        }
                    }]
    
                }],
                where: {
                    id: id
                }
            }).then(place => {
                db.Manefestation.findAll({
                    include: [{
                        model: db.Event,
                        where: {
                            placeId: id
                        }
                    }],
                    where: {
                        placeId: {
                            $not: id
                        }
                    }
    
                }).then(manifestations => {
                    place.Manefestation = place.Manefestation.concat(manifestations.filter(m=>m.Event.length>0))
                    resolve(place);// or you can rename, reassign keys here
                })
            })
        })
    }
    

    这里我只在第一个查询中直接表示。然后,不包含和连接的表现形式。

    【讨论】:

    • 感谢您的回答,这就是我实际所做的,我正在寻找一种续集的方式来代替普通的 js 方式。
    • 你可以执行db查询SQL
    • 查看您的解决方案后,它也无法满足我的要求。仔细阅读我的问题
    • 第一个答案中有一些错别字,我对其进行了编辑。另外,我也添加了一个新答案
    • 感谢您的努力,我在我的问题中添加了一个很大的缺失部分:我当前的解决方案,它或多或少与您提供的相同。但我正在寻找一种“完整”的续集方式来做到这一点。
    【解决方案2】:

    我不知道你现在是否想通了。但下面提供了解决方案。 使用 Sequelize 搜索可能会很有趣 :)。您必须包含在另一个包含中。如果查询变慢,请使用separate:true

    Place.findAll({
              include: [           
                {
                  model: Manifestation,
                  attributes: ['id'],
                  include: [{ 
                  model: Event ,
                  attributes: ['id']
                   }]
                },
               ],
            })
    

    【讨论】:

      【解决方案3】:

      我尝试在单个查询中完成它,但您仍然需要 JavaScript 才能获得所需的输出类型。

      (注意:? 您需要与地点无关的表现形式,但如果该地点存在事件,则应包括在内。获得该表现的唯一 SQL 方法是在所有表之间执行 CROSS JOIN,然后过滤掉结果将是一个非常繁重的查询)


      我想出了这个代码(尝试并执行),它不需要您执行 2 findAll 来获取您当前使用的所有数据。相反,它仅在 1 个查询中获取最终输出所需的数据。

      const places = await Place.findAll({
          include: [{
              model: Manifestation,
              // attributes: ['id']
              through: {
                  attributes: [], // this helps not get keys/data of join table
              },
          }, {
              model: Event,
              include: [{
                  model: Manifestation,
                  // attributes: ['id']
              }],
          }
          ],
      });
      console.log('original output places:', JSON.stringify(places, null, 2));
      
      const result = places.map(p => {
          // destructuring to separate out place, manifestation, event object keys
          const {
              manifestations,
              events,
              ...placeData
          } = p.toJSON();
      
          // building modified manifestation with events array
          const _manifestations = manifestations.map(m => {
              return ({ ...m, events: [] })
          });
      
          // going through places->events to push them to respective manifestation events array
          // + add manifestation which is not directly associated to place but event is of that manifestation
          events.map(e => {
              const {
                  manifestation: e_manifestation, // renaming variable
                  ...eventData
              } = e;
              const mIndex = _manifestations.findIndex(m1 => m1.id === e.manifestationId)
              if (mIndex === -1) { // if manifestation not found add it with the events array
                  _manifestations.push({ ...e_manifestation, events: [eventData] });
              } else { // if found push it into events array
                  _manifestations[mIndex].events.push(eventData);
              }
          });
          // returning a place object with manifestations array that contains events array
          return ({ ...placeData, manifestations: _manifestations });
      })
      // filter `.filter(p => p.manifestations.length)` as used in your question
      console.log('modified places', JSON.stringify(result, null, 2));
      

      【讨论】:

        猜你喜欢
        • 2015-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多