SQL - Parse table of multi-column XML Data - sql-server

I have a table filled with XML Data that I am trying to parse. The XML contains multiple columns of data that I am trying to parse. In some cases there are multiple rows of XML data stuffed into the single column of data and in some cases just one. sample data below:
<REC><C1>0E5627DF-DBB1-4300-40F2-715A8C96190B</C1><C2>apples</C2></REC>
<REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>oranges</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>grapes</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>apples</C2></REC>
<REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>bananas</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>watermelon</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>limes</C2></REC>
<REC><C1>38B13BFB-DBAA-C340-40F2-715A8C961942</C1><C2>apples</C2></REC>
<REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>pears</C2></REC><REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>limes</C2></REC>
What I am trying to do is parse the data into the following 2 column layout
C1 C2
0E5627DF-DBB1-4300-40F2-715A8C96190B apples
59868DA4-DB9D-1384-B07D-715A8C96197B oranges
59868DA4-DB9D-1384-B07D-715A8C96197B grapes
59868DA4-DB9D-1384-B07D-715A8C96197B apples
7FB8C203-DB30-5340-B07D-715A8C9619FA bananas
7FB8C203-DB30-5340-B07D-715A8C9619FA watermelon
7FB8C203-DB30-5340-B07D-715A8C9619FA limes
38B13BFB-DBAA-C340-40F2-715A8C961942 apples
58209738-DB3C-DB00-D01A-7FDA8C9619B5 pears
58209738-DB3C-DB00-D01A-7FDA8C9619B5 limes
Below is my attempt at it:
SELECT Split.XMLD.value('.', 'VARCHAR(500)')
FROM myTable XMLD
CROSS APPLY XMLD.REC.nodes ('/REC') AS Split(XMLD)
Any ideas how to parse this?
Clarification: I want to stay with Native MS SQL SQL here. I don't want to use any third party tools.

Try this:
DECLARE #mockupTable TABLE (ID INT IDENTITY, YourXml XML);
INSERT INTO #mockupTable VALUES
('<REC><C1>0E5627DF-DBB1-4300-40F2-715A8C96190B</C1><C2>apples</C2></REC>')
,('<REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>oranges</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>grapes</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>apples</C2></REC>')
,('<REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>bananas</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>watermelon</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>limes</C2></REC>')
,('<REC><C1>38B13BFB-DBAA-C340-40F2-715A8C961942</C1><C2>apples</C2></REC>')
,('<REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>pears</C2></REC><REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>limes</C2></REC>');
SELECT ID
,r.value(N'(C1/text())[1]','uniqueidentifier') AS C1
,r.value(N'(C2/text())[1]','nvarchar(max)') AS C2
FROM #mockupTable AS t
CROSS APPLY t.YourXml.nodes(N'/REC') AS A(r) ;
The result
+----+--------------------------------------+------------+
| ID | C1 | C2 |
+----+--------------------------------------+------------+
| 1 | 0E5627DF-DBB1-4300-40F2-715A8C96190B | apples |
+----+--------------------------------------+------------+
| 2 | 59868DA4-DB9D-1384-B07D-715A8C96197B | oranges |
+----+--------------------------------------+------------+
| 2 | 59868DA4-DB9D-1384-B07D-715A8C96197B | grapes |
+----+--------------------------------------+------------+
| 2 | 59868DA4-DB9D-1384-B07D-715A8C96197B | apples |
+----+--------------------------------------+------------+
| 3 | 7FB8C203-DB30-5340-B07D-715A8C9619FA | bananas |
+----+--------------------------------------+------------+
| 3 | 7FB8C203-DB30-5340-B07D-715A8C9619FA | watermelon |
+----+--------------------------------------+------------+
| 3 | 7FB8C203-DB30-5340-B07D-715A8C9619FA | limes |
+----+--------------------------------------+------------+
| 4 | 38B13BFB-DBAA-C340-40F2-715A8C961942 | apples |
+----+--------------------------------------+------------+
| 5 | 58209738-DB3C-DB00-D01A-7FDA8C9619B5 | pears |
+----+--------------------------------------+------------+
| 5 | 58209738-DB3C-DB00-D01A-7FDA8C9619B5 | limes |
+----+--------------------------------------+------------+
Some things to think about:
Your XML is not well-formed. There is no root-node. SQL-Server can deal with such XML fragments, but other comsumers might get in troubles.
If this XML is under your control I'd change the design no to store the C1 value over and over.

Here is a really neat way to easily generate the XQuery/XPath query for any XML data no matter the complexity or "ugliness":
It requires SQLHTTP which is a free database/assembly that we created which you can find on our website at: http://sqlhttp.net/documentation/xqueryhelper
First you need to set an XML variable with your data. Notice that I added a opening tag and closing tag.
DECLARE #X xml = '<ROOT>
<REC><C1>0E5627DF-DBB1-4300-40F2-715A8C96190B</C1><C2>apples</C2></REC>
<REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>oranges</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>grapes</C2></REC><REC><C1>59868DA4-DB9D-1384-B07D-715A8C96197B</C1><C2>apples</C2></REC>
<REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>bananas</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>watermelon</C2></REC><REC><C1>7FB8C203-DB30-5340-B07D-715A8C9619FA</C1><C2>limes</C2></REC>
<REC><C1>38B13BFB-DBAA-C340-40F2-715A8C961942</C1><C2>apples</C2></REC>
<REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>pears</C2></REC><REC><C1>58209738-DB3C-DB00-D01A-7FDA8C9619B5</C1><C2>limes</C2></REC>
</ROOT>'
You then execute the following stored procedure:
EXEC SQLHTTP.net.XqueryHelper #X
In the case, the procedure will output the following four lines:
Usage Name Rows
------------------------------------------------- ------ ------
EXEC SQLHTTP.net.XQueryHelper #X, 'ROOT' ROOT 1
EXEC SQLHTTP.net.XQueryHelper #X, 'ROOT/REC' REC 10
EXEC SQLHTTP.net.XQueryHelper #X, 'ROOT/REC/C1' C1 10
EXEC SQLHTTP.net.XQueryHelper #X, 'ROOT/REC/C2' C2 10
The line you're interested in to get you the ten records with the fruit names is the second line:
EXEC SQLHTTP.net.XQueryHelper #X, 'ROOT/REC'
The above stored procedure call will then output your XQuery/XPath like this:
SELECT T.C.value(N'C1[1]', N'nvarchar(MAX)') AS [C1]
,T.C.value(N'C2[1]', N'nvarchar(MAX)') AS [C2]
FROM #X.nodes(N'/ROOT/REC') T(C)

Related

SQL Server find sum of values based on criteria within another table

I have a table consisting of ID, Year, Value
---------------------------------------
| ID | Year | Value |
---------------------------------------
| 1 | 2006 | 100 |
| 1 | 2007 | 200 |
| 1 | 2008 | 150 |
| 1 | 2009 | 250 |
| 2 | 2005 | 50 |
| 2 | 2006 | 75 |
| 2 | 2007 | 65 |
---------------------------------------
I then create a derived, aggregated table consisting of an ID, MinYear, and MaxYear
---------------------------------------
| ID | MinYear | MaxYear |
---------------------------------------
| 1 | 2006 | 2009 |
| 2 | 2005 | 2007 |
---------------------------------------
I then want to find the sum of Values between the MinYear and MaxYear foreach ID in the aggregated table, but I am having trouble determining a proper query.
The final table should look something like this
----------------------------------------------------
| ID | MinYear | MaxYear | SumVal |
----------------------------------------------------
| 1 | 2006 | 2009 | 700 |
| 2 | 2005 | 2007 | 190 |
----------------------------------------------------
Right now I can perform all the joins to create the second table. But then I use a fast forward cursor to iterate through each record of the second table with the code inside the for loop looking like the following
DECLARE #curMin int
DECLARE #curMax int
DECLARE #curID int
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT Sum(Value) FROM ValTable WHERE Year >= #curMin and Year <= #curMax and ID = #curID
Group By ID
FETCH Next FROM fastCursor INTo #curISIN, #curMin , #curMax
Having found the sum of values between specified years, I can connect it back to the second table and I wind up the desired result (the third table).
However, the second table in reality is roughly 4 million rows, so this iteration is extremely time consuming (~generating 300 results a minute) and presumably not the best solution.
My question is, is there a way to generate the third table's results without having to use a cursor/for loop?
During a group by the sum will only be for the ID in question -- since the min year and max year is for the ID itself then you don't need to double query. The query below should give you exactly what you need. If you have a different requirement let me know.
SELECT ID, MIN(YEAR) as MinYear, MAX(YEAR) as MaxYear, SUM(VALUE) as SUMVALUE
FROM tablenameyoudidnotsay
GROUP BY ID
You could use query as bellow
TableA is your first table, and TableB is the second one
SELECT *,
(select SUM(Value) FROM TableA where tablea.ID=TableB.ID AND tableA.Year BETWEEN
TableB.MinYear AND TableB.MaxYear) AS SumValue
from TableB
You can put your criteria into a join and obtain the result all as one set which should be faster:
SELECT b.Id, b.MinYear, b.MaxYear, sum(a.Value)
FROM Table2 b
JOIN Table1 a ON a.Id=b.Id AND b.MinYear <= a.Year AND b.MaxYear >= a.Year
GROUP BY b.Id, b.MinYear, b.MaxYear

Consolidating multiple rows of XML values and primary keys into one SQL-queried table

I have a table named VPX_EVENT_ARG where a column, ARG_DATA, contains XML values.
Table 1
+----------+-------------------+----------+
| EVENT_ID | ARG_TYPE | ARG_DATA |
+----------+-------------------+----------+
| 7121001 | vim.vm.ConfigSpec | XML1 |
| 7121002 | vim.vm.ConfigSpec | XML2 |
| 7121003 | vim.vm.ConfigSpec | XML3 |
+----------+-------------------+----------+
XML1, XML2 and XML3 are XML values. They are too long to type in the table. Here are the real values. Actually they appear in one line.
XML1, for example,
<obj xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:vim25" versionId="5.5" xsi:type="VirtualMachineConfigSpec"><changeVersion>2015-09-24T10:02:53.866694Z</changeVersion><files><vmPathName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test.vmx</vmPathName></files><deviceChange><operation>remove</operation><device xsi:type="VirtualDisk"><key>2003</key><deviceInfo><label>Hard disk 4</label><summary>20,971,520 KB </summary></deviceInfo><backing xsi:type="VirtualDiskFlatVer2BackingInfo"><fileName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test_2.vmdk</fileName><diskMode>persistent</diskMode><split>false</split><writeThrough>false</writeThrough><thinProvisioned>false</thinProvisioned><uuid>6000C29b-e652-b5fe-76fa-18f6de988807</uuid><contentId>5bd085f0f9391346751e1e7efffffffe</contentId><digestEnabled>false</digestEnabled></backing><controllerKey>1000</controllerKey><unitNumber>3</unitNumber><capacityInKB>20971520</capacityInKB><shares><shares>1000</shares><level>normal</level></shares><storageIOAllocation><limit>-1</limit><shares><shares>1000</shares><level>normal</level></shares></storageIOAllocation></device></deviceChange></obj>
I will separate XML1 into multiple lines so that it will be easier to read.
<obj xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:vim25" versionId="5.5" xsi:type="VirtualMachineConfigSpec"><changeVersion>2015-09-24T10:02:53.866694Z</changeVersion><files><vmPathName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test.vmx</vmPathName></files>
<deviceChange>
<operation>remove</operation>
<device xsi:type="VirtualDisk">
<key>2003</key>
<deviceInfo>
<label>Hard disk 4</label>
<summary>20,971,520 KB </summary>
</deviceInfo>
<backing xsi:type="VirtualDiskFlatVer2BackingInfo">
<fileName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test_2.vmdk
</fileName>
<diskMode>persistent</diskMode>
<split>false</split>
<writeThrough>false</writeThrough>
<thinProvisioned>false</thinProvisioned>
<uuid>6000C29b-e652-b5fe-76fa-18f6de988807</uuid>
<contentId>5bd085f0f9391346751e1e7efffffffe</contentId>
<digestEnabled>false</digestEnabled>
</backing>
<controllerKey>1000</controllerKey>
<unitNumber>3</unitNumber>
<capacityInKB>20971520</capacityInKB>
<shares><shares>1000</shares><level>normal</level></shares><storageIOAllocation><limit>-1</limit><shares><shares>1000</shares><level>normal</level></shares></storageIOAllocation>
</device>
</deviceChange>
</obj>
I would like to extract XML1 into tables using an MSSQL query.
DECLARE #xml XML
SET #xml = (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001); --EVENT_ID is fixed.
WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM #xml.nodes('/NS:obj/NS:deviceChange') data(ref)
Well, I get this as a result.
Table 2
+------+-----------+-----------+
| unit | operation | newSizeKB |
+------+-----------+-----------+
| 1 | edit | 24117248 |
| 2 | edit | 108003328 |
| 3 | add | 20971520 |
+------+-----------+-----------+
You can see that Table 2 is just the result of the first row in Table 1. Not even the first row, it is just ARG_DATA on the first row which fixes EVENT_ID as well. I wish anyone can help.
Question 1: I would like to consolidate multiple rows of XML values into one table without fixing EVENT_IDs and put EVENT_IDs into a column too. Please assume that VPX_EVENT_ARG table contains hundreds of rows.
Table3
+----------+------+-----------+-----------+
| EVENT_ID | unit | operation | newSizeKB |
+----------+------+-----------+-----------+
| 7121001 | 1 | edit | 24117248 |
| 7121001 | 2 | edit | 108003328 |
| 7121001 | 3 | add | 20971520 |
| 7121002 | 1 | edit | 1048576 |
| 7121002 | 3 | edit | 52428800 |
| 7121003 | 3 | edit | 125829120 |
| 7121003 | 5 | remove | 83886080 |
+----------+------+-----------+-----------+
Question 2: Is there a way to use the query without setting XML? I need to set XML for the query, then I can use nodes().
SET #xml = (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001);
.
.
FROM #xml.nodes('/NS:obj/NS:deviceChange') data(ref)
I wonder if it can be done like this.
FROM (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001).nodes('/NS:obj/NS:deviceChange') data(ref)
Error: Incorrect syntax near '.'. Expecting AS, ID, or QUOTED_ID.
I really want to use such a result as Table3 to join the table I have queried before. I have hard time dealing with XML but stuck at this so long. By the way, I cannot update the table; it is restricted.
Of couse, you can. Use cross apply on your nodes:
;WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
v.EVENT_ID,
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM VPX_EVENT_ARG AS v
CROSS APPLY v.ARG_DATA.nodes('/NS:obj/NS:deviceChange') data(ref)
If your column is not XML, we need a subquery to convert it:
;WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
v.EVENT_ID,
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM
(
SELECT
c.EVENT_ID,
c.ARG_TYPE,
CONVERT(xml,c.ARG_DATA) AS ARG_DATA,
c.ARG_ID
FROM VPX_EVENT_ARG AS c
) AS v
CROSS APPLY v.ARG_DATA.nodes('/NS:obj/NS:deviceChange') data(ref)

Update second column into one

I would like to update multiple colums into one column of an another table:
I am using MS SQl Server
Table 1:
Num | ColumnA | ColumnB |
--------------------------
1 | Peter | Mueller |
1 | Jonny | Corleone |
2 | John | Cohn |
1 | Sarah | Wood |
Now I want to update ColumnA and ColumnB into ColumA of Table2 like this:
Table2:
Num | ColumnC |
----------------------------------------------------
1 | Peter, Mueller, Jonny, Corleone, Sarah, Wood |
2 | John, Cohn |
It is not good practice to store multiple values in a single column. If you need to do this though, then try this:
SELECT adress,
num,
Stuff((SELECT ',' + ColumnA + ',' + ColumnB
FROM table1 a
WHERE a.Num = b.Num
FOR xml path('')), 1, 1, '')
FROM table1 b
GROUP BY num

Convert XML Nodes To Rows in SQL Server

Consider the below sample:
declare #somexml as xml
set #somexml = '
<Settings>
<Users>
<ID>1</ID>
<ID>2</ID>
<ID>3</ID>
<ID>4</ID>
<ID>5</ID>
</Users>
</Settings>'
The above XML has some ID values that I need to convert to rows of data that can be used in a temp table to perform joins against.
I can't quite get the syntax correct, I've tried a number of samples that I've come across:
SELECT T.r.value('.','int') as id
FROM #somexml.nodes('/Settings/Users') T(r)
Returns:
|ID |
|------|
|12345 |
The following:
SELECT T.r.query('.') as id
from #somexml.nodes('/Settings/Users/ID') as T(r)
Returns:
|ID |
|-----------|
|<ID>1</ID> |
|<ID>2</ID> |
|<ID>3</ID> |
|<ID>4</ID> |
|<ID>5</ID> |
I'm getting close, but I want to remove the XML tags to just leave the ID values like so:
|ID |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
Here's a fiddle if you want to play/solve that way: SQL Fiddle
Any help is appreciated as always.
Replace T.r.query('.') as id with T.r.value('.', 'INT') as id
SELECT T.r.value('.','int') as id
FROM #somexml.nodes('/Settings/Users/ID') T(r)

Find primary key from one table in comma separated list

I've been given the task at work of creating a report based on a very poorly designed table structure.
Consider the following two tables. They contain techniques that each person likes to perform at each gym. Keep in mind that a unique person may show up on multiple rows in the PERSONNEL table:
PERSONNEL
+-----+-----+-------+--------+-----------+
| ID | PID | Name | Gym | Technique |
+-----+-----+-------+--------+-----------+
| 1 | 122 | Bob | GymA | 2,3,4 |
+-----+-----+-------+--------+-----------+
| 2 | 131 | Mary | GymA | 1,2,4 |
+-----+-----+-------+--------+-----------+
| 3 | 122 | Bob | GymB | 1,2,3 |
+-----+-----+-------+--------+-----------+
TECHNIQUES
+-----+------------+
| ID | Technique |
+-----+------------+
| 1 | Running |
+-----+------------+
| 2 | Walking |
+-----+------------+
| 3 | Hopping |
+-----+------------+
| 4 | Skipping |
+-----+------------+
What I am having trouble coming up with is a MSSQL query that will reliably give me a listing of every person in the table that is performing a certain technique.
For instance, let's say that I want a listing of every person that likes skipping. The desired results would be:
PREFERS_SKIPPING
+-----+-------+--------+
| PID | Name | Gym |
+-----+-------+--------+
| 122 | Bob | GymA |
+-----+-------+--------+
| 131 | Mary | GymA |
+-----+-------+--------+
Likewise hopping:
PREFERS_HOPPING
+-----+-------+--------+
| PID | Name | Gym |
+-----+-------+--------+
| 122 | Bob | GymA |
+-----+-------+--------+
| 122 | Bob | GymB |
+-----+-------+--------+
I can break out the strings easily in ColdFusion, but that isn't an option due to the size of the PERSONNEL table. Can anyone help?
I think this query looks cleaner:
SELECT p.*,
t.Technique as ParsedTechnique
FROM Personnel p
JOIN Techniques t
ON CHARINDEX((','+CAST(t.id as varchar(10))+','), (','+p.technique+',')) > 0
WHERE t.id ='1';
You can just change the WHERE t.id = to whatever TechniqueId you need.
Fiddle Here
Using this function
Create FUNCTION F_SplitAsIntTable
(
#txt varchar(max)
)
RETURNS
#tab TABLE
(
ID int
)
AS
BEGIN
declare #i int
declare #s varchar(20)
Set #i = CHARINDEX(',',#txt)
While #i>1
begin
set #s = LEFT(#txt,#i-1)
insert into #tab (id) values (#s)
Set #txt=RIGHT(#txt,Len(#txt)-#i)
Set #i = CHARINDEX(',',#txt)
end
insert into #tab (id) values (#txt)
RETURN
END
You can query like this
declare #a Table (id int,Name varchar(10),Kind Varchar(100))
insert into #a values (1,'test','1,2,3,4'),(2,'test2','1,2,3,5'),(3,'test3','3,5')
Select a.ID,Name
from #a a
cross apply F_SplitAsIntTable(a.Kind) b
where b.ID=2
One of the problems you have to prevent is prevent "1" from matching "10" and "11". For this, you want to be sure that all values are delimited by the separator (in this case a comma).
Here is a method using like that should work effectively (although performance will not be so great):
SELECT p.*, t.Technique as ParsedTechnique
FROM Personnel p join
Techniques t
on ','+p.technique+',' like '%,'+cast(t.id as varchar(255))+',%'
WHERE t.id = 1;
If performance is an issue, then fix your data structure an include a PersonTechniques table so you can do a proper join.
The first comment under the question provided the link to the answer. Here's what I ended up going with:
WHERE
p.Technique LIKE '%,29,%' --middle
OR
p.Technique LIKE '29,%' --start
OR
p.Technique LIKE '%,29' --end
OR
p.Technique = '29' --single (good point by Cheran S in comment)
At initial glance I thought it wouldn't work, but clever use of % made it not match ids like 129, etc.

Resources