Você pode ingressar na mesma tabela nos campos que seriam duplicados e depois ingressar no campo de identificação. Selecione o campo de identificação no primeiro alias da tabela (tn1) e use a função array_agg no campo de identificação do segundo alias da tabela. Por fim, para que a função array_agg funcione corretamente, você agrupará os resultados pelo campo tn1.id. Isso produzirá um conjunto de resultados que contém o ID de um registro e uma matriz de todos os IDs que atendem às condições de junção.
select tn1.id,
array_agg(tn2.id) as duplicate_entries,
from table_name tn1 join table_name tn2 on
tn1.year = tn2.year
and tn1.sid = tn2.sid
and tn1.user_id = tn2.user_id
and tn1.cid = tn2.cid
and tn1.id <> tn2.id
group by tn1.id;
Obviamente, os IDs que estarão na matriz duplicate_entries para um ID também terão suas próprias entradas no conjunto de resultados. Você precisará usar esse conjunto de resultados para decidir qual ID deseja tornar a fonte de 'verdade'. O único registro que não deve ser excluído. Talvez você possa fazer algo assim:
with dupe_set as (
select tn1.id,
array_agg(tn2.id) as duplicate_entries,
from table_name tn1 join table_name tn2 on
tn1.year = tn2.year
and tn1.sid = tn2.sid
and tn1.user_id = tn2.user_id
and tn1.cid = tn2.cid
and tn1.id <> tn2.id
group by tn1.id
order by tn1.id asc)
select ds.id from dupe_set ds where not exists
(select de from unnest(ds.duplicate_entries) as de where de < ds.id)
Seleciona os IDs de número mais baixo duplicados (assumindo que o ID esteja aumentando em PK). Esses seriam os IDs que você manteria por perto.