powered by simpleCommunicator - 2.0.51     © 2025 Programmizd 02
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Вложенные запросы или как правильно это сделать?
48 сообщений из 48, показаны все 2 страниц
Вложенные запросы или как правильно это сделать?
    #39964278
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Имеется три таблицы, основная и две подчинённые. При этом в приложении необходимо отображать данные из основной и несколько вычисляемых столбцов на основе подчинённых.

Примерно так:

Таблицы: A - основная, B и C - подчинённые.

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
     A     |     B     |     C    
 ID      | ID      | ID
 ...     | ID_A    | ID_A
 ...     | DATE    | SOMEVAL 
 ...     | NUM1    | ...
 ...     | NUM2    | ...
 ...     | ...     | ...

Нужно отобразить в сводной таблице для каждой записи из A:
  • все столбцы из A
  • две суммы по двум столбцам из B по некоторому условию (ключ и дата, например)
  • минимальное значение по ключу из C.
Код: plaintext
1.
2.
3.
4.
5.
 Результат:                                                                                                         
A.ID1, ..., SUM(NUM1), SUM(NUM2) [WHERE B.ID_A = A.ID1 AND DATE BETWEEN ....], MIN(SOMEVAL) [WHERE C.ID_A = A.ID1]
A.ID2, ..., SUM(NUM1), SUM(NUM2) [WHERE B.ID_A = A.ID2 AND DATE BETWEEN ....], MIN(SOMEVAL) [WHERE C.ID_A = A.ID2]
A.ID3, ..., SUM(NUM1), SUM(NUM2) [WHERE B.ID_A = A.ID3 AND DATE BETWEEN ....], MIN(SOMEVAL) [WHERE C.ID_A = A.ID3]
...
Вопрос: как это сделать правильно и чтобы работало максимально быстро?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964282
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ответ зависит от многих факторов, включая версию Firebird, размеры таблиц и т.д. и т.п.

Ну и было бы неплохо посмотреть на твои собственные попытки, их планы и статистику.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964284
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Dimitry Sibiryakov

Ответ зависит от многих факторов, включая версию Firebird, размеры таблиц и т.д. и т.п.

Версия 3.0, таблицы... ну пока небольшие - до десятков тысяч записей.
Dimitry Sibiryakov

Ну и было бы неплохо посмотреть на твои собственные попытки, их планы и статистику.
Я пока ещё не делал ничего, т.к. жалко будет убить время на тупиковый путь. Мне не код нужен, а направление в котором двигаться.

Например в данный момент таблицы B ещё нет, а для таблицы C есть поле в A, которое обновляется по триггеру при изменении C.
Но мне кажется это не совсем правильным решением, особенно для новых условий.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964288
Фотография kdv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp,

а чего не a join b join c ? left по вкусу, в зависимости от наличия записей в b и c соответствующих a.

в sql можно по всякому делать. "вложенные запросы" - это обычно от неумения join.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964336
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvpМне не код нужен, а направление в котором двигаться.

Все направления работоспособны при определённых условиях:
1) Простой JOIN + GROUP.
2) JOIN + DERIVED TABLE с GROUP внутри
3) SUBQUERY
4) Хранимые агрегаты.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964355
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Привет.

alekcvp
Имеется три таблицы, основная и две подчинённые
  • все столбцы из A
  • две суммы по двум столбцам из B по некоторому условию (ключ и дата, например)
  • минимальное значение по ключу из C.
Я бы для таблиц B и С агрегатные вычисления оформил бы в виде CTE, которые заджоинил к таблице A.
И все это оформил бы, как VIEW.
Основной вопрос - какой должен быть join (left или inner)?
Могут ли FK-поля в таблицах B и C принимать значение NULL?
Должны ли в результате запроса присутствовать записи из A, ссылки на которые отсутствуют в B и/или C?

С уважением, Polesov.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964380
KreatorXXI
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp,

скорее всего обычным джойном такое не получится. Агрегаты с разными условиями так не сделать. Можно ещё рассмотреть оконные функции, в них агрегаты настраиваются по условию. Совсем просто - это подзапросы по таблицам B и С. Но может оказаться перепробег, т.е. по одним и тем же данным будет несколько проходов. На первый взгляд это Ваш случай - сумма по таблице B, частичная сумма по таблице B... По-моему, оконные для этого - полная выборка и агрегаты по разным условиям.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964428
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov

Я бы для таблиц B и С агрегатные вычисления оформил бы в виде CTE, которые заджоинил к таблице A.

CTE?
Polesov

Могут ли FK-поля в таблицах B и C принимать значение NULL?
Не могут.
Polesov

Должны ли в результате запроса присутствовать записи из A, ссылки на которые отсутствуют в B и/или C?
Должны быть все записи из А.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964429
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kdv
а чего не a join b join c ? left по вкусу, в зависимости от наличия записей в b и c соответствующих a.
Я не знал что в обычных запросах можно делать аггрегатные join, причём с условиями...
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964433
zeon11
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp,

Начни в лоб:
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
select 
   a.ID,
   a.Name,
   (select sum(b.summ) from TableB b where b.ID_A=a.ID and 1=1) SumB,
   (select count(b.ID)    from TableB b where b.ID_A=a.ID and 1=1) CountB,
   (select sum(c.summ) from TableC c where c.ID_A=a.ID and 1=1) SumC,
   (select count(c.ID)    from TableC c where c.ID_A=a.ID and 1=1) CountC

from TableA a 
where 1=1



на нескольких десятках тысяч записей будет отрабатываться очень быстро,
по крайней мере, прототип приложения отладишь, а там и новые идеи придут.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964439
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov,

Что-то типа такого?
Код: sql
1.
2.
3.
4.
5.
6.
with 
  t_in as (select id_a1 as id, sum(num1) as sum1 from b where date between .... group by id_a1),
  t_out as (select id_a2 as id, sum(num2) as sum2 from b where date between .... group by id_a2)
select * from a 
  left join t_in on a.id = t_in.id 
  left join t_out on a.id = t_out.id
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964463
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov,

Что-то типа такого?
Код: sql
1.
2.
3.
4.
5.
6.
with 
  t_in as (select id_a1 as id, sum(num1) as sum1 from b where date between .... group by id_a1),
  t_out as (select id_a2 as id, sum(num2) as sum2 from b where date between .... group by id_a2)
select * from a 
  left join t_in on a.id = t_in.id 
  left join t_out on a.id = t_out.id



Агрегат по В можно делать в одной CTE.

Я тут интереса ради смакетировал свое предложение (скрипт под спойлером).

/* TABLE A ****************************************/
create table A (
ID integer not null,
VAL varchar(32)
);

alter table A
add constraint PK_A
primary key ( ID );

/* TABLE B ****************************************/
create table B (
ID integer not null,
ID_A integer not null,
SUM1 float,
SUM2 float,
FIX date
);

alter table B
add constraint PK_B
primary key ( ID );

alter table B
add constraint FK_B_A
foreign key ( ID_A )
references A ( ID );

create index IDX_B_FIX on B ( FIX );

/* TABLE C ****************************************/
create table C (
ID integer not null,
ID_A integer not null,
MIN1 float
);

alter table C
add constraint PK_C
primary key ( ID );

alter table C
add constraint FK_C_A
foreign key ( ID_A )
references A ( ID );

create index IDX_C_MIN1 on C ( MIN1 );

/* VIEW V_ABC *************************************/
create view V_ABC (
ID,
VAL,
SUM_1,
SUM_2,
MIN_1 )
as
with
CTE_B ( ID_A, SUM_1, SUM_2 ) as
( select ID_A,
sum( SUM1 ) as SUM_1,
sum( SUM2 ) as SUM_2
from B
where FIX between '01-JAN-2020' and '31-DEC-2020'
group by ID_A ),

CTE_C ( ID_A, MIN_1 ) as
( select ID_A,
min( MIN1 ) as MIN_1
from C
group by ID_A )

select a.ID,
a.VAL,
cte_b.SUM_1,
cte_b.SUM_2,
cte_c.MIN_1
from A
join CTE_B on ( cte_b.ID_A = a.ID )
join CTE_C on ( cte_c.ID_A = a.ID )
;

/* FILL DATA **************************************/

set term ^ ;

execute block
as
declare variable ID_A integer;
declare variable ID_B integer;
declare variable ID_C integer;
declare variable XX_N integer;
begin
ID_A = 0;
ID_B = 0;
ID_C = 0;
while ( :ID_A < 100000 ) do
begin
ID_A = :ID_A + 1;

insert into A (
ID,
VAL )
values (
:ID_A,
'VAL#' || :ID_A );

XX_N = 0;
while ( :XX_N < 5 ) do
begin
ID_B = :ID_B + 1;
insert into B (
ID,
ID_A,
SUM1,
SUM2,
FIX )
values (
:ID_B,
:ID_A,
:ID_B / 0.1,
:ID_B / 0.3,
'01-JUL-2020' );
XX_N = :XX_N + 1;
end

XX_N = 0;
while ( :XX_N < 7 ) do
begin
ID_C = :ID_C + 1;
insert into C (
ID,
ID_A,
MIN1 )
values (
:ID_C,
:ID_A,
:ID_C / 0.7 );
XX_N = :XX_N + 1;
end
end
end ^

set term ; ^

commit;

A - 100000 записей.
B - 500000 записей.
C - 700000 записей.

Ну, что сказать, как и ожидалось:
inner join - выполняется быстро (select count(*) from V_ABC с inner join ~0.9 sec на Cor-i7 3.6 HGz + SSD )
left join - уш-да-уш ... ((
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964464
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov

Агрегат по В можно делать в одной CTE.

Я в исходном посте упустил, что в B два ключа для A и суммы, соответственно, считаются по разным.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964469
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov

Агрегат по В можно делать в одной CTE.

Я в исходном посте упустил...

Ну, это уже не столь существенно.
А вот если все-же требование на left join , я бы рассмотрел возможность изменения схемы выборки.
Уж больно left join в данной схеме тормозной.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964471
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Должны быть все записи из А.

Возможна ли ситуация, когда в А есть записи, на которые нет ссылок из С и из B по обеим FK?
Если да, то требуется left join, а в моем варианте это, мягко говоря, не очень быстрый вариант.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964474
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
alekcvp
Должны быть все записи из А.

Возможна ли ситуация, когда в А есть записи, на которые нет ссылок из С и из B по обеим FK?
Если да, то требуется left join, а в моем варианте это, мягко говоря, не очень быстрый вариант.

В C для каждой записи из A должна быть минимум одна запись, а вот в B - не обязательно, плюс там date between будет, под который может ни одна запись не попадать.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964475
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Изначально предусмотрено что ...

Надеюсь, идея и ее плюсы и минусы, понятны.
Далее надо смотреть по месту.
На мой взгляд, странно иметь в таблице B 2 FK на таблицу A - это безусловное применение let join.
Впрочем, сложно что-то советовать, не зная предметной области.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964476
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
плюс там date between будет, под который может ни одна запись не попадать.

Это условие на inner/left join не влияет.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964482
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
На мой взгляд, странно иметь в таблице B 2 FK на таблицу A - это безусловное применение let join.
Впрочем, сложно что-то советовать, не зная предметной области.

Хм... чё-то я задумался... возможно стоит таблицу B разбить на две... но всё равно в этих B1 и B2 может не быть записей для A, т.е. никуда от left join не деться.
Может как-то индексами помочь?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964486
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov
На мой взгляд, странно иметь в таблице B 2 FK на таблицу A - это безусловное применение let join.
Впрочем, сложно что-то советовать, не зная предметной области.

Хм... чё-то я задумался... возможно стоит таблицу B разбить на две... но всё равно в этих B1 и B2 может не быть записей для A, т.е. никуда от left join не деться.
Может как-то индексами помочь?

А обязательно данные получать в одном рекорд-сете?
Может быть, для отображения данных на клиенте, дешевле сделать master-detail?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964492
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
А обязательно данные получать в одном рекорд-сете?
Может быть, для отображения данных на клиенте, дешевле сделать master-detail?

По ТЗ - обязательно... но в таблице A вряд ли будет больше 100000 записей.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964501
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov
А обязательно данные получать в одном рекорд-сете?
Может быть, для отображения данных на клиенте, дешевле сделать master-detail?

По ТЗ - обязательно...

Для обеспечения inner join можно рассмотреть вариант создания в B и C фиктивных записей,
что бы каждой записи из A было соответствие в B и C, но это уже, на мой взгляд, костыль.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964513
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
Для обеспечения inner join можно рассмотреть вариант создания в B и C фиктивных записей,
что бы каждой записи из A было соответствие в B и C, но это уже, на мой взгляд, костыль.

ИМХО проще тогда сделать эти поля в A и при изменении записей в B и C триггером это поле обновлять...
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964515
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp,
А связь A к B и A к C один к одному?
Если нет, то этот способ еще хуже.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964522
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
alekcvp,
А связь A к B и A к C один к одному?
Если нет, то этот способ еще хуже.

Нет, один ко многим.
А почему хуже? По триггеру пересчитать одно поле одной записи вроде не должно занимать много времени?

Почему всё-таки Left Join так катастрофически влияет на производительность?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964524
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov,

Это забавно.
Структура БД
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
CREATE TABLE A (
    ID INTEGER NOT NULL,
    VAL VARCHAR(32) CHARACTER SET UTF8);


CREATE TABLE B1 (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    NUM INTEGER,
    FIX DATE);


CREATE TABLE B2 (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    NUM INTEGER,
    FIX DATE);


CREATE TABLE C (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    FLAG SMALLINT);

ALTER TABLE A ADD CONSTRAINT PK_A PRIMARY KEY (ID);
ALTER TABLE B1 ADD CONSTRAINT PK_B1 PRIMARY KEY (ID);
ALTER TABLE B2 ADD CONSTRAINT PK_B2 PRIMARY KEY (ID);
ALTER TABLE C ADD CONSTRAINT PK_C PRIMARY KEY (ID);

ALTER TABLE B1 ADD CONSTRAINT FK_B1_A FOREIGN KEY (ID_A) REFERENCES A (ID);
ALTER TABLE B2 ADD CONSTRAINT FK_B2_A FOREIGN KEY (ID_A) REFERENCES A (ID);
ALTER TABLE C ADD CONSTRAINT FK_C_A FOREIGN KEY (ID_A) REFERENCES A (ID);

CREATE INDEX IDX_B1_FIX ON B1 (FIX);
CREATE INDEX IDX_B2_FIX ON B2 (FIX);
CREATE INDEX IDX_C_FLAG ON C (FLAG);



Запрос
Код: plsql
1.
2.
3.
4.
5.
6.
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2,
   (SELECT MIN(C.FLAG) FROM C WHERE C.ID_A=A.ID) FLAG
FROM A

отрабатывает за 3.5 секунды, при Count(*) A = 100К, B1 = 400K, B2 = 700K, C = 150K...

Как блин?.. И можно ли это сделать ещё быстрее? :)
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964555
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В общем на данный момент самым оптимальным запросом под мои задачи, из тех что я смог придумать и протестировать, оказался такой:
Код: sql
1.
2.
3.
4.
5.
6.
7.
WITH
  FLAGS AS (SELECT ID_A, MIN(FLAG) AS FLAG FROM C GROUP BY ID_A)
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A = A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A = A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2
FROM A INNER JOIN FLAGS F ON F.ID_A = A.ID


Код: plaintext
1.
2.
3.
План:
PLAN (B2 INDEX (FK_B2_A, IDX_B2_FIX))
PLAN (B1 INDEX (FK_B1_A, IDX_B1_FIX))
PLAN JOIN (F C ORDER FK_C_A, A INDEX (PK_A))

Вопрос: добавил к B2 ещё один внешний ключ к B1 (т.к. они всё-таки взаимосвязаны). Запрос стал выполняться на 60% дольше (было 3.5, стало 5.2 секунды)... хотя этот ключ непосредственно в запросе не используется. Это можно как-то нивелировать?

Также приветствуются любые советы по оптимизации.

Если вдруг кому интересно, то бэкап тестовой базы с тестовыми данными тут (~10 Мб, сама база ~200 Мб).
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964630
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov
alekcvp,
А связь A к B и A к C один к одному?
Если нет, то этот способ еще хуже.

Нет, один ко многим.
А почему хуже? По триггеру пересчитать одно поле одной записи вроде не должно занимать много времени?

Я имел ввиду, что хуже потому, что усложнится логика.
20578934 )))
Например: добавлена запись в A. В таблице С соответствующих записей нет.
Что произойдет в триггере, если в таблицу С будет вставлена запись?
Что-то типа
Код: sql
1.
update А set MIN_FLAG = ( select min( FLAG ) from C where ID_A = new.ID_A );

И аналогичные действия при апдейте и удалении записи из таблицы C?

Хотя, согласен - в принципе, схема работоспособная.
Ну, и не допускать ошибочной деактивации триггера )
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964635
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov,

Это забавно.
Структура БД
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
CREATE TABLE A (
    ID INTEGER NOT NULL,
    VAL VARCHAR(32) CHARACTER SET UTF8);


CREATE TABLE B1 (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    NUM INTEGER,
    FIX DATE);


CREATE TABLE B2 (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    NUM INTEGER,
    FIX DATE);


CREATE TABLE C (
    ID INTEGER NOT NULL,
    ID_A INTEGER NOT NULL,
    FLAG SMALLINT);

ALTER TABLE A ADD CONSTRAINT PK_A PRIMARY KEY (ID);
ALTER TABLE B1 ADD CONSTRAINT PK_B1 PRIMARY KEY (ID);
ALTER TABLE B2 ADD CONSTRAINT PK_B2 PRIMARY KEY (ID);
ALTER TABLE C ADD CONSTRAINT PK_C PRIMARY KEY (ID);

ALTER TABLE B1 ADD CONSTRAINT FK_B1_A FOREIGN KEY (ID_A) REFERENCES A (ID);
ALTER TABLE B2 ADD CONSTRAINT FK_B2_A FOREIGN KEY (ID_A) REFERENCES A (ID);
ALTER TABLE C ADD CONSTRAINT FK_C_A FOREIGN KEY (ID_A) REFERENCES A (ID);

CREATE INDEX IDX_B1_FIX ON B1 (FIX);
CREATE INDEX IDX_B2_FIX ON B2 (FIX);
CREATE INDEX IDX_C_FLAG ON C (FLAG);



Запрос
Код: plsql
1.
2.
3.
4.
5.
6.
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2,
   (SELECT MIN(C.FLAG) FROM C WHERE C.ID_A=A.ID) FLAG
FROM A

отрабатывает за 3.5 секунды, при Count(*) A = 100К, B1 = 400K, B2 = 700K, C = 150K...

Как блин?.. И можно ли это сделать ещё быстрее? :)

Я уже предлагал способ - обеспечить в таблице A наличие фиктивных записей, подзпаросы оформить в виде CTE и использовать inner join.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964639
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov,
> обеспечить в таблице A наличие фиктивных записей
обеспечить для таблицы A наличие фиктивных записей
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964669
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Polesov,
Запрос
Код: plsql
1.
2.
3.
4.
5.
6.
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2,
   (SELECT MIN(C.FLAG) FROM C WHERE C.ID_A=A.ID) FLAG
FROM A

отрабатывает за 3.5 секунды, при Count(*) A = 100К, B1 = 400K, B2 = 700K, C = 150K...

А как измерял?
Я окончания полного фетча для этого запроса ждал 2 минуты - не дождался.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964679
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
В общем на данный момент самым оптимальным запросом под мои задачи, из тех что я смог придумать и протестировать, оказался такой:
Код: sql
1.
2.
3.
4.
5.
6.
7.
WITH
  FLAGS AS (SELECT ID_A, MIN(FLAG) AS FLAG FROM C GROUP BY ID_A)
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A = A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A = A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2
FROM A INNER JOIN FLAGS F ON F.ID_A = A.ID

1. Индекс по FIX в данном случае только мешает, его нужно либо отключить, либо добавить в него ID_A
Код: sql
1.
2.
3.
4.
5.
6.
7.
WITH
  FLAGS AS (SELECT ID_A, MIN(FLAG) AS FLAG FROM C GROUP BY ID_A)
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A = A.ID AND FIX+0 BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A = A.ID AND FIX+0 BETWEEN '10.05.2020' AND '20.05.2020') SUM2
FROM A INNER JOIN FLAGS F ON F.ID_A = A.ID



2. Т.к. полей из С в результате нет, то можно избавиться от агрегата с ней
Код: sql
1.
2.
3.
4.
5.
6.
SELECT
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A = A.ID AND FIX+0 BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A = A.ID AND FIX+0 BETWEEN '10.05.2020' AND '20.05.2020') SUM2
  FROM A
 WHERE EXISTS (SELECT * FROM C WHERE C.ID_A = A.ID)
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964712
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad

2. Т.к. полей из С в результате нет

Думаю, ТС, приводя запрос, просто ошибся.
В исходном посте сказано, что из таблицы C надо выводить min( FLAG ).

Кстати, есть ли существенные минусы в добавлении фиктивных записей в дочерние таблицы для обеспечения использования inner join?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964718
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
SELECT ID_A, MAX(SUM1), MAX(SUM2), MAX(FLAG)
FROM
(
SELECT
    ID_A,
    SUM(NUM) SUM1,
    NULL SUM2,
    NULL FLAG
FROM B1 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
GROUP BY ID_A
UNION ALL
SELECT
    ID_A,
    NULL,
    SUM(NUM),
    NULL
FROM B2 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
GROUP BY ID_A
UNION ALL
SELECT
    ID_A,
    NULL,
    NULL,
    MIN(C.FLAG)
FROM C
GROUP BY ID_A
) B1B2C
LEFT JOIN A ON A.ID = B1B2C.ID_A
GROUP BY ID_A
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964726
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov
alekcvp
Polesov,
Запрос
Код: plsql
1.
2.
3.
4.
5.
6.
SELECT 
   A.ID,
   (SELECT SUM(B1.NUM) FROM B1 WHERE B1.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM1,
   (SELECT SUM(B2.NUM) FROM B2 WHERE B2.ID_A=A.ID AND FIX BETWEEN '10.05.2020' AND '20.05.2020') SUM2,
   (SELECT MIN(C.FLAG) FROM C WHERE C.ID_A=A.ID) FLAG
FROM A

отрабатывает за 3.5 секунды, при Count(*) A = 100К, B1 = 400K, B2 = 700K, C = 150K...

А как измерял?
Я окончания полного фетча для этого запроса ждал 2 минуты - не дождался.

А запрос во время выполнения разве не все данные считает, а тоже порциями? Я имел в виду время выполнения самого запроса.
Фетч-то на 100к записей может и сам по себе долго выполняться, нет?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964729
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad

2. Т.к. полей из С в результате нет, то можно избавиться от агрегата с ней
Пропустил F.FLAG в запросе :(
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964733
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke,

Охренеть. Спасибо!
Я так понимаю этот запрос разворачивает все три таблицы в одну, а потом тупо строит по ней аггрегатный запрос?
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964745
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke,

нужно ещё HAVING MAX(FLAG) IS NOT NULL добавить, иначе запрос не эквивалентен.

А вот так будет ещё немножко лучше

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
SELECT ID_A, MAX(SUM1), MAX(SUM2), MAX(FLAG)
  FROM
  (
    SELECT ID_A, SUM(NUM) SUM1, NULL SUM2, NULL FLAG
      FROM B1 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
    GROUP BY ID_A
    
    UNION ALL
    
    SELECT ID_A, NULL, SUM(NUM), NULL
      FROM B2 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
    GROUP BY ID_A
    
    UNION ALL
    
    SELECT ID_A, NULL, NULL, MIN(C.FLAG)
      FROM C
    GROUP BY ID_A
  ) B1B2C JOIN A ON A.ID +0= B1B2C.ID_A   -- заставим делать HASH JOIN
GROUP BY ID_A
HAVING MAX(FLAG) IS NOT NULL
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964758
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvp, таблицы агрегируются независимо друг от друга, чтобы избежать коррелированных подзапросов.
Потом результаты склеивается одну строку для каждого ID_A и берётся полный их список из A.

hvlad, вроде же в исходном запросе все записи из таблицы А достаются и показываются?
HASH JOIN
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964760
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А я эпично обломался. Решил начать от простого к сложному, хаха :(

Внезапно, даты в BETWEEN у меня не задаются в параметрах запроса, а вычисляются из поля таблицы A, причём сложно.
Т.е. там есть поле "начало учётного периода", которое содержит в себе число. И интервал Between - это либо прошлый, либо текущий месяц. Т.е. если там "20", а сегодня 10е мая, то надо просуммировать либо с 20 марта по 19 апреля (SUM2), либо с 20 апреля по сегодняшний день, включительно (SUM1). Блин.

Что-то я всё больше склоняюсь к кэшированию в A всех сумм и обновлению их по триггерам...
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964773
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
SELECT ID_A, MAX(SUM1), MAX(SUM2), MAX(FLAG)
FROM
(
SELECT
    ID_A,
    SUM(NUM) SUM1,
    NULL SUM2,
    NULL FLAG
FROM B1 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
GROUP BY ID_A
UNION ALL
SELECT
    ID_A,
    NULL,
    SUM(NUM),
    NULL
FROM B2 WHERE FIX BETWEEN '10.05.2020' AND '20.05.2020'
GROUP BY ID_A
UNION ALL
SELECT
    ID_A,
    NULL,
    NULL,
    MIN(C.FLAG)
FROM C
GROUP BY ID_A
) B1B2C
LEFT JOIN A ON A.ID = B1B2C.ID_A
GROUP BY ID_A


Исходное условие - должны быть все записи из A.
В результат данного запроса не попадут записи из A, которых нет ни в B1, ни B2, ни в C.
Тогда уж
Код: sql
1.
RIGHT JOIN A ON A.ID = B1B2C.ID_A
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964774
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Polesov

Исходное условие - должны быть все записи из A.
В результат данного запроса не попадут записи из A, которых нет ни в B1, ни B2, ни в C.

Ну by design записи в C создаются одновременно с A, если запись есть в A, но нет ни одной записи в C - это нештатная ситуация, так что если такие записи не будет видно - не страшно. Вот отстутствие записей в B1, B2 - это нормально.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964790
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
alekcvp
Ну by design записи в C создаются одновременно с A

Аднака, паходу выявилась масса нюансоффф )))
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964807
hvlad
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke
hvlad, вроде же в исходном запросе все записи из таблицы А достаются и показываются?
Вот здесь 22142932 - не все, только те, для которых есть запись в C.
Всю ветку не читал, скучно :)
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964844
Polesov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hvlad
Flashpoke
hvlad, вроде же в исходном запросе все записи из таблицы А достаются и показываются?
Вот здесь 22142932 - не все, только те, для которых есть запись в C.

22143229
Выяснилось, что в C есть все, что есть в А.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964848
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А вот этого монстра можно как-нибудь оптимизировать?..
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
-- RD1, RD2 - день месяца (1..31)
SELECT ID_A, RD1, RD2, SUM(SNUM1), SUM(SNUM2), MIN(FLAG) AS FLAG
  FROM
  (
    SELECT ID_A, FIX FIX1, NULL FIX2, SUM(NUM) SNUM1, NULL SNUM2, NULL FLAG FROM B1
    GROUP BY ID_A, FIX
    
    UNION ALL
    
    SELECT ID_A, NULL, FIX, NULL, SUM(NUM), NULL FROM B2
    GROUP BY ID_A, FIX
    
    UNION ALL
    
    SELECT ID_A, NULL, NULL, NULL, NULL, MIN(C.FLAG) FROM C
    GROUP BY ID_A
  ) B1B2C JOIN A ON A.ID = B1B2C.ID_A
WHERE
  (FIX1 BETWEEN DFST(A.RD1) AND DLST(A.RD1) OR FIX1 IS NULL) AND
  (FIX2 BETWEEN DFST(A.RD2) AND DLST(A.RD2) OR FIX2 IS NULL)
GROUP BY ID_A, RD1, RD2
-- HAVING MIN(FLAG) IS NOT NULL -- наличие этого условия не обязательно


DFST/DLST
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
SET TERM ^ ;

create or alter function DFST (
    RDAY smallint not null)
returns date not null
as
declare variable RDIFF smallint;
begin
  RDIFF = RDAY - EXTRACT(DAY FROM CURRENT_DATE);
  return IIF(RDIFF < 0,  DATEADD(DAY, RDIFF, CURRENT_DATE), DATEADD(MONTH, -1, DATEADD(DAY, RDIFF, CURRENT_DATE)));
end
^

create or alter function DLST (
    RDAY smallint not null)
returns date not null
as
declare variable RDIFF smallint;
begin
  RDIFF = RDAY - EXTRACT(DAY FROM CURRENT_DATE);
  return IIF(RDIFF < 0,  DATEADD(MONTH, 1, DATEADD(DAY, RDIFF, CURRENT_DATE)), DATEADD(DAY, RDIFF, CURRENT_DATE)) - 1;
end
^

SET TERM ; ^


Polesov
Выяснилось, что в C есть все, что есть в А.
Предполагается что есть, судьба записей для которых нет - не важна (показывать или нет без разницы).
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964866
Flashpoke
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
alekcvp
А вот этого монстра можно как-нибудь оптимизировать?..
За 8 секунд отрабатывает, вроде нормально.
...
Рейтинг: 0 / 0
Вложенные запросы или как правильно это сделать?
    #39964869
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Flashpoke
alekcvp
А вот этого монстра можно как-нибудь оптимизировать?..
За 8 секунд отрабатывает, вроде нормально.

Ну исходный-то за секунду срабатывал, пока я в него кривыми ручками не залез :)
...
Рейтинг: 0 / 0
48 сообщений из 48, показаны все 2 страниц
Форумы / Firebird, InterBase [игнор отключен] [закрыт для гостей] / Вложенные запросы или как правильно это сделать?
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]