Otra posible forma de hacerlo es
;
--Ensure that any immediately preceding statement is terminated with a semicolon above
WITH cte
AS (SELECT ROW_NUMBER() OVER (PARTITION BY Col1, Col2, Col3
ORDER BY ( SELECT 0)) RN
FROM #MyTable)
DELETE FROM cte
WHERE RN > 1;
Estoy usando lo ORDER BY (SELECT 0)anterior, ya que es arbitrario qué fila conservar en caso de empate.
Para conservar el último en RowIDorden, por ejemplo, podría usarORDER BY RowID DESC
Planes de ejecucion
El plan de ejecución para esto es a menudo más simple y más eficiente que el de la respuesta aceptada, ya que no requiere la autounión.

Esto no es siempre el caso, sin embargo. Un lugar donde se GROUP BYpodría preferir la solución son las situaciones en las que se elegiría un agregado de hash en lugar de un agregado de flujo.
La ROW_NUMBERsolución siempre dará el mismo plan, mientras que la GROUP BYestrategia es más flexible.

Los factores que podrían favorecer el enfoque agregado de hash serían
- No hay índice útil en las columnas de partición
- relativamente menos grupos con relativamente más duplicados en cada grupo
En las versiones extremas de este segundo caso (si hay muy pocos grupos con muchos duplicados en cada uno), también se podría considerar simplemente insertar las filas para mantenerlas en una nueva tabla, luego TRUNCATEcopiar el original y copiarlo de nuevo para minimizar el registro en comparación con eliminar un Muy alta proporción de las filas.
DELETE FROMun término CTE directamente. Ver stackoverflow.com/q/18439054/398670