1

I am working with PostgreSQL.

My idea is to group players who appear more than 3 times in table-a.

table-a = <player_id, year_world, c, d, e>

With the first subquery I get player_id and the number of times that player played a world cup. And with the NATURAL JOIN I recover the year or other attributes in the tuples that player appears.

The following query works but I want to optimize it:

SELECT player_id , year_world
FROM    (SELECT player_id , count(player_id ) player_id_count
        FROM <table-a> 
        GROUP BY player_id 
        HAVING count(player_id ) > 3) playersMoreThree
NATURAL JOIN <table-a>

I would like to know if it is possible not to use the NATURAL JOIN to obtain the other attributes of the table. Because when I put the rest of the attributes I want to project into SELECT and in theGROUP BY I get an empty table.

Keep in mind that the attribute that is added to the SELECT of the subquery of the GROUP BY has to be determined by the attribute that we are grouping. That is, for each player_id value, I can always have the same value of the new attribute, if for a same value of player_id I have different values of the new attribute, this is not determined. For add an attribute that is not correlated by what group I use NATURAL JOIN or another solution.

The following query without NATURAL JOIN does not work for me:

SELECT player_id, count(player_id ) player_id_count, year_world, c, d, e
FROM <table-a> 
GROUP BY player_id, year_world, c, d, e
HAVING count(player_id ) > 3

What would be the most efficient way in DML?

7
  • 1
    Put the rest of the attributes in the SELECT list only of the main query. The GROUP BY subquery should remain unchanged. Commented Jul 19, 2018 at 6:59
  • 1
    Unrelated, but: natural join should be avoided, you should explicitly state your join columns.
    – user1822
    Commented Jul 19, 2018 at 7:01
  • @ypercubeᵀᴹ Could you write me how the consultation would be? Because if I add the year_world attribute in the SELECT of the subquery the server asks me to also go in the GROUP BY; On the other hand, if I add the attribute in the external query, I need the NATURAL JOIN or JOIN conditional, otherwise the 'attribute does not exist' Commented Jul 19, 2018 at 7:16
  • @a_horse_with_no_name thank you, from now on I will cross the tables with explicit conditions: JOIN ... ON Commented Jul 19, 2018 at 7:17
  • 1
    If the question is how to do this in the most efficient way, then please edit the question and add the CREATE TABLE statement and all the indexes on the table. Commented Jul 19, 2018 at 7:43

2 Answers 2

2

You can simply add more columns in the SELECT of the main query, without changing the subquery at all:

SELECT player_id , year_world, c, d, e                   -- all columns
FROM    (SELECT player_id , count(player_id ) player_id_count
        FROM <table-a> 
        GROUP BY player_id 
        HAVING count(player_id ) > 3) playersMoreThree
NATURAL JOIN <table-a>

Whether you do a NATURAL join or a JOIN .. ON doesn't matter for efficiency (but using a LATERAL join may do matter).

The other common method would be using window functions in a subquery or CTE and then restricting the result with a WHERE in the main query - exactly what Lennart's answer does.

In any case, the problem needs some kind of subquery to be solved.

For efficiency, an index on (player_id) is very likely to help, with either method you write the query. Memory settings, like work_mem should also be examined and can affect performance. Optimal settings and efficiency depend on many things (size of the tables, data distribution, RAM size of the whole server, number of connections, version of Postgres, etc.)

It could very well be that the solution with the join on the GROUP BY result (or the LATERAL variation) is more efficient than the solution using window functions. This depends largely on your data distribution and the indexes available. You have to compare the execution plan for all methods.

5
  • I think he wants to avoid the self-JOIN. Commented Jul 19, 2018 at 7:38
  • I know, but my idea is to eliminate the subquery and the NATURAL JOIN Commented Jul 19, 2018 at 7:38
  • @Lennart ah. I read the question as " it is possible to use the NATURAL JOIN ..." Commented Jul 19, 2018 at 8:15
  • 1
    @ITDevelopers: it could very well be that the solution with the join on the GROUP BY result is more efficient than the solution using window functions. This depends largely on your data distribution and the indexes available. You have to compare the execution plan for both solutions.
    – user1822
    Commented Jul 19, 2018 at 8:23
  • @a_horse_with_no_name thnx. Added in the answer. Commented Jul 19, 2018 at 8:29
3

You can use a window function:

SELECT player_id , ... FROM (
    SELECT player_id, year_world, ...
         , COUNT(*) over (PARTITION BY player_id) AS player_id_count
    FROM <table-a> 
) AS T
WHERE player_id_count > 3

over (partition by player_id) defines the window where count(*) should be applied. In this case, we want the window to be all rows for the player in question. This means that you will get the count for player_id on all rows with player_id.

4
  • I still can not vote but this works. Thank you. Why do you use the first FROM in lowercase? Commented Jul 19, 2018 at 7:24
  • No particular reason. SQL is case insensitive so it does not matter, but I'll update it. Commented Jul 19, 2018 at 7:25
  • I knew it but I thought you used some convention. Is there any way to do the same using GROUP BY or HAVING in a performant way? Commented Jul 19, 2018 at 7:27
  • Not really. The GROUP BY clause needs to functionally determine the SELECT clause, so you'll lose some attributes in your new relation. Commented Jul 19, 2018 at 7:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.