# Sql запросы ## Просматривайте план выполнения запроса, чтобы узнать проблемные места План запроса – последовательность шагов в виде дерева для получения результата SQL-запроса. Каждый шаг – операция: извлечение или обработка данных, сканирование индекса, пересечение или объединение множеств, построение битовых карт или другая. Чтобы построить план, оптимизатор использует правила преобразования, эвристику и алгоритмы оптимизации на базе стоимости. Правило преобразует план в другой план с лучшей стоимостью. Например, операции фильтрации и проекции уменьшают размер набора данных и, следовательно, должны выполняться как можно раньше, правило может переупорядочить операции так, чтобы операции фильтрации и проекции выполнялись раньше. Алгоритм оптимизации выбирает план с наименьшей оценочной стоимостью. Число возможных планов (называемых пространством планов) для запроса, содержащим несколько операций, является огромным - слишком большим, чтобы алгоритм рассмотрел каждый возможный план, поэтому используются эвристики для сокращения числа планов, оцениваемых оптимизатором. Получить план запрос можно в dBeaver с помощью операции на панели слева `Получить план выполнения`, а так же с помощью команды `explain` перед запросом Пример: ```sql explain analyze SELECT w.* FROM stk_internalwarrant w WHERE w.idstateinmc = 300 AND EXISTS (SELECT 1 FROM stk_internalwarrantdet d WHERE d.idinternalwarrant = w.id AND d.idcons IS null) --Вывод Hash Semi Join (cost=13.79..52.69 rows=14 width=3485) Hash Cond: (w.id = d.idinternalwarrant) -> Seq Scan on stk_internalwarrant w (cost=0.00..38.44 rows=32 width=3485) Filter: (idstateinmc = '300'::numeric) -> Hash (cost=12.25..12.25 rows=123 width=8) -> Index Scan using idx_stk_internalwarrantdet_idcons on stk_internalwarrantdet d (cost=0.15..12.25 rows=123 width=8) Index Cond: (idcons IS NULL) ``` - [Использование EXPLAIN](https://postgrespro.ru/docs/postgresql/16/using-explain) - [Курс «PostgreSQL для начинающих»: #4 — Анализ запросов](https://habr.com/ru/companies/tensor/articles/790282/) ## При написании сложных запросов дробите его на части и используйте подзапросы При построении сложных запросов, дробите его на части - основной запрос с условиями и с минимальным кол-вом соединений можно вынести в with или в нижнеуровневый запрос. Если часть данных надо брать из доп. таблиц и при этом понадобится группировка, то лучше сделать это подзапросом, либо с помощью left join lateral. Тщательно анализируйте условия фильтрации, большинство условий старайтесь выносить на нижний уровень, чтобы как можно меньше было данных для последующих соединений. Если условия фильтрации идут по нескольким полям, но индексы есть только по отдельным полям, то создавайте составной индекс. Минимизируйте кол-во соединяемых таблиц, можно использовать денормализованные таблицы или столбцы. Пример: ```sql --использование with WITH t as ( SELECT t.* from Prc_PriceListRegFull t where t.idPRiceList = 1 and t.sFindKey in (select unnest(array['a','b','c'])) ) SELECT t.* from t where not exists(select 1 from t t1 where t1.idPRiceList = t.idPRiceList and t1.sFindKey = t.sFindKey and t1.dDoc > t.dDoc and t1.dFrom <= t.dFrom and t1.dTo >= t.dTo ) --подзапрос с использованием jsonb_build_object, чтобы использовать сразу несколько столбцов SELECT c.jStkRem->>'sMasterCode' as sMasterRem ,c.idOrder ,cast(c.jStkRem->>'nQtyBase' as numeric) as nQtyBase ,c.nQtyBase as nQtyStockCard ,c.sNumSrcDoc ,c.dSrcDoc ,c.sHLSrcDoc from (select c.* ,(select jsonb_build_object( 'nQtyBase', sum(rc.nQtyBaseMsr) ,'sStockCode' ,string_agg(distinct ss.sCode, ', ') ,'sStockCaption', string_agg(distinct ss.sCaption, ', ') ,'sMasterCode', string_agg(distinct mm.sMnemoCode_dz, ', ') ) from (select sum(rc.nQtyBaseMsr) nQtyBaseMsr ,rc.idStock ,rc.gidMaster from Stk_RegConsMat rc where rc.idGds = c.idGds and rc.gidsuite = c.gid and rc.idCons = getgidid(c.gidCons) and rc.stype = 'mov' group by rc.idStock ,rc.gidMaster having sum(rc.nQtyBaseMsr) > 0 ) rc left join Stk_Stock ss on ss.id = rc.idStock left join Stk_Master mm on mm.gidref = rc.gidMaster ) jStkRem from Gds_StockCard c ) as c --использование join lateral select t.id ,t.idNeedObj ,t8.nQtySumBySpec ,t8.nSumNoTaxBySpec from Prs_PurchTrans t left join lateral(select sum(t7.nQty) as nQtySumBySpec ,sum(t7.nSumNoTax) as nSumNoTaxBySpec from Prs_PurchTrans t7 where t7.gidSrcObj = t.gidSrcObj ) as t8 on true ``` - [Табличные выражения](https://postgrespro.ru/docs/postgresql/16/queries-table-expressions) - [Запросы WITH](https://postgrespro.ru/docs/postgresql/16/queries-with) ## Используйте индексы, чтобы ускорить получение запрашиваемых данных Индекс в таблице базы данных — это дополнительная структура данных, которая создаётся наряду с данными в таблице. Индексы используются для ускорения процесса извлечения данных и повышения производительности запросов. Они обеспечивают эффективный способ обращения к упорядоченным записям. Когда индекс создан, никакие дополнительные действия не требуются: система сама будет обновлять его при изменении данных в таблице и сама будет использовать его в запросах, где, по её мнению, это будет эффективнее, чем сканирование всей таблицы. Вам, возможно, придётся только периодически запускать команду ANALYZE для обновления статистических данных, на основе которых планировщик запросов принимает решения. Создание индекса для большой таблицы может занимать много времени. По умолчанию PostgreSQL позволяет параллельно с созданием индекса выполнять чтение (операторы SELECT) таблицы, но операции записи (INSERT, UPDATE и DELETE) блокируются до окончания построения индекса. Составной индекс - это индексы создаваемый по нескольким столбцам таблицы. Частичный индекс — это индекс, который строится по подмножеству строк таблицы, определяемому условным выражением (оно называется предикатом частичного индекса). Такой индекс содержит записи только для строк, удовлетворяющих предикату. Частичные индексы довольно специфичны, но в ряде ситуаций они могут быть очень полезны. Примеры: ```sql --частиный индекс CREATE INDEX IF NOT EXISTS idx_stk_cons_bneedcheckin_IsNullIdRes ON stk_cons (bneedcheckin) WHERE idcontrolresult IS NULL; --составной индекс CREATE INDEX IF NOT EXISTS idx_stk_regmovmat_gdsmastertype ON stk_regmovmat (idGds, gidMaster, stype) --GIN индекс для jSonb поля create index if not exists idx_Cnt_ContractSumDistr_jBbbObjAttrs on Cnt_ContractSumDistr using gin (jBbbObjAttrs) ``` - [Индексы](https://postgrespro.ru/docs/postgresql/16/indexes) ## Используйте параметры при передача значений в запрос При написании запросов в ASelect или ATSQL для передачи параметров не используйте прямую «врезку» значений в тело запроса, нужно передавать через аргументы. При написании запроса в onRefresh или selectStatement для Avi, к параметрам выборки или фильтра можно обращаться через двоеточие. Пример: ```scala //До for (rv <- new ASelect{ val id: NLongColumn = asNLong("id") SQL(s"""select o2.id from mes_WorkOrderOperation o1 join Mct_WorkStructureOperation o2 on o1.idWorkStructureOperation = o2.id where o1.idWorkOrder in (${idaWork.mkString(",")}) and coalesce(o1.bAccept,0) = 0 and o1.dFactEndDate is null and not exists (select 1 from mct_WorkStructureOperation c where c.idParent = o2.id)""") }) { val rop = Mct_WorkStructureOperationApi().load(rv.id()) Mct_WorkStructureOperationApi().setidResource(rop, idResource) } //После for (rv <- new ASelect{ val id: NLongColumn = asNLong("id") SQL(s"""select o2.id from mes_WorkOrderOperation o1 join Mct_WorkStructureOperation o2 on o1.idWorkStructureOperation = o2.id where o1.idWorkOrder in (select unnest({idaWork})) and coalesce(o1.bAccept,0) = 0 and o1.dFactEndDate is null and not exists (select 1 from mct_WorkStructureOperation c where c.idParent = o2.id)""") on(Symbol("idaWork") -> LongPgArray(idaWork)) }) { val rop = Mct_WorkStructureOperationApi().load(rv.id()) Mct_WorkStructureOperationApi().setidResource(rop, idResource) } //или так for (rv <- new ASelect{ val id: NLongColumn = asNLong("id") SQL"""select o2.id from mes_WorkOrderOperation o1 join Mct_WorkStructureOperation o2 on o1.idWorkStructureOperation = o2.id where o1.idWorkOrder in (select unnest(${LongPgArray(idaWork)})) and coalesce(o1.bAccept,0) = 0 and o1.dFactEndDate is null and not exists (select 1 from mct_WorkStructureOperation c where c.idParent = o2.id)""" }) { val rop = Mct_WorkStructureOperationApi().load(rv.id()) Mct_WorkStructureOperationApi().setidResource(rop, idResource) } ``` ## При группировке минимизируйте кол-во группируемых полей Чем больше группируемые полей, тем дольше будет выполняться запрос. Пример: ```sql // До select t.idgds ,g.smnemocode_dz as idGdsMC ,g.sheadline_dz as idGdsHL ,greatest(sum(case when ft.scode in ('20', '25') then t.nQtyRelease end), sum(case when ft.scode in ('30') then t.nQtyRelease end)) as nQtyRelease from Mes_WorkOrderOperation o join Mes_WorkOrderOperationGds t on t.idworkorderoperation = o.id join Mct_MaterialFulfillmentType ft on ft.id = t.idmaterialfulfillmenttype join bs_goods g on g.id = t.idgds where --@resultCondition and ft.scode in ('20', '25', '30') group by t.idgds ,g.smnemocode_dz ,g.sheadline_dz //После select t.idgds ,max(g.smnemocode_dz) as idGdsMC ,max(g.sheadline_dz) as idGdsHL ,greatest(sum(case when ft.scode in ('20', '25') then t.nQtyRelease end), sum(case when ft.scode in ('30') then t.nQtyRelease end)) as nQtyRelease from Mes_WorkOrderOperation o join Mes_WorkOrderOperationGds t on t.idworkorderoperation = o.id join Mct_MaterialFulfillmentType ft on ft.id = t.idmaterialfulfillmenttype join bs_goods g on g.id = t.idgds where --@resultCondition and ft.scode in ('20', '25', '30') group by t.idgds ``` ## Фильтруйтесь по индексируемым полям, вместо служебных полей Например, при фильтрации по мнемокоду надо использовать поле, которое является мнемокодом, а не служебное smnemocode_dz. Потому что индекс создается на поле, которое является мнемокодом Пример: ```sql // До select crt.id from oil_contrasroletype crt where crt.smnemocode_dz = 'Surveyor' // После select crt.id from oil_contrasroletype crt where crt.scode = 'Surveyor' ``` ## При соединении таблиц по идентификаторам, избегайте различных преобразований При добавлении в запросе различных преобразований на индексируемые поля не будет использоваться индекс и может существенно замедлиться запрос Пример: ```sql // До select t.id from bs_goods t join msr_measureitem mm on coalesce(t.idmeasureitem,0) = coalesce(mm.id,0) where t.id = 1 // После select t.id from bs_goods t left join msr_measureitem mm on t.idmeasureitem = mm.id where t.id = 1 ``` ## Используйте правильное приведение типов, в соответствие, с типом данных поля При добавлении в запросе условий с не верными типами данных на индексируемые поля не будет использоваться индекс и может существенно замедлиться запрос Пример: ```sql // До select c.id, c.sName from oco_query t join btk_class c on c.id = cast(substring(t.gidobject,'^[^/]*')as numeric) // После select c.id, c.sName from oco_query t join btk_class c on c.id = cast(substring(t.gidobject,'^[^/]*')as bigint) ``` Пример: ```sql // До select * from stm_actin t where to_char(t.dDocIn,'yyyy') >= '2024' // После select * from stm_actin t where t.dDocIn >= to_date('01.01.2024','dd.mm.yyyy') ```