arema
arema

Reputation: 43

How to do two JOIN query from the same table in Vapor 3 using Fluent?

This is what I want to do (using postgresql):

select H."name", A."name"

FROM "Matches" M

JOIN "Teams" H ON M."homeTeamID" = H.id

JOIN "Teams" A ON M."awayTeamID" = A.id

//This will give me an error

return Matches.query(on: request)
.join(\Teams.id, to: \Matches.homeTeamID)
.alsoDecode(Teams.self)
.join(\Teams.id, to: \Matches.awayTeamID)
.alsoDecode(Teams.self)

Here is the error:

{

error: true,

reason: "table name "Teams" specified more than once"

}

Any help is appreciated! Thanks!

Upvotes: 4

Views: 565

Answers (3)

Luke
Luke

Reputation: 35

Appreciate this is an old question now but I had a similar problem and I solved it with an alternative approach by using a raw SQL query.

The below will add additional columns for the home and away team name. You'd need to create a MatchObject to decode the result to and make the connection specific to your circumstances.

func matchObjects(_ req: Request) throws -> Future<[MatchObject]> {
    return req.withPooledConnection(to: .psql, closure: { conn in
                return conn.raw("""
                    SELECT "Matches".*, h.name as home_team_name, a.name as away_team_name
                    FROM "Matches"
                    INNER JOIN "Teams" as h ON "Matches"."homeTeamID" = h.id
                    INNER JOIN "Teams" as a ON "Matches"."awayTeamID" = a.id
                    """).all(decoding: MatchObject.self)
            })
}

Upvotes: 0

mixio
mixio

Reputation: 101

I created a test project here: https://github.com/mixio/multi-join-test

Upvotes: 0

mixio
mixio

Reputation: 101

@arema, I tried to reproduce your use case and had a similar issue with Fluent. I reported the issue on Fluent's github: https://github.com/vapor/fluent/issues/563

Here is a workaround, but it's far from elegant.

// Requires conforming `Match` to hashable, Equatable.
func getMatches2Handler(_ req: Request) throws -> Future<[MatchObjects]> {
    return map(
        to: [MatchObjects].self,
        Match.query(on: req).join(\Team.id, to: \Match.homeTeamID).alsoDecode(Team.self).all(),
        Match.query(on: req).join(\Team.id, to: \Match.awayTeamID).alsoDecode(Team.self).all()
    ) { homeTuples, awayTuples in
        let homeDictionary = homeTuples.toDictionary()
        let awayDictionary = awayTuples.toDictionary()
        var matchObjectsArray: [MatchObjects] = []
        matchObjectsArray.reserveCapacity(homeDictionary.count)
        for (match, homeTeam) in homeDictionary {
            let awayTeam = awayDictionary[match]!
            matchObjectsArray.append(MatchObjects(match: match, homeTeam: homeTeam, awayTeam: awayTeam))
        }
        return matchObjectsArray
    }
}

//...

extension Array {
    func toDictionary<K,V>() -> [K:V] where Iterator.Element == (K,V) {
        return self.reduce([:]) {
            var dict:[K:V] = $0
            dict[$1.0] = $1.1
            return dict
        }
    }
}

Upvotes: 1

Related Questions