Actualy, if you do a search for "DELETE DUPLICATES", you come up with just a couple of articles which use cursors and group bys and adding columns and using MAX/GROUP BY... and ... and... and... none of them appear to be straight forward.
So here's a straight forward, scalable approach (as a generic boilerplate). On my humble home computer, it will find and delete 400 duplicate rows in a million record table in less than a minute.
First, the disclaimer:
THIS SCRIPT DELETES DATA! DO NOT TEST ON PRODUCTION DATA! DO NOT USE ON PRODUCTION DATA UNLESS YOU HAVE A VIABLE AND RESTORABLE BACKUP FOR YOUR DATA. I AM NOT AND CANNOT BE MADE TO BE RESPONSIBLE FOR YOUR DATA IN ANY WAY, SHAPE, OR FORM!
WHERE tablename.ID IN
--The following SELECT statement acts as a derived table. The DISTINCT term is
--required because if there are 3 or more dupes for any given fields, the return
--will grow, exponentially.
SELECT DISTINCT t1.ID
FROM tablename AS t1
INNER JOIN tablename AS t2
-- These "ON" conditions contain the comparisons of the fields to be checked for dupes
ON t1.dupchkfield1 = t2.dupchkfield1
AND t1.dupchkfield2 = t2.dupchkfield2
--AND t1.dupchkfield3 = t2.dupchkfield3 --Add additional fields like this
--AND t1.dupchkfield4 = t2.dupchkfield4 --Add additional fields like this
-- This "ON" condition contains the comparison of the field to be checked for age.
-- "Age" can be based on date/datetime/time or auto-incrementing ID.
-- In all cases, the earlier/lower the value, the older the record is compared to others.
-- The comparison operator (">" in this example) determines which duplicate will be kept.
-- The functionality of this example can be changed by changing the comparison operator to...
-- ">" KEEPS only the OLDEST or EARLIEST record if duplicates are detected (OLD IS GREATer)
-- "<" KEEPS only the YOUNGEST or LATEST record if duplicates are detected (YOUNG is LESS-THAN OLD)
-- "<>" REMOVES ALL DUPLICATED RECORDS INCLUDING YOUNGEST AND OLDEST (be careful)
AND t1.chronologyfield > t2.chronologyfield