Поиск нескольких значений в столбце xml в SQL

#sql-server #xml #sql-server-2008 #xquery-sql

#sql-сервер #xml #sql-server-2008 #xquery-sql

Вопрос:

Это моя таблица

 BasketId(int)   BasketName(varchar) BasketFruits(xml)
1       Gold        <FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID><FID>5</FID><FID>6</FID></FRUITS>
2       Silver      <FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID></FRUITS>
3       Bronze      <FRUITS><FID>3</FID><FID>4</FID><FID>5</FID></FRUITS>
  

Мне нужно выполнить поиск корзины, которая имеет FID значения 1 и 3
так что в этом случае я бы получил золото и серебро

Хотя я добрался до результата, в котором я могу искать ЕДИНСТВЕННОЕ значение FID, например 1, используя этот код:

 declare @fruitId varchar(10);
set @fruitId=1;
select * from Baskets
WHERE BasketFruits.exist('//FID/text()[contains(.,sql:variable("@fruitId"))]') = 1
  

ЕСЛИ бы это был T-SQL, я бы использовал предложение IN следующим образом

 SELECT * FROM Baskets where FID in (1,3)
  

Приветствуется любая помощь / обходной путь…

Ответ №1:

Первым вариантом было бы добавить другое предложение exist the where.

 declare @fruitId1 int;
set @fruitId1=1;

declare @fruitId2 int;
set @fruitId2=3;

select *
from @Test
where
  BasketFruits.exist('/FRUITS/FID[.=sql:variable("@fruitId1")]')=1 and
  BasketFruits.exist('/FRUITS/FID[.=sql:variable("@fruitId2")]')=1
  

Другой версией было бы использовать обе переменные в инструкции xquery, подсчитывая совпадения.

 select * 
from @Test
where BasketFruits.value(
  'count(distinct-values(/FRUITS/FID[.=(sql:variable("@fruitId1"),sql:variable("@fruitId2"))]))', 'int') = 2
  

Два приведенных выше запроса будут работать просто отлично, если вы знаете, сколько параметров FID вы собираетесь использовать при написании запроса. Если вы находитесь в ситуации, когда количество идентификаторов FID варьируется, вы могли бы использовать что-то вроде этого вместо этого.

 declare @FIDs xml = '<FID>1</FID><FID>3</FID>'

;with cteParam(FID) as
(
  select T.N.value('.', 'int')
  from @FIDs.nodes('FID') as T(N)
)  
select T.BasketName
from @Test as T
  cross apply T.BasketFruits.nodes('/FRUITS/FID') as F(FID)
  inner join cteParam as p
    on F.FID.value('.', 'int') = P.FID
group by T.BasketName
having count(T.BasketName) = (select count(*) from cteParam)
 
  

Создайте переменную @FIDs в формате XML для хранения значений, которые вы хотите использовать в запросе.

Вы можете протестировать последний запрос здесь:https://data.stackexchange.com/stackoverflow/q/101600/relational-division-with-xquery

Комментарии:

1. Отличная работа!! Искал этот ответ долгое время. Отличная работа Микаэля Эрикссона. Ты мужчина!

Ответ №2:

Это немного сложнее, чем я надеялся, но это решение работает.

По сути, я использую CTE (общее табличное выражение), которое разбивает таблицу и перекрестно соединяет все значения из <FID> узлов с именами корзин.

Из этого CTE я выбираю те корзины, которые содержат как значение 1 , так и 3 .

 DECLARE @Test TABLE (BasketID INT, BasketName VARCHAR(20), BasketFruits XML)

INSERT INTO @TEST
VALUES(1, 'Gold', '<FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID><FID>5</FID><FID>6</FID></FRUITS>'),
(2, 'Silver', '<FRUITS><FID>1</FID><FID>2</FID><FID>3</FID><FID>4</FID></FRUITS>'),
(3, 'Bronze', '<FRUITS><FID>3</FID><FID>4</FID><FID>5</FID></FRUITS>')

;WITH IDandFID AS
(
SELECT
    t.BasketID,
    t.BasketName,
    FR.FID.value('(.)[1]', 'int') AS 'FID'
FROM @Test t
CROSS APPLY basketfruits.nodes('/FRUITS/FID') AS FR(FID)
)
SELECT DISTINCT 
    BasketName
FROM 
    IDandFID i1
WHERE 
    EXISTS(SELECT * FROM IDandFID i2 WHERE i1.BasketID = i2.BasketID AND i2.FID = 1)
    AND EXISTS(SELECT * FROM IDandFID i3 WHERE i1.BasketID = i3.BasketID AND i3.FID = 3)
  

Выполняя этот запрос, я получаю ожидаемый результат:

 BasketName
----------
Gold
Silver
  

Комментарии:

1. Это решение работает без сомнения. Отличная работа! Однако, если количество сопоставляемых значений увеличивается, как сейчас, если я хочу искать корзины с идентификаторами фруктов 1,2 и 3, другой оператор EXISTS должен быть объединен с использованием динамических запросов. Или, я полагаю, мне нужно было бы рекурсивно вызвать эту хранимую процедуру в цикле while …?

2. @nav: да, я согласен — решение не идеально в том смысле, что оно не является «расширяемым», но я не могу придумать никакого другого способа прямо сейчас

Ответ №3:

Это слишком тривиально?

 SELECT * FROM Baskets WHERE BasketFruits LIKE '%<FID>1</FID>%' AND BasketFruits LIKE '%<FID>3</FID>%'