Find islands using PostGIS

Assuming that you have a guaranteed topological equality between island and water polygon rings, using a simpler EXISTS filter should increase performance:

SELECT  a.id
FROM    islands AS a
WHERE   a.type <> 'water'
  AND   EXISTS (
    SELECT  1
    FROM    islands AS b,
            LATERAL ST_DumpRings(b.geom) AS dmp   -- rings to polygons
    WHERE   b.type = 'water'
      AND   dmp.path[1] > 0                       -- only interior rings
      AND   ST_Equals(a.geom, dmp.geom)
  )
;
  • ST_DumpRings is relatively cheap compared to constructing geometries, and enables finding matches more generically for all inner rings of a water body polygon
  • EXISTS will quit its execution once the first result row of its query is truthy
  • no additional aggregation (GROUP BY) required

@Cyril's answer will also work great with ST_DumpPoints, i.e. using

...
tblb AS (SELECT dmp.geom FROM islands, LATERAL ST_DumpRings(geom) AS dmp WHERE type = 'water' AND dmp.path[1] > 0)
... 

If topological equality is not guaranteed, you could check for ST_Within on the exterior ring polygon:

SELECT  a.id
FROM    islands AS a
WHERE   a.type <> 'water'
  AND   EXISTS (
    SELECT  1
    FROM    islands AS b,
            LATERAL ST_DumpRings(b.geom) AS dmp  -- rings to polygon
    WHERE   b.type = 'water'
      AND   dmp.path[1] = 0                      -- only exterior ring
      AND   ST_Within(a.geom, dmp.geom)
);

Here's another way to solve the question asked.

Run the script:

WITH                                                                          
tbla AS (SELECT * FROM islands WHERE type = 'land'),
tblb AS (SELECT ST_InteriorRingN(geom,1) geom FROM islands WHERE type = 'water')
SELECT ID, type, (a.geom) geom FROM tbla a INNER JOIN tblb b ON ST_Intersects(a.geom, b.geom);

Check the result, and if I misunderstood the question, let me know.