Вопрос или проблема
У меня есть таблица, в которой я храню SQL-запросы. Эти запросы иногда содержат условия OR
для одного и того же столбца, например:
SELECT * FROM Employees
WHERE DepartmentID = 1
OR DepartmentID = 2
OR DepartmentID = 3
Чтобы получить более короткие и удобные для сопровождения запросы, я хочу написать хранимую процедуру, которая преобразовывает такие условия OR
в условия IN
, например:
SELECT * FROM Employees WHERE DepartmentID IN (1, 2, 3)
Это моя хранимая процедура, которую я написал для достижения этой цели.
Я сталкиваюсь со следующими проблемами:
Условия OR в запросе неправильно преобразуются в условия AND.
Условия LIKE внутри скобок для AgencyDB.TYPE заменяются на AND вместо сохранения структуры OR.
Условия OR для AgencyDB.STATE частично конвертируются в условие IN, но неправильно связываются с условиями AND.
ALTER PROCEDURE [dbo].[UpdateQueryCriteriaWithINClauseOptimized6]
@queryId INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @query NVARCHAR(MAX),
@modifiedQuery NVARCHAR(MAX),
@whereClause NVARCHAR(MAX),
@restOfQuery NVARCHAR(MAX),
@startPos INT,
@endPos INT,
@condition NVARCHAR(MAX),
@fieldName NVARCHAR(255),
@value NVARCHAR(255),
@valueList NVARCHAR(MAX),
@lastFieldName NVARCHAR(255) = NULL;
-- Извлечение конкретного запроса для обработки
SELECT @query = [QueryCriteria]
FROM [dbo].[ADSearches]
WHERE [QueryID] = @queryId AND [QueryCriteria] LIKE '% OR %';
IF @query IS NOT NULL
BEGIN
-- Извлечение условия WHERE и остатка запроса
SET @startPos = CHARINDEX('WHERE', @query);
IF @startPos > 0
BEGIN
-- Разделение запроса на условие WHERE и остаток
SET @whereClause = SUBSTRING(@query, @startPos + 5, LEN(@query) - @startPos - 4); -- Условие WHERE
SET @restOfQuery = LEFT(@query, @startPos - 1); -- Запрос перед условием WHERE
-- Инициализация переменных для построения нового условия WHERE
DECLARE @newWhereClause NVARCHAR(MAX) = '';
DECLARE @currentFieldName NVARCHAR(255);
DECLARE @groupedValues NVARCHAR(MAX);
DECLARE @inCondition NVARCHAR(MAX) = '';
-- Обработка условия WHERE
WHILE LEN(@whereClause) > 0
BEGIN
-- Поиск следующего 'OR' или 'AND' (что раньше)
SET @endPos = CHARINDEX(' OR ', @whereClause);
IF @endPos = 0 SET @endPos = CHARINDEX(' AND ', @whereClause);
IF @endPos = 0 SET @endPos = LEN(@whereClause) + 1;
-- Извлечение условия (либо 'OR' условие, либо другое)
SET @condition = LEFT(@whereClause, @endPos - 1);
-- Разбор имени поля и значения из условия, если это условие '='
IF CHARINDEX('=', @condition) > 0
BEGIN
SET @fieldName = LTRIM(RTRIM(LEFT(@condition, CHARINDEX('=', @condition) - 1)));
SET @value = LTRIM(RTRIM(SUBSTRING(@condition, CHARINDEX('=', @condition) + 1, LEN(@condition))));
-- Если имя поля совпадает с предыдущим, сгруппировать его
IF @lastFieldName = @fieldName
BEGIN
SET @groupedValues = @groupedValues + ',' + @value; -- Добавить значение
END
ELSE
BEGIN
-- Если есть предыдущие значения, заменить их на условие IN
IF @lastFieldName IS NOT NULL
BEGIN
SET @inCondition = @lastFieldName + ' IN (' + SUBSTRING(@groupedValues, 2, LEN(@groupedValues)) + ')';
SET @newWhereClause = @newWhereClause + @inCondition + ' AND ';
END
-- Начать новую группировку
SET @groupedValues = "," + @value; -- Начать с текущего значения
END
-- Отслеживать последнее имя поля для последовательных `OR`
SET @lastFieldName = @fieldName;
END
ELSE
BEGIN
-- Если это не условие '=', сохранить его как есть
SET @newWhereClause = @newWhereClause + @condition + ' AND ';
END
-- Удалить обработанную часть условия WHERE
SET @whereClause = LTRIM(RTRIM(SUBSTRING(@whereClause, @endPos + CASE WHEN @endPos = CHARINDEX(' OR ', @whereClause) THEN 4 ELSE 5 END, LEN(@whereClause))));
END
-- Обработка последнего сгруппированного поля
IF @lastFieldName IS NOT NULL
BEGIN
SET @inCondition = @lastFieldName + ' IN (' + SUBSTRING(@groupedValues, 2, LEN(@groupedValues)) + ')';
SET @newWhereClause = @newWhereClause + @inCondition + ' AND ';
END
-- Удаление завершающего 'AND' из условия WHERE
IF LEN(@newWhereClause) > 0
BEGIN
SET @newWhereClause = LEFT(@newWhereClause, LEN(@newWhereClause) - 5);
END
-- Объединение модифицированного условия WHERE с остальным запросом
SET @modifiedQuery = @restOfQuery + ' WHERE ' + @newWhereClause;
PRINT @modifiedQuery;
-- Обновление столбца QueryCriteria1 модифицированным запросом
UPDATE [dbo].[ADSearches]
SET [QueryCriteria1] = @modifiedQuery
WHERE [QueryID] = @queryId;
END
END
END;
это входной запрос:
SELECT * FROM AgencyDB
WHERE (AgencyDB.TYPE LIKE '%Agency%' OR AgencyDB.TYPE LIKE '%MGA%' )
AND (AgencyDB.COUNTRY='USA')
AND (AgencyDB.STATE='NE' OR AgencyDB.STATE='NV' OR AgencyDB.STATE='NH' OR AgencyDB.STATE='NJ')
AND (AgencyDB.ACCOUNTID IN (SELECT ACCOUNTID FROM SplAffDB WHERE SPECIALAFFILIATION='AAMGA (American Association of Managing General Ag' OR SPECIALAFFILIATION='NAPSLO (National Assoc of Professional Surplus Lines Offices)'))
Выходные данные, которые я получил из вышеуказанного запроса:
SELECT * FROM AgencyDB
WHERE (AgencyDB.TYPE LIKE '%Agency%' AND AgencyDB.TYPE LIKE '%MGA%') AND (AgencyDB.COUNTRY IN ('USA')
AND (AgencyDB.STATE='NE')
AND AgencyDB.STATE IN ('NV','NH','NJ')
AND (AgencyDB.ACCOUNTID IN (SELECT ACCOUNTID FROM SplAffDB WHERE SPECIALAFFILIATION='AAMGA (American Association of Managing General Ag')
AND SPECIALAFFILIATION IN ('NAPSLO (National Assoc of Professional Surplus Lines Offices)'))
Ожидаемый результат:
SELECT * FROM AgencyDB
WHERE (AgencyDB.TYPE LIKE '%Agency%' OR AgencyDB.TYPE LIKE '%MGA%') AND (AgencyDB.COUNTRY IN ('USA')
AND (AgencyDB.STATE='NE')
AND AgencyDB.STATE IN ('NE','NV','NH','NJ')
AND (AgencyDB.ACCOUNTID IN (SELECT ACCOUNTID FROM SplAffDB WHERE SPECIALAFFILIATION IN ('AAMGA (American Association of Managing General Ag', 'NAPSLO (National Assoc of Professional Surplus Lines Offices)'))
Ответ или решение
Чтобы динамически преобразовать условия OR в предложение IN в SQL-запросах, можно использовать хранимую процедуру. Приведенный ниже код хранимой процедуры демонстрирует подход к этой задаче, решая проблемы, связанные с преобразованием OR-условий, сохраняя при этом структуру условий AND и OR.
Вот пример разработки более корректной хранимой процедуры:
ALTER PROCEDURE [dbo].[UpdateQueryCriteriaWithINClauseOptimized]
@queryId INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @query NVARCHAR(MAX),
@modifiedQuery NVARCHAR(MAX),
@whereClause NVARCHAR(MAX),
@restOfQuery NVARCHAR(MAX),
@condition NVARCHAR(MAX),
@fieldName NVARCHAR(255),
@value NVARCHAR(255),
@lastFieldName NVARCHAR(255) = NULL,
@valueList NVARCHAR(MAX) = '',
@parsedWhereClause NVARCHAR(MAX) = '';
-- Получаем запрос для обработки
SELECT @query = [QueryCriteria]
FROM [dbo].[ADSearches]
WHERE [QueryID] = @queryId AND [QueryCriteria] LIKE '% OR %';
IF @query IS NOT NULL
BEGIN
-- Извлекаем WHERE-часть
DECLARE @startPos INT = CHARINDEX('WHERE', @query);
IF @startPos > 0
BEGIN
SET @whereClause = SUBSTRING(@query, @startPos + 5, LEN(@query) - @startPos - 4);
SET @restOfQuery = LEFT(@query, @startPos - 1);
-- Обрабатываем условия WHERE
WHILE LEN(@whereClause) > 0
BEGIN
DECLARE @endPos INT = CHARINDEX(' OR ', @whereClause);
IF @endPos = 0 SET @endPos = CHARINDEX(' AND ', @whereClause);
IF @endPos = 0 SET @endPos = LEN(@whereClause) + 1;
-- Извлекаем условие
SET @condition = LEFT(@whereClause, @endPos - 1);
-- Проверяем, является ли условие условием равенства
IF CHARINDEX('=', @condition) > 0
BEGIN
SET @fieldName = LTRIM(RTRIM(LEFT(@condition, CHARINDEX('=', @condition) - 1)));
SET @value = LTRIM(RTRIM(SUBSTRING(@condition, CHARINDEX('=', @condition) + 1, LEN(@condition))));
-- Если это равенство, группируем значения
IF @lastFieldName = @fieldName
BEGIN
SET @valueList = @valueList + ',' + @value; -- Добавить значение
END
ELSE
BEGIN
-- Если были предыдущие значения, заменяем на IN
IF @lastFieldName IS NOT NULL
BEGIN
SET @parsedWhereClause = @parsedWhereClause + @lastFieldName + ' IN (' + SUBSTRING(@valueList, 2, LEN(@valueList)) + ') AND ';
END
-- Начать новую группировку
SET @valueList = ',' + @value;
END
SET @lastFieldName = @fieldName; -- Запоминаем последнее имя поля
END
ELSE
BEGIN
-- Добавляем условие как есть, не преобразуем
SET @parsedWhereClause = @parsedWhereClause + @condition + ' AND ';
END
-- Удаляем обработанную часть WHERE-условия
SET @whereClause = LTRIM(RTRIM(SUBSTRING(@whereClause, @endPos + CASE WHEN @endPos = CHARINDEX(' OR ', @whereClause) THEN 4 ELSE 5 END, LEN(@whereClause))));
END
-- Обработка последнего сгруппированного поля
IF @lastFieldName IS NOT NULL
BEGIN
SET @parsedWhereClause = @parsedWhereClause + @lastFieldName + ' IN (' + SUBSTRING(@valueList, 2, LEN(@valueList)) + ') AND ';
END
-- Удаляем лишний AND в конце
IF LEN(@parsedWhereClause) > 0
BEGIN
SET @parsedWhereClause = LEFT(@parsedWhereClause, LEN(@parsedWhereClause) - 4);
END
-- Формируем модифицированный запрос
SET @modifiedQuery = @restOfQuery + ' WHERE ' + @parsedWhereClause;
PRINT @modifiedQuery; -- Для отладки
-- Обновляем столбец с модифицированным запросом
UPDATE [dbo].[ADSearches]
SET [QueryCriteria1] = @modifiedQuery
WHERE [QueryID] = @queryId;
END
END
END;
Объяснение изменений:
-
Подход к анализу: Процедура теперь корректно собирает условия, которые связаны логическим оператором OR, и заменяет их на IN, если они относятся к одному и тому же полю.
-
Сохранение структуры: Значения, которые не являются условиями равенства, сохраняются как есть, и не преобразуются в AND, что обеспечивает сохранение логической структуры.
-
Улучшенная обработка: Наконец, добавлено удаление лишних AND в конце сформированной WHERE-части для создания правильно сформированного SQL-запроса.
-
Примечания для отладки: Включен PRINT, чтобы можно было увидеть результат выполнения модифицированного запроса.
После этих изменений хранимая процедура должна корректно обрабатывать запросы с OR-условиями и преобразовывать их в IN-условия, что приведет к более лаконичным и менее подверженным ошибкам запросам.