web-dev-qa-db-ja.com

Postgresqlのサブクエリで使用される完全なテーブルでのグループ化を回避するために、サブクエリで外部制限オフセットとフィルターを適用する方法

次のようなレガシーテーブルがあります。

従業員

------------------------------------
|  employee_id  |   name    
------------------------------------
|  1            |   David    
|  2            |  Mathew  
------------------------------------

給与

-------------------------------------
|  employee_id  | salary      
-------------------------------------
|   2           |  200000    
|   3           |  90000      
-------------------------------------

結合とフィルターの後で、次のデータを取得したいと思います。

-----------------------------------------------------------
|  address_id  | employee_id  |  address        
-----------------------------------------------------------
|   1          |  2           |  street 1, NY  
|   2          |  2           |  street 2, DC  
------------------------------------------------------------

次のクエリがあります。

SELECT employee_id, salary, address_arr 
FROM employee 
LEFT JOIN payroll on payroll.employee_id = employee.employee_id
INNER JOIN 
     (
       SELECT employee_id, ARRAY_AGG(address) as address_arr 
       FROM addresses 
       GROUP BY employee_id
     ) table_address ON table_address.employee_id = employee.employee_id
WHERE employee.employee_id < 1000000
LIMIT 100
OFFSET 0

上記のクエリは目的の出力を提供しますが、JOINオペレーションで外部クエリに使用される前に、完全なアドレステーブルに対してGROUP BYオペレーションが発生するため、非常に最適化されていません。

親切に答えてください:

  1. 外部クエリのGROUP BYを使用して、アドレステーブル全体でLIMIT OFFSET操作が発生しないようにするにはどうすればよいですか?
  2. 条件WHERE employee.employee_id < 1000000は、内部クエリのGROUP BY操作の前または後にサブクエリに適用されますか。 GROUP BYの後に条件が適用される場合、どうすればそれを回避できますか?

注:使用されている実際のクエリには、複数のJOINsとサブクエリがあります。

1

これが本当に効率的かどうかはわかりませんが、制限を適用する派生テーブルに結合しようとすることができます。

select emp.employee_id, emp.salary, adr.address_arr 
from (
  SELECT employee_id, salary, address_arr 
  FROM employee 
    LEFT JOIN payroll on payroll.employee_id = employee.employee_id
  WHERE employee.employee_id < 1000000
  LIMIT 100
  OFFSET 0
) as emp
  JOIN (
     SELECT a.employee_id, ARRAY_AGG(a.address) as address_arr 
     FROM addresses a
     GROUP BY employee_id
  ) as adr ON adr.employee_id = emp.employee_id;

最初の派生テーブルは100行のみを選択し、結合/グループ化はそれらの100人の従業員に対してのみ実行されます。

オプティマイザがプッシュダウンしない場合は、代わりにラテラル結合を試行してプッシュダウンを「強制」できます。

select emp.employee_id, emp.salary, adr.address_arr 
from (
  SELECT employee_id, salary, address_arr 
  FROM employee 
    LEFT JOIN payroll on payroll.employee_id = employee.employee_id
  WHERE employee.employee_id < 1000000
  LIMIT 100
  OFFSET 0
) as emp
  LATERAL JOIN (
     SELECT a.employee_id, ARRAY_AGG(a.address) as address_arr 
     FROM addresses a
     WHERE a.employee_id = emp.employee_id
     GROUP BY employee_id
  ) as adr ON adr.employee_id = emp.employee_id;

結合条件は実際には必要ありませんが、害はありません

私はこれが初めてで、いくつかの助けがありました、これが私が思いついたものです:

with t as (SELECT employee.employee_id, salary  FROM employee  LEFT JOIN payroll on payroll.employee_id = employee.employee_id WHERE employee.employee_id < 1000000 LIMIT 100 OFFSET 0)
select t.employee_id, max(t.salary), ARRAY_AGG(address) as address_arr from address left join t on address.employee_id = t.employee_id where address.employee_id = t.employee_id group by t.employee_id;

収量の分析について説明する

HashAggregate  (cost=443931.83..443933.08 rows=100 width=44) (actual time=3173.705..3173.705 rows=1 loops=1)
   Group Key: t.employee_id
   CTE t
     ->  Limit  (cost=313.99..316.90 rows=100 width=12) (actual time=7.441..7.511 rows=100 loops=1)
           ->  Hash Right Join  (cost=313.99..145616.13 rows=4983381 width=12) (actual time=7.437..7.490 rows=100 loops=1)
                 Hash Cond: (payroll.employee_id = employee.employee_id)
                 ->  Seq Scan on payroll  (cost=0.00..76778.79 rows=4983879 width=12) (actual time=0.036..0.047 rows=100 loops=1)
                 ->  Hash  (cost=189.00..189.00 rows=9999 width=4) (actual time=7.370..7.370 rows=10000 loops=1)
                       Buckets: 16384  Batches: 1  Memory Usage: 480kB
                       ->  Seq Scan on employee  (cost=0.00..189.00 rows=9999 width=4) (actual time=0.016..3.783 rows=10000 loops=1)
                             Filter: (employee_id < 1000000)
   ->  Hash Join  (cost=3.25..441981.29 rows=217817 width=37) (actual time=7.699..3119.096 rows=200000 loops=1)
         Hash Cond: (address.employee_id = t.employee_id)
         ->  Seq Scan on address  (cost=0.00..364792.00 rows=20002100 width=29) (actual time=0.067..1568.256 rows=20000000 loops=1)
         ->  Hash  (cost=2.00..2.00 rows=100 width=12) (actual time=7.609..7.609 rows=100 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 13kB
               ->  CTE Scan on t  (cost=0.00..2.00 rows=100 width=12) (actual time=7.449..7.580 rows=100 loops=1)
 Planning time: 0.456 ms
 Execution time: 3174.499 ms
(19 rows)

enter image description here

1
Rahul Ahuja

多分

SELECT employee.employee_id, payroll.salary, ARRAY_AGG(addresses.address)
FROM employee 
INNER JOIN addresses ON addresses.employee_id = employee.employee_id
LEFT JOIN payroll on payroll.employee_id = employee.employee_id
WHERE employee.employee_id < 1000000
GROUP BY employee.employee_id
LIMIT 100
OFFSET 0

そして-あなたは本当にpayroll.salaryのNULLにつながるpayrollテーブルの適切なレコードがないレコードで必要ですか?多分、INNER JOINで十分ですか?

1
Akina