4

This is my table

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>

I need to search for the basket which has FID values 1 and 3 so that in this case i would get Gold and Silver

Although i've reached to the result where i can search for a SINGLE FID value like 1 using this code:

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

HAD it been T-SQL i would have used the IN Clause like this

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

Any help/workaround appreciated...

3 Answers 3

2

First option would be to add another exist the where clause.

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

Another version would be to use both variables in the xquery statement, counting the hits.

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

The two queries above will work just fine if you know how many FID parameters you are going to use when you write the query. If you are in a situation where the number of FID's vary you could use something like this instead.

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)
 

Build the @FIDs variable as an XML to hold the values you want to use in the query.

You can test the last query here: https://data.stackexchange.com/stackoverflow/q/101600/relational-division-with-xquery

Sign up to request clarification or add additional context in comments.

1 Comment

Excellent work!! Was looking for this answer for a long long time. Great work Mikael Eriksson. You're the man!
1

It is a bit more involved than I hoped it would be - but this solution works.

Basically, I'm using a CTE (Common Table Expression) which breaks up the table and cross joins all values from the <FID> nodes to the basket names.

From that CTE, I select those baskets that contain both a value of 1 and 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)

Running this query, I do get the expected output of:

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

2 Comments

This solution works without a doubt. Great Job! However, if the number of values to be matched for increases like now if i want to search for Baskets with FruitIds 1,2 and 3, another EXISTS statement will have to be concatenated using dynamic queries. Or i guess, i'd need to call this stored procedure in a while loop recursively...?
@nav: yes, I agree - the solution isn't perfect in that it's not "extensible" - but I cannot think of any other way just right now
0

Is this too trivial?

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.