Skip to content

Feauture/logical or in join query section #3471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/diagnostics/LogicalOrInJoinQuerySection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Логическое 'ИЛИ' в соединениях запроса (LogicalOrInJoinQuerySection)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Описание диагностики
Диагностика выявляет использование оператора `ИЛИ` в условиях соединений таблиц.

Присутствие операторов `ИЛИ` в условиях соединений может привести к тому, что СУБД не сможет использовать
индексы таблиц и будет выполнять сканирование, что увеличит время работы запроса и вероятность возникновения блокировок.

Ошибка может быть решена "разнесением" предикатов условия с `ИЛИ` на разные пакеты запросов с объединением

ВАЖНО:
Диагностика контролирует наличие предикатов в условии `ИЛИ`, над различными полями, так как использование оператора `ИЛИ`
над вариантами одного поля при выполнении запроса на стороне SQL автоматически преобразуется в условие IN.
## Примеры
1) Ошибка не будет зафиксирована при использовании `ИЛИ` над вариантами одного поля

```bsl
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ВидыНоменклатуры КАК ВидыНоменклатуры //Тест работы на вложенном соединении
ПО СправочникНоменклатура.ВидНоменклатуры = ВидыНоменклатуры.Ссылка
И (СправочникНоменклатура.СрокГодности > 1
ИЛИ СправочникНоменклатура.СрокГодности < 10)
```
2) При использовании оператора `ИЛИ` над различными полями ошибка будет зафиксирована для каждого вхождения оператора

```bsl
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО РеализацияТоваровУслугТовары.Ссылка = РеализацияТоваровУслуг.Ссылка
И (РеализацияТоваровУслугТовары.Сумма > 0
ИЛИ РеализацияТоваровУслугТовары.СуммаНДС > 0
ИЛИ РеализацияТоваровУслугТовары.СуммаСНДС > 0)

```

Такие конструкции предлагается исправлять вынесением в отдельные пакеты запросов с объединением:

```bsl
ВЫБРАТЬ *
ИЗ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО РеализацияТоваровУслугТовары.Ссылка = РеализацияТоваровУслуг.Ссылка
И РеализацияТоваровУслугТовары.Сумма > 0

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ *
ИЗ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО РеализацияТоваровУслугТовары.Ссылка = РеализацияТоваровУслуг.Ссылка
И РеализацияТоваровУслугТовары.СуммаНДС > 0

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ *
ИЗ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО РеализацияТоваровУслугТовары.Ссылка = РеализацияТоваровУслуг.Ссылка
И РеализацияТоваровУслугТовары.СуммаСНДС > 0
```

3) Диагностика также отработает для вложенных соединений с использованием `ИЛИ` в условиях.

```bsl
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО РеализацияТоваровУслугТовары.Ссылка = РеализацияТоваровУслуг.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СправочникНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ВидыНоменклатуры КАК ВидыНоменклатуры //Тест работы на вложенном соединении
ПО СправочникНоменклатура.ВидНоменклатуры = ВидыНоменклатуры.Ссылка
И (СправочникНоменклатура.СрокГодности > 1
ИЛИ ВидыНоменклатуры.ЗапрещенаПродажаЧерезПатент = ИСТИНА)

```
Рекомендуется исправление аналогично п.2 заменой вложенного соединения на соединение с созданием промежуточной временной таблицы

## Источники
- [Стандарт - Эффективные условия запросов, п.2](https://its.1c.ru/db/v8std/content/658/hdoc)
- [Использование логического ИЛИ в условиях - Типичные причины неоптимальной работы запросов и методы оптимизации](https://its.1c.ru/db/content/metod8dev/src/developers/scalability/standards/i8105842.htm#or)
80 changes: 80 additions & 0 deletions docs/en/diagnostics/LogicalOrInJoinQuerySection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Logical 'OR' in 'JOIN' query section (LogicalOrInJoinQuerySection)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Description

Diagnostics reveals the use of the `OR` operator in the conditions of table joins.

The presence of the `OR` operators in connection conditions may cause the DBMS to be unable to use
table indexes and perform scans, which will increase query running time and the likelihood of locks.

The error can be solved by "spreading" the predicates of the condition with `OR` into different query packages with combining

IMPORTANT:
Diagnostics monitors the presence of predicates in the condition `OR`, over various fields, since the use of the operator `OR`
When executing a query on the SQL side, the control over the variants of one field is automatically converted to the IN condition.

## Examples
1) The error will not be fixed when using `OR` over variants of a single field.

```bsl
LEFT JOIN Catalog.NomenclatureTypes КАК NomenclatureTypes
ON CatalogNomenclature.NomenclatureType = NomenclatureTypes.Reference
AND (CatalogNomenclature.ExpirationDate > 1
OR CatalogNomenclature.ExpirationDate < 10)
```

2) When using the `OR` operator over various fields, the error will be fixed for each occurrence of the operator.

```bsl
INNER JOIN Document.GoodsServicesSaling КАК GoodsServicesSaling
ON GoodsServicesSalingGoods.Reference = GoodsServicesSaling.Reference
AND (GoodsServicesSalingGoods.Amount > 0
OR GoodsServicesSalingGoods.AmountVAT > 0
OR GoodsServicesSalingGoods.AmountWithVAT > 0)

```

It is proposed to correct such constructions by placing requests in separate packages with combining:

```bsl
SELECT *
FROM
INNER JOIN Document.GoodsServicesSaling КАК GoodsServicesSaling
ON GoodsServicesSalingGoods.Reference = GoodsServicesSaling.Reference
AND GoodsServicesSalingGoods.Amount > 0

UNION ALL

SELECT *
FROM
INNER JOIN Document.GoodsServicesSaling КАК GoodsServicesSaling
ON GoodsServicesSalingGoods.Reference = GoodsServicesSaling.Reference
AND GoodsServicesSalingGoods.AmountVAT > 0

UNION ALL

SELECT *
FROM
INNER JOIN Document.GoodsServicesSaling КАК GoodsServicesSaling
ON GoodsServicesSalingGoods.Reference = GoodsServicesSaling.Reference
AND GoodsServicesSalingGoods.AmountWithVAT > 0
```

3) Diagnostics will also work for nested connections using `OR` in conditions.

```bsl
Document.GoodsServicesSaling.Goods КАК GoodsServicesSalingGoods
INNER JOIN Document.GoodsServicesSaling КАК GoodsServicesSaling
ON GoodsServicesSalingGoods.Reference = GoodsServicesSaling.Reference
LEFT JOIN Catalog.Nomenclature КАК CatalogNomenclature
LEFT JOIN Catalog.NomenclatureTypes КАК NomenclatureTypes
ON CatalogNomenclature.NomenclatureType = NomenclatureTypes.Reference
AND (CatalogNomenclature.ExpirationDate > 1
OR NomenclatureTypes.SaleThroughAPatentIsProhibited = TRUE)

```
A fix similar to paragraph 2 is recommended by replacing the nested connection with a connection with the creation of an intermediate temporary table.
## Sources
- [Standard: Effective Query Conditions, Clause 2 (RU)](https://its.1c.ru/db/v8std/content/658/hdoc)
- [Typical Causes of Suboptimal Query Performance and Optimization Techniques: Using Logical OR in Conditions (RU)](https://its.1c.ru/db/content/metod8dev/src/developers/scalability/standards/i8105842.htm#or)
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
import com.github._1c_syntax.bsl.parser.SDBLParser;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@DiagnosticMetadata(
type = DiagnosticType.CODE_SMELL,
severity = DiagnosticSeverity.INFO,
minutesToFix = 1,
tags = {
DiagnosticTag.SQL,
DiagnosticTag.PERFORMANCE,
DiagnosticTag.UNPREDICTABLE
}

)
public class LogicalOrInJoinQuerySectionDiagnostic extends AbstractSDBLVisitorDiagnostic {

@Override
public ParseTree visitQuery(SDBLParser.QueryContext ctx) {
//При посещении запроса сразу отбираем контексты соединений, которые в цикле обрабатываем отдельным методом.
Trees.findAllRuleNodes(ctx, SDBLParser.RULE_joinPart).
forEach(jpt -> processJoinPart((SDBLParser.JoinPartContext) jpt));

return ctx;
}

private void processJoinPart(SDBLParser.JoinPartContext ctx) {

//Инициализируем поток для коллекции условий соединения, каждое условие преобразуем в логическое выражение.
ctx.condition.condidions.stream().map(SDBLParser.PredicateContext::logicalExpression)
//фильтрация null отсекает условия, не содержащие составных логических конструкций.
.filter(Objects::nonNull)
//Каждое условие дополнительно отбирается по наличию более чем одного различных полей.
.filter(this::isMultipleFieldsExpression)
//По оставшимся условиям проводится цикл с поиском операторов "ИЛИ"
// и вложенным циклом фиксации ошибки диагностики для каждого оператора
.forEach(
exp -> Trees.findAllTokenNodes(exp, SDBLParser.OR)
.forEach(diagnosticStorage::addDiagnostic));

}

private boolean isMultipleFieldsExpression(SDBLParser.LogicalExpressionContext exp){

//От контекста логического выражения спускаемся до контекста колонки,
// далее поток текстового представления колонок собираем в Set.
//По наличию в Set более чем одного элемента проверяем использование различных полей в условии
Set<String> expFields = Trees.findAllRuleNodes(exp, SDBLParser.RULE_column).stream()
.map(ParseTree::getText)
.collect(Collectors.toSet());
return expFields.size() > 1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,16 @@
},
"$id": "#/definitions/LineLength"
},
"LogicalOrInJoinQuerySection": {
"description": "Logical 'OR' in 'JOIN' query section",
"default": true,
"type": [
"boolean",
"object"
],
"title": "Logical 'OR' in 'JOIN' query section",
"$id": "#/definitions/LogicalOrInJoinQuerySection"
},
"LogicalOrInTheWhereSectionOfQuery": {
"description": "Using a logical \"OR\" in the \"WHERE\" section of a query",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@
"LineLength": {
"$ref": "parameters-schema.json#/definitions/LineLength"
},
"LogicalOrInJoinQuerySection": {
"$ref": "parameters-schema.json#/definitions/LogicalOrInJoinQuerySection"
},
"LogicalOrInTheWhereSectionOfQuery": {
"$ref": "parameters-schema.json#/definitions/LogicalOrInTheWhereSectionOfQuery"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage='OR' operator is detected in JOIN condition
diagnosticName=Logical 'OR' in 'JOIN' query section
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage=Обнаружен оператор 'ИЛИ' в условии соединения
diagnosticName=Логическое 'ИЛИ' в соединениях запроса
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2025
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;

class LogicalOrInJoinQuerySectionDiagnosticTest extends AbstractDiagnosticTest<LogicalOrInJoinQuerySectionDiagnostic> {
LogicalOrInJoinQuerySectionDiagnosticTest() {
super(LogicalOrInJoinQuerySectionDiagnostic.class);
}

@Test
void test() {

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics).hasSize(8);

assertThat(diagnostics, true)
.hasRange(12, 62, 12, 65)
.hasRange(12, 108, 12, 111)
.hasRange(24, 14, 24, 17)
.hasRange(26, 14, 26, 17)
.hasRange(27, 14, 27, 17)
.hasRange(29, 14, 29, 17)
.hasRange(30, 14, 30, 17)
.hasRange(19, 15, 19, 18);

}
}
Loading