Paste this into a new question or an answer at dba.stackexchange.com:
<!-- --> > create table links(id1, id2, id3, id4, content) as > values (1,2,3,4,'Text') > , (1,2,5,4,'Other thing') > , (8,4,3,2,'More stuff'); > > <pre> 3 rows affected > </pre> <!-- --> > explain > select * > from links l > inner join ( > values (1,2,3,4),(1,2,5,4),(8,4,3,2) > ) v(id1, id2, id3, id4) using(id1, id2, id3, id4); > > <pre> > | QUERY PLAN | > | :--------------------------------------------------------------------------------------------------------------------------------------------- | > | Hash Join (cost=0.10..36.86 rows=1 width=48) | > | Hash Cond: ((l.id1 = &quot;*VALUES*&quot;.column1) AND (l.id2 = &quot;*VALUES*&quot;.column2) AND (l.id3 = &quot;*VALUES*&quot;.column3) AND (l.id4 = &quot;*VALUES*&quot;.column4)) | > | -&gt; Seq Scan on links l (cost=0.00..20.70 rows=1070 width=48) | > | -&gt; Hash (cost=0.04..0.04 rows=3 width=16) | > | -&gt; Values Scan on &quot;*VALUES*&quot; (cost=0.00..0.04 rows=3 width=16) | > </pre> <!-- --> > -- DANGER! With non-unique values, you get more rows than an IN clause would return > select * > from links l > inner join ( > values (1,2,3,4),(1,2,5,4),(1,2,5,4) > ) v(id1, id2, id3, id4) using(id1, id2, id3, id4); > > <pre> > id1 | id2 | id3 | id4 | content > --: | --: | --: | --: | :---------- > 1 | 2 | 3 | 4 | Text > 1 | 2 | 5 | 4 | Other thing > 1 | 2 | 5 | 4 | Other thing > </pre> <!-- --> > -- If you aren't certain, select distinct > select * > from links l > inner join ( > select distinct * > from (values (1,2,3,4),(1,2,5,4),(1,2,5,4)) vi(id1, id2, id3, id4) > ) v using(id1, id2, id3, id4); > > <pre> > id1 | id2 | id3 | id4 | content > --: | --: | --: | --: | :---------- > 1 | 2 | 3 | 4 | Text > 1 | 2 | 5 | 4 | Other thing > </pre> *db<>fiddle [here](https://dbfiddle.uk/?rdbms=postgres_9.6&fiddle=2796bdabfda3931e98da8c7c23030e80)*
back to fiddle