PostgreSQL: выполнение запроса для каждой строки и сохранение результата в нем - PullRequest
1 голос
/ 21 июня 2011

Я храню результаты игр за неделю в таблице под названием pref_money :

# select * from pref_money limit 5;
       id       | money |   yw
----------------+-------+---------
 OK32378280203  |   -27 | 2011-44
 OK274037315447 |   -56 | 2011-44
 OK19644992852  |     8 | 2011-44
 OK21807961329  |   114 | 2011-44
 FB1845091917   |   774 | 2011-44
(5 rows)

А для победителей каждой недели я показываю медаль (и):

screenshot

Я нахожу количество медалей для пользователя, набрав:

# select count(id) from (
     select id,
            row_number() over(partition by yw order by money desc) as ranking
     from pref_money
) x
where x.ranking = 1 and id='OK260246921082';
 count
-------
     3
(1 row)

И этот запрос довольно дорогой:

# explain analyze select count(id) from (
    select id,
           row_number() over(partition by yw order by money desc) as ranking
    from pref_money
) x
where x.ranking = 1 and id='OK260246921082';
                                                                QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=18946.46..18946.47 rows=1 width=82) (actual time=2423.145..2423.145 rows=1 loops=1)
   ->  Subquery Scan x  (cost=14829.44..18946.45 rows=3 width=82) (actual time=2400.004..2423.138 rows=3 loops=1)
         Filter: ((x.ranking = 1) AND ((x.id)::text = 'OK260246921082'::text))
         ->  WindowAgg  (cost=14829.44..17182.02 rows=117629 width=26) (actual time=2289.079..2403.685 rows=116825 loops=1)
               ->  Sort  (cost=14829.44..15123.51 rows=117629 width=26) (actual time=2289.069..2319.575 rows=116825 loops=1)
                     Sort Key: pref_money.yw, pref_money.money
                     Sort Method:  external sort  Disk: 4320kB
                     ->  Seq Scan on pref_money  (cost=0.00..2105.29 rows=117629 width=26) (actual time=0.006..22.566 rows=116825 loops=1)
 Total runtime: 2425.001 ms
(9 rows)

Именно поэтому (и поскольку мой веб-сайт испытывает трудности во время пиковых нагрузок, когда в журнале pgbouncer отображается 50 запросов / с), я хотел бы кэшировать это значение и добавить столбец medals в другой таблица - pref_users :

pref=> \d pref_users;
                Table "public.pref_users"
   Column   |            Type             |   Modifiers
------------+-----------------------------+---------------
 id         | character varying(32)       | not null
 first_name | character varying(32)       |
 last_name  | character varying(32)       |
 female     | boolean                     |
 avatar     | character varying(128)      |
 city       | character varying(32)       |
 lat        | real                        |
 lng        | real                        |
 login      | timestamp without time zone | default now()
 last_ip    | inet                        |
 medals     | smallint                    | default 0
 logout     | timestamp without time zone |
Indexes:
    "pref_users_pkey" PRIMARY KEY, btree (id)
Check constraints:
    "pref_users_lat_check" CHECK ((-90)::double precision <= lat AND lat <= 90::double precision)
    "pref_users_lng_check" CHECK ((-90)::double precision <= lng AND lng <= 90::double precision)
    "pref_users_medals_check" CHECK (medals >= 0)

Я хотел бы создать cronjob, который будет запускаться каждые 15 минут, чтобы обновить этот столбец для всех пользователей в таблице pref_users :

*/15       *       *       *       *       psql -a -f $HOME/bin/medals.sql

Как видите, у меня почти все на месте. Моя проблема в том, что я еще не предложил оператор SQL для обновления столбца medals .

Любая помощь, пожалуйста?

Я использую PostgreSQL 8.4.8 с CentOS Linux 5.6 / 64 bit.

Спасибо! Alex

1 Ответ

1 голос
/ 22 июня 2011

Ну, разве это не приведет к получению идентификаторов пользователей и количества медалей?

create view user_medal_count as
select id, count(*) as medals from (
     select id,
            row_number() over(partition by yw order by money desc) as ranking
     from pref_money
) x
where x.ranking = 1
group by id

Так что вам нужно использовать это как источник для обновления ваших пользователей:

update pref_users
set medals = user_medal_count.medals
from user_medal_count
where pref_users.id = user_medal_count.id
      and (pref_users.medal_count is null
           or pref_users.medal_count <> user_medal_count.medal_count)

Я надеюсь, что вы начали.

Осталось рассмотреть вопросы. Возможно, вы захотите определить, в какой момент пользователь награждается медалью - медаль за «текущую неделю» предположительно может быть изменена, поэтому вы можете определить количество медалей как стабильный счет предыдущего медали недели, вычислите медаль текущей недели на лету (что должно потребовать рассмотрения гораздо меньшего количества данных), или просто исключите ее. (Если вы ничего не делаете, то вы можете обнаружить, что пользователи получают значение medal_count, равное 1, если они временно получают медаль текущей недели, но это никогда не будет сброшено до 0, если впоследствии оно будет передано кому-то другому).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...