I have table like this:
MYDATA
Company Reference FirstName Surname
1 A001 Test1Name Test1Surname
2 A001 Test2Name Test2Surname
3 A001 Test3Name Test3Surname
and xml variable @searchList
that hold this data:
<list>
<item>
<company>1</company>
<reference>A001</reference>
</item>
<item>
<company>2</company>
<reference>A001</reference>
</item>
<item>
<company>3</company>
<reference>A001</reference>
</item>
</list>
Usually where there is just one condition I would do something like:
select * from dbo.MYDATA rec
where Reference in
(
select entity.value('(reference/text())[1]', 'varchar(32)')
from searchList.nodes('/list/item') as T(entity)
)
Where there are 2 conditions (company = xml.company and reference = xml.reference
) it gets more complex.
One way to do it is to create a temporary table with companyid and reference columns, insert everything from xml into that temporary table and do a join on the MYDATA table. Other way is a similar thing but with a subquery.
Is there any other more elegant and mostly important performance wise way to accomplish this?
This is what gave me the best performance result for now:
DECLARE @tbl TABLE(Id INT, Company INT,Reference VARCHAR(10),FirstName VARCHAR(100),Surname VARCHAR(100));
INSERT INTO @tbl VALUES
(1, 1,'A001','Test1Name','Test1Surname')
,(2, 2,'A001','Test2Name','Test2Surname')
,(3, 3,'A001','Test3Name','Test3Surname');
DECLARE @xml XML=
N'<list>
<item>
<company>1</company>
<reference>A001</reference>
</item>
<item>
<company>2</company>
<reference>A001</reference>
</item>
<item>
<company>3</company>
<reference>A001</reference>
</item>
</list>';
WITH SEARCHLIST (company, reference) as
(
select li.value('(company/text())[1]', 'int') AS company
,li.value('(reference/text())[1]', 'nvarchar(max)') AS reference
from @xml.nodes('/list/item') AS A(li)
)
select rec.* from SEARCHLIST srl
left join @tbl rec on srl.reference = rec.Reference and srl.company = rec.Company
where rec.Id is not null;
You can use a CTE to get a derived table out of your XML using .nodes()
.
This allows you, to deal with the values taken from your XML as if they were a normal table:
DECLARE @tbl TABLE(Company INT,Reference VARCHAR(10),FirstName VARCHAR(100),Surname VARCHAR(100));
INSERT INTO @tbl VALUES
(1,'A001','Test1Name','Test1Surname')
,(2,'A001','Test2Name','Test2Surname')
,(3,'A001','Test3Name','Test3Surname');
DECLARE @xml XML=
N'<list>
<item>
<company>1</company>
<reference>A001</reference>
</item>
<item>
<company>2</company>
<reference>A001</reference>
</item>
<item>
<company>3</company>
<reference>A001</reference>
</item>
</list>';
WITH CTE AS
(
SELECT li.value(N'company[1]',N'int') AS company
,li.value(N'reference[1]',N'nvarchar(max)') AS reference
FROM @xml.nodes(N'/list/item') AS A(li)
)
SELECT *
FROM @tbl AS t
INNER JOIN CTE ON t.Reference=CTE.reference --use any column however you like it
To be honest: You write Other way is a similar thing but with a subquery. The CTE approach is technically spoken quite the same as a sub-select in this case.
Depending on the size of your XML it is - in most cases - faster to use the XML method .exist()
where you can check the existance of an XPath
with predicates.
The CTE appraoch must read out the whole set, just to tear it down again. If performance matters, you can include a filter predicate in .nodes()
too.
But the best solution is highly depending on your actual needs...
.exist()
Try this (changed the sample data to carry differing values)
DECLARE @tbl TABLE(Company INT,Reference VARCHAR(10),FirstName VARCHAR(100),Surname VARCHAR(100));
INSERT INTO @tbl VALUES
(1,'A001','Test1Name','Test1Surname')
,(2,'A002','Test2Name','Test2Surname')
,(3,'A004','Test3Name','Test3Surname'); <-- Will not be returned
DECLARE @xml XML=
N'<list>
<item>
<company>1</company>
<reference>A001</reference>
</item>
<item>
<company>2</company>
<reference>A002</reference>
</item>
<item>
<company>3</company>
<reference>A003</reference>
</item>
</list>';
select * from @tbl rec
where @xml.exist(N'/list/item/reference[text()=sql:column("Reference")]')=1