Vishal0903 Ответов: 3

Динамический запрос с динамическими записями в SQL


У меня есть таблица, как показано ниже
ID	CustomerID	Type	Value
1	1			T1		123
2	1			T2		XYZ
3	1			T1		456
4	2			T1		789
5	1			T2		ABC
6	4			T1		111

Мой запрос к SP будет иметь значение в переменной as

@SPInput = 'T1,123|T2,ABC|T1,456' --(It can have any combinations of Types and Values).

Значение в столбце "тип" не может быть предсказано и может быть до 200 или до 300.

Если я получаю 2 значения для T1 в запросе, то логический оператор OR должен быть выполнен в таблице

И

Если я получу другое значение, скажем T2 в запросе, то логический оператор AND должен быть выполнен в таблице для T1 и T2

Возможны любые комбинации типов и значений

Мне нужен CustomerID, если все значения совпадают с моим входным запросом.

Что я уже пробовал:

---------------- невежественный ----------------

3 Ответов

Рейтинг:
2

Maciej Los

Я не уверен, что хорошо вас понимаю...

Вам нужно разделиться @SPInput около {'|', ','} получить [Type] и [Value] Затем вы должны сравнить эти значения со значениями в таблице. Видеть:

DECLARE @tmp TABLE(ID int,	CustomerID int,	[Type] varchar(30),	[Value] varchar(30))

INSERT INTO @tmp(ID, CustomerID, [Type],  [Value])
VALUES(1, 1, 'T1', '123'),
(2, 1, 'T2', 'XYZ'),
(3,	1, 'T1', '456'),
(4,	2, 'T1', '789'),
(5,	1, 'T2', 'ABC'),
(6,	4, 'T1', '111')

DECLARE @SPInput varchar(150) = 'T1,123|T2,ABC|T1,456'

--split @SPInput by '|'
;WITH KeyPairs AS
(
	--initial query
	SELECT LEFT(@SPInput, CHARINDEX('|', @SPInput)-1) KeyPair, RIGHT(@SPInput, LEN(@SPInput)-CHARINDEX('|', @SPInput)) Remainder
	WHERE CHARINDEX('|', @SPInput)>0
	--recursive part
	UNION ALL
	SELECT LEFT(Remainder, CHARINDEX('|', Remainder)-1) KeyPair, RIGHT(Remainder, LEN(Remainder)-CHARINDEX('|', Remainder)) Remainder
	FROM KeyPairs
	WHERE CHARINDEX('|', Remainder)>0
	UNION ALL
	SELECT Remainder KeyPair, NULL Remainder
	FROM KeyPairs
	WHERE CHARINDEX('|', Remainder)=0
), TV AS --split KeyPair by ','
(
	SELECT LEFT(KeyPair, CHARINDEX(',', KeyPair)-1) [Type], RIGHT(KeyPair, LEN(KeyPair)-CHARINDEX(',', KeyPair)) [Value]
	FROM KeyPairs
	WHERE CHARINDEX(',', KeyPair)>0
)
-- get data from @tmp table where type and value passed by @SPInput are equal
SELECT t1.*
FROM @tmp t1 INNER JOIN TV t2 ON t1.[Type] = t2.[Type] AND t1.[Value] = t2.[Value]


Результат:
ID	CustomerID	Type	Value
1	1	T1	123
3	1	T1	456
5	1	T2	ABC


В приведенном выше примере я использую КТОС[^] для разделения входных данных на части, но вы также можете использовать другой способ: Разделение строк с разделителями с помощью XML в SQL Server[^]

Теперь вам нужно изменить код в соответствии с вашими потребностями.

Удачи вам!

[РЕДАКТИРОВАТЬ]
Что касается нашего обсуждения (в комментариях) этого условия: (T1 AND (123 OR 456)) AND (T2 AND BC)...
Самый простой способ фильтрации данных на основе вышеуказанного условия-использовать клиентский код (c#, vb.net и т. д.). см.:
//crate sample data
DataTable tmp = new DataTable();
tmp.Columns.AddRange(new DataColumn[]
	{
		new DataColumn("ID", typeof(int)),
		new DataColumn("CustomerID", typeof(int)),
		new DataColumn("Type", typeof(string)),
		new DataColumn("Value", typeof(string))
	});
tmp.Rows.Add(new object[]{1, 1, "T1", "123"});
tmp.Rows.Add(new object[]{2, 1, "T2", "XYZ"});
tmp.Rows.Add(new object[]{3, 1, "T1", "456"});
tmp.Rows.Add(new object[]{4, 2, "T1", "789"});
tmp.Rows.Add(new object[]{5, 1, "T2", "ABC"});
tmp.Rows.Add(new object[]{6, 4, "T1", "111"});
//determine values to find
KeyValuePair<string, string>[] values2find = new KeyValuePair<string, string>[]
	{
		new KeyValuePair<string, string>("T1", "123"),
		new KeyValuePair<string, string>("T2", "BC"),
		new KeyValuePair<string, string>("T1", "456")
	};
//group values to find by its Type
var filter = values2find
	.GroupBy(x=>x.Key)
	.ToList();
//filter data 
var result = tmp.AsEnumerable()
	.Where(x=> filter.All(y=>y.Key==x.Field<string>("Type") && y.Any(z=>z.Value==x.Field<string>("Value"))))
	.ToList();
	
Console.WriteLine($"Found {result.Count()} result(s)");
foreach(var f in result)
	Console.WriteLine($"Type: {f.Field<string>("Type")}\tValue:{f.Field<string>("Value")}");


Результат:
Found 0 result(s)


Для получения более подробной информации, пожалуйста, смотрите:
Перечислимый.Все&ЛТ;метод&ГТ;(интерфейс IEnumerable&ЛТ;метод&ГТ;, функция&ЛТ;метод логического и GT;) Способ (Система.По LINQ) | Майкрософт Документы[^]
Перечислимый.Любой Метод (System.Linq) | Microsoft Docs[^]


Vishal0903

Если я передам "DECLARE @SPInput varchar(150) = 'T1,123|T2,BC|T1,456'"

Он не должен возвращать никакого результата - так как мой конечный результат равен T1 и T2

но T2 -> BC нет в моей таблице

Этот запрос почти работает, но за исключением и - не могли бы Вы помочь с этим?

Maciej Los

"Он не должен возвращать никакого результата..."
Почему? Вы прошли 3 пары: {{T1, 123}, {T2, BC}, {T1, 456}}. Два из них существуют в таблице: {{T1, 123}, {T1, 456}}...

Vishal0903

Записи могут присутствовать или не присутствовать в таблице - только после запроса таблицы я смогу узнать, существует ли это значение

Vishal0903

Логический или когда один и тот же тип имеет 2 значения
И
Логично и когда присутствует другой тип

T1,123|T2,BC|T1,456

Экс - (123 или 456) и (до н. э.)

Maciej Los

Если вы хотите построить гораздо более сложные условия для фильтрации данных, я бы предложил сделать это на клиентском коде (c#, vb.net и т.д.), А не на серверном коде (t-sql).

Maciej Los

Так вот, где заявление должно выглядеть: (T1 AND (123 OR 456)) AND (T2 AND BC) Разве я не прав?

Vishal0903

Да, именно этого я и жду

На основе этого вывода можно выполнить множество других шагов - например, обработку в C# или VB.Сеть будет еще сложнее

Maciej Los

Извини, но ты ошибаешься. Пожалуйста, смотрите обновленное решение (после слова "редактировать").

Sandeep Mewara

Мой +5 ... Мастер SQL! :)

Maciej Los

Спасибо, Сандип.

Sandeep Mewara

:)
Пожалуйста, взгляните на: https://www.codeproject.com/Articles/5279992/Data-Visualization-Insights-with-Matplotlib

Рейтинг:
2

Siddharth Sreelal

Вы можете создать табличную функцию с приведенной ниже логикой и присоединить результат к таблице, из которой вы хотите отфильтровать.

Лучшим вариантом было бы использовать пользовательские типы данных для передачи значений через определенный пользователем тип.

Declare @SPInput NVARCHAR(MAX)= 'T1,123|T2,ABC|T1,456'


DECLARE @SetSeparator NVARCHAR(1) = '|'
DECLARE @ValSeparator NVARCHAR(1) = ','

 DECLARE @StartIndex INT, @TypeEndIndex INT,@ValStartIndex INT, @EndIndex INT

DECLARE  @Output TABLE (
      TypeName NVARCHAR(MAX),
	  Val NVARCHAR(MAX)
	  )
 
      SET @StartIndex = 1
      IF SUBSTRING(@SPInput, LEN(@SPInput) - 1, LEN(@SPInput)) <> @SetSeparator
      BEGIN
            SET @SPInput = @SPInput + @SetSeparator
      END
 
      WHILE CHARINDEX(@SetSeparator, @SPInput) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@SetSeparator, @SPInput)
			SET @TypeEndIndex = CHARINDEX(@ValSeparator, @SPInput)

			DECLARE @SubSet NVARCHAR(MAX) = SUBSTRING(@SPInput, @StartIndex, @EndIndex - 1);
           
            INSERT INTO @Output(TypeName,Val)
            SELECT SUBSTRING(@SubSet, @StartIndex, @TypeEndIndex - 1),SUBSTRING(@SubSet, @TypeEndIndex + 1, @EndIndex - 1)
           
            SET @SPInput = SUBSTRING(@SPInput, @EndIndex + 1, LEN(@SPInput))
      END

	  SELECT *
	  FROM @Output


Рейтинг:
1

OriginalGriff

Это, вероятно, не очень хорошая идея: вам нужно будет разорвать строку (что не так уж невозможно: Преобразование данных, разделенных запятыми в столбце, в строки для выбора[^] показывает вам основы того, как это сделать), затем используйте эти разделенные элементы для создания предложения WHERE для нового запроса SELECT, а затем выполните этот запрос.

Что можно сделать, хотя это неприятная работа и будет Пита для поддержания и проверки всех крайних случаев.

Проблема в том, что вы собираетесь писать SP, который оставляет вашу БД широко открытой для SQL-инъекции из-за необходимости построить командную строку и выполнить ее. Я действительно очень, очень сильно думал, прежде чем начать это: это может быть очень опасно в реальном мире.