How to SELECT the newest four items per category?

前端 未结 8 2255
被撕碎了的回忆
被撕碎了的回忆 2020-11-22 01:43

I have a database of items. Each item is categorized with a category ID from a category table. I am trying to create a page that lists every category, and underneath each

8条回答
  •  爱一瞬间的悲伤
    2020-11-22 02:33

    This solution is an adaptation from another SO solution, thank you RageZ for locating this related/similar question.

    NOTE

    This solution seems satisfactory for Justin's use case. Depending on your use case you may want to check Bill Karwin or David Andres' solutions in this posting. Bill's solution has my vote! See why, as I put both queries next to one another ;-)

    The benefit of my solution is that it returns one record per category_id (the info from the item table is "rolled-up"). The main drawback of my solution is its lack of readability and its growing complexity as the number of desired rows grows (say to have 6 rows per category rather than 6). Also it may be slightly slower as the number of rows in the item table grows. (Regardless, all solutions will perform better with a smaller number of eligible rows in the item table, and it is therefore advisable to either periodically delete or move older items and/or to introduce a flag to help SQL filter out rows early)

    First try (didn't work!!!)...

    The problem with this approach was that the subquery would [rightfully but bad for us] produce very many rows, based on the cartesian products defined by the self joins...

    SELECT id, CategoryName(?), tblFourImages.*
    FROM category
    JOIN (
        SELECT i1.category_id, i1.image as Image1, i2.image AS Image2, i3.image AS Image3, i4.image AS Image4
        FROM item AS i1
        LEFT JOIN item AS i2 ON i1.category_id = i2.category_id AND i1.date_listed > i2.date_listed
        LEFT JOIN item AS i3 ON i2.category_id = i3.category_id AND i2.date_listed > i3.date_listed
        LEFT JOIN item AS i4 ON i3.category_id = i4.category_id AND i3.date_listed > i4.date_listed
    ) AS tblFourImages ON tblFourImages.category_id = category.id
    --WHERE  here_some_addtional l criteria if needed
    ORDER BY id ASC;
    

    Second try. (works ok!)

    A WHERE clause in added for the subquery, forcing the date listed to be the latest, second latest, thrird lateest etc. for i1, i2, i3 etc. respectively (and also allowing for the null cases when there are fewer than 4 items for a given category id). Also added was unrelated filter clauses to prevent showing entries that are "sold" or entries that do not have an image (added requirements)

    This logic makes the assumption that there are no duplicate date listed values (for a given category_id). Such cases would otherwise create duplicate rows. Effectively this use of the date listed is that of a monotonically incremented primary key as defined/required in Bill's solution.

    SELECT id, CategoryName, tblFourImages.*
    FROM category
    JOIN (
        SELECT i1.category_id, i1.image as Image1, i2.image AS Image2, i3.image AS Image3, i4.image AS Image4, i4.date_listed
        FROM item AS i1
        LEFT JOIN item AS i2 ON i1.category_id = i2.category_id AND i1.date_listed > i2.date_listed AND i2.sold = FALSE AND i2.image IS NOT NULL
              AND i1.sold = FALSE AND i1.image IS NOT NULL
        LEFT JOIN item AS i3 ON i2.category_id = i3.category_id AND i2.date_listed > i3.date_listed AND i3.sold = FALSE AND i3.image IS NOT NULL
        LEFT JOIN item AS i4 ON i3.category_id = i4.category_id AND i3.date_listed > i4.date_listed AND i4.sold = FALSE AND i4.image IS NOT NULL
        WHERE NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i1.date_listed)
          AND (i2.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i2.date_listed AND date_listed <> i1.date_listed)))
          AND (i3.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i3.date_listed AND date_listed <> i1.date_listed AND date_listed <> i2.date_listed)))
          AND (i4.image IS NULL OR (NOT EXISTS (SELECT * FROM item WHERE category_id = i1.category_id AND date_listed > i4.date_listed AND date_listed <> i1.date_listed AND date_listed <> i2.date_listed AND date_listed <> i3.date_listed)))
    ) AS tblFourImages ON tblFourImages.category_id = category.id
    --WHERE  --
    ORDER BY id ASC;
    

    Now... compare the following where I introduce an item_id key and use Bill's solution to provide the list of these to the "outside" query. You can see why Bill's approach is better...

    SELECT id, CategoryName, image, date_listed, item_id
    FROM item I
    LEFT OUTER JOIN category C ON C.id = I.category_id
    WHERE I.item_id IN 
    (
    SELECT i1.item_id
    FROM item i1
    LEFT OUTER JOIN item i2
      ON (i1.category_id = i2.category_id AND i1.item_id < i2.item_id
          AND i1.sold = 'N' AND i2.sold = 'N'
          AND i1.image <> '' AND i2.image <> ''
          )
    GROUP BY i1.item_id
    HAVING COUNT(*) < 4
    )
    ORDER BY category_id, item_id DESC
    

提交回复
热议问题