Reputation: 965
I have a very simple table:
create table mydata (id integer, data character varying(255), a integer, b integer);
insert into mydata values
(1, 'both a and b not found', null, null),
(2, 'a=not found, b=20', null, 20),
(3, 'a=10, b=not found', 10, null),
(4, 'a=10, b=20', 10, 20),
(... more rows with unique combinations of a and b);
I need a query that returns exactly one row for any value of a and b.
If both values for a and b are found, that shall select the specified row (id=4 in this example).
If these values are not found, but there is a row that matches the value of a and has a null in column b, that row shall define the default for this specific a and unknown b. Likewise, if b is found but not a and a row exists with the value of b and a null in column a, that is the default for that situation.
If no such default can be found but a row exists with a null in columns a and b, this row designates the general default.
Finally, in case the row does not exist either, zero rows are returned.
http://sqlfiddle.com/#!17/a5173/5
For now, I have solved the problem by some java code that runs several specific sql queries until it finds a match, but I guess there is a sql way to solve this problem. Please advise.
Upvotes: 1
Views: 1143
Reputation: 1270643
The most efficient method -- assuming an index on (a, b)
-- is probably a more complicated query:
select d.*
from mydata d
where (a is null and b is null) or
(a = 10 and b is null) or
(a is null and b = 20) or
(a = 10 and b = 10)
order by a nulls last, b nulls last
limit 1;
Expanding the where
clause conditions should allow Postgres to use the index for fetching the rows rather than scanning the entire table.
Upvotes: 0
Reputation: 247495
SELECT * FROM mydata
WHERE (coalesce(a, user_a), coalesce(b, user_b)) = (user_a, user_b)
ORDER BY a IS NULL, b IS NULL
LIMIT 1;
Here user_a
and user_b
are the values for which you query.
Upvotes: 5
Reputation: 37477
Hmm, maybe you can first filter the possible candidates and then use a CASE
expression in a ORDER BY
clause to sort the candidates by priority. Pick the top one by a LIMIT
clause.
SELECT *
FROM mydata
WHERE a = :a
AND b = :b
OR a = :a
AND b IS NULL
OR a IS NULL
AND b = :b
OR a IS NULL
AND b IS NULL
ORDER BY CASE
WHEN a = :a
AND b = :b THEN
1
WHEN a = :a
AND b IS NULL THEN
2
WHEN a IS NULL
AND b = :b THEN
3
WHEN a IS NULL
AND b IS NULL THEN
4
END
LIMIT 1;
:a
and :b
denote the values for a
or b
you search for.
Upvotes: 0