Search code examples
mysqlsqlsubqueryderived-table

Unknown column from within double nested derived table in JOIN statement


In my DB, there are two types of images: challenges, and answers. They both have lat/lng location columns. In this query, I wish to select the Challenge or Answer that the user created most recently via an INNER JOIN.

(The business logic behind this: we basically want to obtain a list of users which includes the user's last known location, which is determined by their most recent Challenge or Answer - whichever is most recent. If a user does not have a last known location, they should not be included in this list.)

I am getting [Err] 1054 - Unknown column 'U.Id' in 'where clause':

SELECT
    U.Id,
    U.TotalPoints,
    LastImage.Lat,
    LastImage.Lng
FROM User U
INNER JOIN
(
    SELECT Lat, Lng FROM
    (
        (SELECT Lat, Lng, CreatedOn FROM AnswerImage WHERE UserId = U.Id ORDER BY Id DESC LIMIT 1)
        UNION ALL
        (SELECT Lat, Lng, CreatedOn FROM ChallengeImage WHERE UserId = U.Id ORDER BY Id DESC LIMIT 1)
    ) LastImages ORDER BY CreatedOn DESC LIMIT 1
) LastImage
WHERE U.Type = 1 AND U.Status = 2
ORDER BY TotalPoints DESC;

I cannot seem to reference the User table (alias U) from within my derived 'LastImages' table (or whatever the proper term for it is).

Can anyone help? I've tried other methods, but none meet all my requirements:

  1. A row is only returned if the user has at least 1 challenge or 1 answer (hence the UNION)
  2. The most recent (as determined by ChallengeImage.CreatedOn and AnswerImage.CreatedOn) image is to be used in the join

Thanks!


Solution

  • SELECT u.id,
           u.TotalPoints,
           IF(IFNULL(a.CreatedOn, '1900-01-01') > IFNULL(c.CreatedOn, '1900-01-01'), a.Lat, c.Lat) AS Lat,
           IF(IFNULL(a.CreatedOn, '1900-01-01') > IFNULL(c.CreatedOn, '1900-01-01'), a.Lng, c.Lng) AS Lng
    FROM User AS u
    LEFT JOIN
        (SELECT UserId, Lat, Lng, CreatedOn
         FROM AnswerImage AS a
         JOIN (SELECT UserId, MAX(CreatedOn) AS CreatedOn
               FROM AnswerImage
               GROUP BY UserId) AS amax
         USING (UserId, CreatedOn)) AS a
        ON u.id = a.UserId
    LEFT JOIN
        (SELECT UserId, Lat, Lng, CreatedOn
         FROM ChallengeImage AS c
         JOIN (SELECT UserId, MAX(CreatedOn) AS CreatedOn
               FROM ChallengeImage
               GROUP BY UserId) AS cmax
         USING (UserId, CreatedOn)) AS c
        ON u.id = c.UserId
    WHERE U.Type = 1 AND U.Status = 2
        AND (a.Lat IS NOT NULL OR c.Lat IS NOT NULL)
    ORDER BY TotalPoints DESC;
    

    The subqueries are one of the common ways to get the last row per group in a table, see

    Retrieving the last record in each group

    Then you left join them with the User table so you'll get results even if a user doesn't have matches in both tables.