在 include 的 include 上使用 where 子句对查询进行续集

Sequelize query with a where clause on an include of an include

提问人:xavier.seignard 提问时间:3/26/2020 最后编辑:xavier.seignard 更新时间:2/21/2021 访问量:2805

问:

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

一些背景

我有以下型号:

  • A 可以有 [0..n]ManifestationEvent
  • an 属于 1(an 不能没有EventManifestationEventManifestation)
  • A 可以有 [0..n]PlaceEvent
  • an 属于 1(an 不能没有EventPlaceEventPlace)
  • A 可以有 [1..n]ManifestationPlace
  • A 可以有 [0..n]PlaceManifestation

我按如下方式对关系进行建模:

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' })

对我来说,这似乎是正确的,但如果您有评论,请不要犹豫。

问题

我正在尝试查询以获取所有并在给定中发生的情况。但是对于那些,我想将它们包含在他们的中,即使给定的.PlaceManifestationEventPlaceEventManifestationManifestationPlace

下面是我尝试实现的“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
      ]
    }
  ]
}

所以我想包括 with : 11,因为它之一出现在给定的 (with : 1) 中ManifestationidEventPlaceid

更新(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()
  }
})

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

谢谢

SQL 续集.js

评论


答:

0赞 Nilanka Manoj 4/3/2020 #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);
        })
    })
}

从中,您可以获得分配给放置的所有表现形式,或者具有分配的任何事件。Manefestations 中包含的所有事件都分配给该地点。

编辑:您也可以使用以下方法:

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
            })
        })
    })
}

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

评论

0赞 xavier.seignard 4/3/2020
感谢您的回答,这就是我实际所做的,我一直在寻找一种续集的方式,而不是普通的 js 方式。
0赞 Nilanka Manoj 4/3/2020
不过,您可以执行数据库查询 SQL
0赞 xavier.seignard 4/4/2020
在查看了您的解决方案后,它也没有达到我想要的效果。仔细阅读我的问题
0赞 Nilanka Manoj 4/5/2020
第一个答案中有一些错别字,我对其进行了编辑。另外,我还添加了一个新答案
0赞 xavier.seignard 4/6/2020
感谢您的努力,我在问题中添加了一个很大的缺失部分:我当前的解决方案,它或多或少与您提供的解决方案相同。但我正在寻找一种“完整”的续集方式来做到这一点。
0赞 iwaduarte 11/22/2020 #2

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

Place.findAll({
          include: [           
            {
              model: Manifestation,
              attributes: ['id'],
              include: [{ 
              model: Event ,
              attributes: ['id']
               }]
            },
           ],
        })
0赞 Abhishek Shah 2/21/2021 #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));