#RuPostgresLive
Как писать и читать сложные SQL-запросы
Николай Самохвалов
Twitter: @postgresmen
ru@postgresql.org
2017-11-23
Подготовка
create table post (
id bigserial primary key,
person_id int8 not null,
created_at timestamptz not null,
something text
);
insert into post(user_id, created_at, something)
select
(random() * 10^5)::int8 as person_id, -- ~100k users
now() - interval '1 minute' * (random() * 60 * 24 * 365 * 2) as created_at,
(
select string_agg(substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW XYZ0123456789 ', (random() *
72)::integer + 1, 1), '')
from generate_series(1, 100 + i % 10 + (random() * 2000)::integer)
) as something -- 100-2100 bytes of something
from generate_series(1, 1000000) AS g(i) -- 1 million posts
;
Подготовка
delete from post
where (person_id, created_at) in (
select person_id, created_at
from post
group by person_id, created_at
having count(*) > 1
);
create index i_post_created_at on post using btree(created_at);
create unique index u_post_author_id_created_at on post(person_id, created_at);
create table person as
select distinct on (person_id)
person_id as id,
'person_' || person_id as name
from post
;
alter table person add primary key (id);
alter table post add constraint fk_post_person_id foreign key (person_id) references person(id);
analyze post;
analyze person;
Быстрый count(..). Таблица целиком
select count(*) from post; -- sloooow
explain select * from post; -- see “rows= …” for an estimation
Быстрый count(..). Поиск по индексу
select count(*) from post where person_id = 1;
select count(1) from post where person_id = 1;
Ещё (много интересного) — в статье Joe Nelson “Faster PostgreSQL Counting”
https://gist.github.com/begriffs/67839ff18176d5879e77954bfcd38f1f
https://www.citusdata.com/blog/2016/10/12/count-performance/
Немного про #nooffset
select *
from post
order by id desc
limit 10 offset 100000;
Немного про #nooffset
select *
from post
order by id desc
limit 10 offset 500000;
Немного про #nooffset
select *
from post
order by id desc
limit 10 offset 500000;
select *
from post
where id < :last_id
order by id desc
limit 10;
Как делать OFFSET правильно? Вариант 2
select pl.*
from (
select id
from post
order by id offset 500000
limit 1
) as t,
lateral (
select *
from post
where id >= t.id
order by id
limit 10
) as pl;
Хорошая статья про LATERAL JOIN:
https://medium.com/kkempin/postgresqls-lateral-join-bfd6bd0199df
Top-N постов
select
person.name,
p.*
from person
join lateral (
select *
from post
where person_id = person.id
order by id desc
limit 3
) as p on true
where person.id between 1 and 1000
order by person_id
;
DISTINCT
select distinct person_id
from post;
DISTINCT — recursive CTE
with recursive t as (
-- start from least persno ID
(
select person_id as _person_id
from post
order by 1
limit 1
)
union all
select person_id as _person_id
from t, lateral (
-- find the next person_id > current person_id
select person_id
from post
where person_id > t._person_id
order by 1
limit 1
) as p_id
)
select _person_id
from t;
Полезное
● Очень хорошая презентация о современном SQL и хороший сайт в
целом – Use the Index, Luke!
http://use-the-index-luke.com/blog/2015-02/modern-sql
● Про count(..)
https://gist.github.com/begriffs/67839ff18176d5879e77954bfcd38f1f
● Максим Богук “Как научить слона танцевать Рок-н-ролл”
https://pgday.ru/presentation/232/5964945ea4142.pdf
● Twitter: @postgresmen

#RuPostgresLive 4: как писать и читать сложные SQL-запросы

  • 1.
    #RuPostgresLive Как писать ичитать сложные SQL-запросы Николай Самохвалов Twitter: @postgresmen ru@postgresql.org 2017-11-23
  • 2.
    Подготовка create table post( id bigserial primary key, person_id int8 not null, created_at timestamptz not null, something text ); insert into post(user_id, created_at, something) select (random() * 10^5)::int8 as person_id, -- ~100k users now() - interval '1 minute' * (random() * 60 * 24 * 365 * 2) as created_at, ( select string_agg(substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW XYZ0123456789 ', (random() * 72)::integer + 1, 1), '') from generate_series(1, 100 + i % 10 + (random() * 2000)::integer) ) as something -- 100-2100 bytes of something from generate_series(1, 1000000) AS g(i) -- 1 million posts ;
  • 3.
    Подготовка delete from post where(person_id, created_at) in ( select person_id, created_at from post group by person_id, created_at having count(*) > 1 ); create index i_post_created_at on post using btree(created_at); create unique index u_post_author_id_created_at on post(person_id, created_at); create table person as select distinct on (person_id) person_id as id, 'person_' || person_id as name from post ; alter table person add primary key (id); alter table post add constraint fk_post_person_id foreign key (person_id) references person(id); analyze post; analyze person;
  • 4.
    Быстрый count(..). Таблицацеликом select count(*) from post; -- sloooow explain select * from post; -- see “rows= …” for an estimation
  • 5.
    Быстрый count(..). Поискпо индексу select count(*) from post where person_id = 1; select count(1) from post where person_id = 1; Ещё (много интересного) — в статье Joe Nelson “Faster PostgreSQL Counting” https://gist.github.com/begriffs/67839ff18176d5879e77954bfcd38f1f https://www.citusdata.com/blog/2016/10/12/count-performance/
  • 6.
    Немного про #nooffset select* from post order by id desc limit 10 offset 100000;
  • 7.
    Немного про #nooffset select* from post order by id desc limit 10 offset 500000;
  • 8.
    Немного про #nooffset select* from post order by id desc limit 10 offset 500000; select * from post where id < :last_id order by id desc limit 10;
  • 9.
    Как делать OFFSETправильно? Вариант 2 select pl.* from ( select id from post order by id offset 500000 limit 1 ) as t, lateral ( select * from post where id >= t.id order by id limit 10 ) as pl; Хорошая статья про LATERAL JOIN: https://medium.com/kkempin/postgresqls-lateral-join-bfd6bd0199df
  • 10.
    Top-N постов select person.name, p.* from person joinlateral ( select * from post where person_id = person.id order by id desc limit 3 ) as p on true where person.id between 1 and 1000 order by person_id ;
  • 11.
  • 12.
    DISTINCT — recursiveCTE with recursive t as ( -- start from least persno ID ( select person_id as _person_id from post order by 1 limit 1 ) union all select person_id as _person_id from t, lateral ( -- find the next person_id > current person_id select person_id from post where person_id > t._person_id order by 1 limit 1 ) as p_id ) select _person_id from t;
  • 13.
    Полезное ● Очень хорошаяпрезентация о современном SQL и хороший сайт в целом – Use the Index, Luke! http://use-the-index-luke.com/blog/2015-02/modern-sql ● Про count(..) https://gist.github.com/begriffs/67839ff18176d5879e77954bfcd38f1f ● Максим Богук “Как научить слона танцевать Рок-н-ролл” https://pgday.ru/presentation/232/5964945ea4142.pdf ● Twitter: @postgresmen