3

I have a table of contacts with phone numbers similar to this:

Name    Phone
Alice   11
Alice   33
Bob     22
Bob     44
Charlie 12
Charlie 55

I can't figure out how to query such a table with LIMITing the rows not just by plain count but by distinct names. For example, if I had a magic LIMIT_BY clause, it would work like this:

SELECT * FROM "Contacts" ORDER BY "Phone" LIMIT_BY("Name") 1

Alice 11
Alice 33
-- ^ only the first contact


SELECT * FROM "Contacts" ORDER BY "Phone" LIMIT_BY("Name") 2

Alice   11
Charlie 12
Alice   33
Charlie 55
-- ^ now with Charlie because his phone 12 goes right after 11. Bob isn't here because he's third, beyond the limit

How could I achieve this result?

In other words, select all rows containing top N distinct Names ordered by Phone

0

3 Answers 3

1

I don't think that PostgreSQL provides any particularly efficient way to do this, but for 6 rows it doesn't need to be very efficient. You could do a subquery to compute which people you want to see, then join that subquery back against the full table.

select * from 
"Contacts" join
(select name from "Contacts" group by name order by min(phone) limit 2) as limited 
using (name)

You could put the subquery in an IN-list rather than a JOIN, but that often performs worse.

Sign up to request clarification or add additional context in comments.

6 Comments

order by min(phone) what if two contacts share the minimal phone number? Is there a way to perform full set comparison?
I don't know what the outcome of a 'full set comparison' would be expected to be. Could you edit the question to include the example data that has this condition, and the desired result?
sqlfiddle.com/#!17/97234/2/0 here I want it to return Alice but it returns Bob
If you prefer Alice because a is before b, then you could order by min(phone), name limit 1 in the subquery to break the tie
I think you can then order by an array_agg, see sqlfiddle.com/#!17/273182/1
|
1

If you want all names that are in the first n rows, you can use in:

select t.*
from t
where t.name in (select t2.name
                 from t t2
                 order by t2.phone
                 limit 2
                );

If you want the first n names by phone:

select t.*
from t
where t.name in (select t2.name
                 from t t2
                 group by t2.name
                 order by min(t2.phone)
                 limit 2
                );

2 Comments

order by t2.phone limit 2 basically leaves me with the two top phone numbers. Not what I need. order by min(t2.phone) what if two contacts have equal min(phone)? Can I perform full set comparison somehow?
@thesame . . . No. These returns all rows that have the same name based on the subquery.
0

try this:

SELECT distinct X.name
,X.phone
FROM (
SELECT *
FROM (
    SELECT name
        ,rn
    FROM (
        SELECT name
            ,phone
            ,row_number() OVER (
                ORDER BY phone
                ) rn
        FROM "Contacts"
        ) AA
    ) DD
WHERE rn <= 2 --rn is the "limit" variable
) EE
,"Contacts" X
WHERE EE.name = X.name

above seems to be working correctly on following dataset:

create table "Contacts" (name text, phone text);
insert into "Contacts" (name, phone) VALUES
 ('Alice', '11'),
 ('Alice', '33'),
 ('Bob', '22'),
 ('Bob', '44'),
 ('Charlie', '13'),
 ('Charlie', '55'),
 ('Dennis', '12'),
 ('Dennis', '66');

3 Comments

Try now, also it would be helpful if you can tell the complete requirements or provide a complete test datasets
There's no complete test datasets, I'm making them on the fly. But I'll try to formulate the requirement: select all rows containing top N distinct Names ordered by Phone.
Your query still returns one Name when I ask for two: sqlfiddle.com/#!17/3a9ab/3/0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.