MAX(age) seems safe enough -- no one gets any younger :-).
Combining MIN(name) and MAX(name) would allow one name change ... that should handle everyone, right!?
/*
insert into #temp(id,name)
select 2,'Jane Single'
insert into #temp(id,age)
select 2,28
insert into #temp(id,contact_number)
select 2,'123-456-7890'
insert into #temp(id,contact_number)
select 2,'987-654-3210'
insert into #temp(id,name)
select 2,'Jane Married'
*/
SELECT
tGroup.id, ca1.name, tGroup.age, t.contact_number
FROM (
SELECT id, MIN(name) AS name_min, MAX(name) AS name_max, MAX(age) AS age
FROM #temp
GROUP BY id
) AS tGroup
LEFT OUTER JOIN #temp t ON
t.id = tGroup.id
CROSS APPLY (
SELECT DISTINCT name
FROM (
SELECT name_min AS name UNION ALL
SELECT name_max
) AS derived
) AS ca1
WHERE
t.contact_number IS NOT NULL
SQL DBA,SQL Server MVP(07, 08, 09) A socialist is someone who will give you the shirt off *someone else's* back.