Поиск нескольких значений в столбце xml в SQL - PullRequest
4 голосов
/ 28 мая 2011

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

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, чтобы в этом случае я получил Золото и Серебро

Несмотря на то, что я достиг результата, где я могу найти значение SINGLE FID, например 1 , используя этот код:

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

HADэто был T-SQL, я бы использовал IN вот так

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

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

Ответы [ 3 ]

2 голосов
/ 30 мая 2011

Первый вариант - добавить еще одно предложение 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 для хранения значений, которые вы хотите использовать в запросе.

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

1 голос
/ 29 мая 2011

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

По сути, я использую CTE (Common Table Expression), который разбивает таблицу и перекрестно соединяет все значения от узлов <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
0 голосов
/ 29 мая 2011

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

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