split data from long string text then update each data - sql-server

I am trying to create a bit complex stored procedure in ms sql database. My main focus is take a input of long string text then split the data by characters and update that data where matching. In detail: DataString is the input long text string which will contain value like bellow example.
'-' is separator between name of data type and its value
',' is separator between two different kind of value
':' is separator between two set of data, each set of data separates by :
Now can u tell me how can i grab each data from long string and insert them where matching? Ask question if you still need to know something. Thanks in advance
Example of long text string:
ASIN-NsQf8,type-0,Price-7,IsPrime-1:ASIN-fD5tsQ,type-1,Price-13,IsPrime-0:ASIN-tvQtsu,type-1,Price-14,IsPrime-1
The Unfinished SQL Code:
CREATE PROCEDURE dbo.lk_UpdateMatchingDataOfThirdparty
#DataString VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
UPDATE ThirdPartyData SET Price = #value_get_from_string, IsPrime = #value_get_from_string, DateChecked = GETDATE()
WHERE ASIN = '#value_get_from_string' AND type = '#value_get_from_string';
END
GO

Unfortunately string_split() does NOT return a sequence number. So if you are open to a an alternative split/parse function which is also performant.
Note: I made assumptions with the try_convert() portion
Example
Declare #DataString varchar(max) = 'ASIN-NsQf8,type-0,Price-7,IsPrime-1:ASIN-fD5tsQ,type-1,Price-13,IsPrime-0:ASIN-tvQtsu,type-1,Price-14,IsPrime-1'
UPDATE A
SET Price = B.Price
, IsPrime = B.IsPrime
, DateChecked = GETDATE()
From ThirdPartyData A
Join (
Select [ASIN] =replace(max(case when B.RetVal Like 'ASIN-%' then B.RetVal end),'ASIN-','')
,[Type] =try_convert(int,replace(max(case when B.RetVal Like 'type-%' then B.RetVal end),'type-',''))
,[Price] =try_convert(money,replace(max(case when B.RetVal Like 'price-%' then B.RetVal end),'price-',''))
,[IsPrime]=try_convert(bit,replace(max(case when B.RetVal Like 'IsPrime-%' then B.RetVal end),'IsPrime-',''))
From [dbo].[tvf-Str-Parse](#DataString,':') A
Cross Apply [dbo].[tvf-Str-Parse](A.RetVal,',') B
Group By A.RetSeq
) B
on A.[ASIN]=B.[ASIN] and A.[Type]=B.[Type]
If it Helps with the Visualization, the sub-query Returns
ASIN Type Price IsPrime
tvQtsu 1 14.00 1
NsQf8 0 7.00 1
fD5tsQ 1 13.00 0
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
EDIT: If you want the 2016+ string_split() approach
Select [ASIN] =replace(max(case when Value Like 'ASIN-%' then Value end),'ASIN-','')
,[Type] =try_convert(int,replace(max(case when Value Like 'type-%' then Value end),'type-',''))
,[Price] =try_convert(money,replace(max(case when Value Like 'price-%' then Value end),'price-',''))
,[IsPrime]=try_convert(bit,replace(max(case when Value Like 'IsPrime-%' then Value end),'IsPrime-',''))
From (
Select A.RN
,B.*
From ( Select RN=Row_Number() over (Order by (select null)),* from string_split(#DataString,':') ) A
Cross Apply string_split(A.Value,',') B
) A
Group By RN

Related

Ordering slash separated list

I have column 'GUI_KVLevelName'.
It has data as :
500.00/69.00/34.50
500.00/400.00/138.00
500.00/69.00
500.00/400.00
500.00/345.00/34.50
57.00/8.30
I want to use order by with this. It is a varchar column but i want to order it as numeric. So how could i use order by this column?
Here is a little cheat that may work
By removing the decimal points, we are converting the individual values into a larger INT. This is then converted to and then sorted via a hierarchyid type
Example
Declare #YourTable Table ([GUI_KVLevelName] varchar(50))
Insert Into #YourTable Values
('500.00/69.00/34.50')
,('500.00/400.00/138.00')
,('500.00/69.00')
,('500.00/400.00')
,('500.00/345.00/34.50')
,('0.45/5.30') -- Added for leading zero
,('0.10/9.30') -- Added for leading zero
Select *
From #YourTable
Order By try_convert(hierarchyid,replace('/'+replace([GUI_KVLevelName],'.','1')+'/','/0','/'))
Returns
GUI_KVLevelName
0.10/9.30
0.45/5.30
500.00/69.00
500.00/69.00/34.50
500.00/345.00/34.50
500.00/400.00
500.00/400.00/138.00
Second answer in case you can't GTD two decimal places.
Example
Declare #YourTable Table ([GUI_KVLevelName] varchar(50))
Insert Into #YourTable Values
('500.00/69.00/34.50')
,('500.00/400.00/138.00')
,('500.00/69.00')
,('500.00/400.00')
,('500.00/345.00/34.50')
,('0.45/5.30')
,('0.1/9.30')
,('0.01/9.30')
,('0.05/9.30')
,('1.3/4.30')
Select A.*
From #YourTable A
Cross Apply (
Select Pos1 = xDim.value('/x[1]','money')
,Pos2 = xDim.value('/x[2]','money')
,Pos3 = xDim.value('/x[3]','money')
,Pos4 = xDim.value('/x[4]','money')
,Pos5 = xDim.value('/x[5]','money')
,Pos6 = xDim.value('/x[6]','money')
,Pos7 = xDim.value('/x[7]','money')
From (Select Cast('<x>' + replace([GUI_KVLevelName],'/','</x><x>')+'</x>' as xml) as xDim) as A
) B
Order By Pos1,Pos2,Pos3,Pos4,Pos5,Pos6,Pos7
Returns
GUI_KVLevelName
0.01/9.30
0.05/9.30
0.1/9.30
0.45/5.30
1.3/4.30
500.00/69.00
500.00/69.00/34.50
500.00/345.00/34.50
500.00/400.00
500.00/400.00/138.00

Searching stored procedure and views but ignoring comments

I have a query as
select
definition
from
sys.objects so
join
sys.sql_modules ssmsp on so.[object_id] = ssmsp.[object_id]
where
so.type in ('v', 'p')
where
definition like '%exec%'
While populating records, gets populated from comments also. How can I avoid getting filtered from comments?
Is there any solution?
Thanks
I think this is going to be nearly impossible to achieve in a single query.
Bear in mind that [definition] has no formatting, no line breaks, etc. the code is a single line (copy one and paste it into the editor).
If a comment starts with -- then where does it end? you have no way of knowing.
It is a little easier with /* because you can find the corresponding */ but there is still the added complication of multiple occurrences of the search string.
You might have a little more luck using PATINDEX and specifying a case-sensitive version of your collation (if you have a case insensitive database) and for example you know you only want occurrences of EXEC and not "execute" e.g. WHERE patindex('%EXEC%',defintion COLLATE SQL_Latin1_General_CP1_CS_AS) > 0
First for a fast varchar(max) string "splitter". Below is a hacked version of Jeff Moden's delimitedSplit8K.
IF OBJECT_ID('dbo.DelimitedSplit2B','IF') IS NOT NULL DROP FUNCTION dbo.DelimitedSplit2B;
GO
CREATE FUNCTION dbo.DelimitedSplit2B
(
#pString varchar(max),
#pDelimiter char(1)
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH L1(N) AS
(
SELECT N
FROM (VALUES
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(N)
), --216 values
cteTally(N) AS
(
SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(#pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM L1 a CROSS JOIN L1 b CROSS JOIN L1 c
--2,176,782,336 rows: enough to handle 2,147,483,647 characters (the varchar(max) limit)
),
cteStart(N1) AS
(
SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(#pString,t.N,1) = #pDelimiter OR t.N = 0)
)
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
Item = SUBSTRING(#pString,s.N1,ISNULL(NULLIF((LEAD(s.N1,1,1)
OVER (ORDER BY s.N1) - 1),0)-s.N1,DATALENGTH(ISNULL(#pString,1))))
FROM cteStart s;
Next for the function to search your DDL
create function dbo.SearchObjectDDLFor (#searchstring varchar(100), #maxLen int)
returns table as return
select objectName, lineNumber, lineText
from
(
select
objectName = ss.[name]+'.'+so.[name],
lineNumber = itemnumber,
lineText = substring(t.item, 1, isnull(nullif(charindex('--', t.item),0)-1, 8000)),
isLongComment =
sum
( -- this will assign a 1 for everything
case when t.item like '/*%' then 1
when t.item like '*/%'then -1
else 0 end
) over (partition by so.[name] order by itemnumber)
from sys.objects so
join sys.sql_modules ssmsp on so.[object_id] = ssmsp.[object_id]
join sys.schemas ss on so.schema_id = ss.schema_id
cross apply dbo.delimitedSplit2B(definition, char(10))
cross apply (values (rtrim(ltrim(replace(item,char(13),''))))) t(item)
where so.type in ('v', 'p')
and len(definition) < isnull(#maxLen,100000) -- character limit is #maxLen (100K default)
) splitLines
where isLongComment = 0 and lineText not like '--%' and lineText <> '*/'
and lineText like '%'+#searchstring+'%';
This function:
Accepts an input string to search for (#searchstring)
Splits your objects into lines
Returns only the portions of the line not part of a comment
filters the lines created in step3 for ones that contain #searchstring and returns the ObjectName (.), Line number and Text.
Caveats:
I just threw this together quick so forgive any errors
A t-sql splitter that accepts [n]varchar(max) will be slow. A CLR splitter would likely be faster but we're not talking about millions of rows. That said, you can speed it up by filtering the number of lines with #maxLen. #maxlen says "ignore and objects with more that #maxLen number of lines." When null it will search objects up to 100K lines long (but this can be adjusted).
This function address comments scenarios where comments look have "--" any where in the string: and scenarios where the comment is nested between "/" and "\ on separate lines.
a few scenarios which require more coding to suppress the comments include:
.
select col1, /* skipping col2 for now */ col3, col4
and
/*********
comments here
*********/
Examples:
select * from dbo.SearchObjectDDLFor('nocount', NULL);
select * from dbo.SearchObjectDDLFor('nocount', 2000);
Results will look something like :

TSQL/SQL Server - table function to parse/split delimited string to multiple/separate columns

So, my first post is less a question and more a statement! Sorry.
I needed to convert delimited strings stored in VarChar table columns to multiple/separate columns for the same record. (It's COTS software; so please don't bother telling me how the table is designed wrong.) After searching the internet ad nauseum for how to create a generic single line call to do that - and finding lots of how not to do that - I created my own. (The name is not real creative.)
Returns: A table with sequentially numbered/named columns starting with [Col1]. If an input value is not provided, then an empty string is returned. If less than 32 values are provided, all past the last value are returned as null. If more than 32 values are provided, they are ignored.
Prerequisites: A Number/Tally Table (luckily, our database already contained 'dbo.numbers').
Assumptions: Not more than 32 delimited values. (If you need more, change "WHERE tNumbers.Number BETWEEN 1 AND XXX", and add more prenamed columns ",[Col33]...,[ColXXX]".)
Issues: The very first column always gets populated, even if #InputString is NULL.
--======================================================================
--SMOZISEK 2017/09 CREATED
--======================================================================
CREATE FUNCTION dbo.fStringToPivotTable
(#InputString VARCHAR(8000)
,#Delimiter VARCHAR(30) = ','
)
RETURNS TABLE AS RETURN
WITH cteElements AS (
SELECT ElementNumber = ROW_NUMBER() OVER(PARTITION BY #InputString ORDER BY (SELECT 0))
,ElementValue = NodeList.NodeElement.value('.','VARCHAR(1022)')
FROM (SELECT TRY_CONVERT(XML,CONCAT('<X>',REPLACE(#InputString,#Delimiter,'</X><X>'),'</X>')) AS InputXML) AS InputTable
CROSS APPLY InputTable.InputXML.nodes('/X') AS NodeList(NodeElement)
)
SELECT PivotTable.*
FROM (
SELECT ColumnName = CONCAT('Col',tNumbers.Number)
,ColumnValue = tElements.ElementValue
FROM DBO.NUMBERS AS tNumbers --DEPENDENT ON ANY EXISTING NUMBER/TALLY TABLE!!!
LEFT JOIN cteElements AS tElements
ON tNumbers.Number = tElements.ElementNumber
WHERE tNumbers.Number BETWEEN 1 AND 32
) AS XmlSource
PIVOT (
MAX(ColumnValue)
FOR ColumnName
IN ([Col1] ,[Col2] ,[Col3] ,[Col4] ,[Col5] ,[Col6] ,[Col7] ,[Col8]
,[Col9] ,[Col10],[Col11],[Col12],[Col13],[Col14],[Col15],[Col16]
,[Col17],[Col18],[Col19],[Col20],[Col21],[Col22],[Col23],[Col24]
,[Col25],[Col26],[Col27],[Col28],[Col29],[Col30],[Col31],[Col32]
)
) AS PivotTable
;
GO
Test:
SELECT *
FROM dbo.fStringToPivotTable ('|Height|Weight||Length|Width||Color|Shade||Up|Down||Top|Bottom||Red|Blue|','|') ;
Usage:
SELECT 1 AS ID,'Title^FirstName^MiddleName^LastName^Suffix' AS Name
INTO #TempTable
UNION SELECT 2,'Mr.^Scott^A.^Mozisek^Sr.'
UNION SELECT 3,'Ms.^Jane^Q.^Doe^'
UNION SELECT 5,NULL
UNION SELECT 7,'^Betsy^^Ross^'
;
SELECT SourceTable.*
,ChildTable.Col1 AS ColTitle
,ChildTable.Col2 AS ColFirst
,ChildTable.Col3 AS ColMiddle
,ChildTable.Col4 AS ColLast
,ChildTable.Col5 AS ColSuffix
FROM #TempTable AS SourceTable
OUTER APPLY dbo.fStringToPivotTable(SourceTable.Name,'^') AS ChildTable
;
No, I have not tested any plan (I just needed it to work).
Oh, yeah: SQL Server 2012 (12.0 SP2)
Comments? Corrections? Enhancements?
Here is my TVF. Easy to expand up to the 32 (the pattern is pretty clear).
This is a straight XML without the cost of the PIVOT.
Example - Notice the OUTER APPLY --- Use CROSS APPLY to Exclude NULLs
Select A.ID
,B.*
From #TempTable A
Outer Apply [dbo].[tvf-Str-Parse-Row](A.Name,'^') B
Returns
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse-Row] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
Where #String is not null
)
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[tvf-Str-Parse-Row]('John <test> Cappelletti',' ')

How to send a comma delimited string of integers as a parameter list in sql server?

In this sql server stored procedure query
SELECT HireResponseID,
HireResponse,
DateResponse,
Comments,
YearFileOpened,
file_number,
isCaseOpen,
last_update,
isConfidential,
date_created,
OurClient,
TheirClient,
ProjectName,
description,
lawyer_lastname,
lawyer_firstname,
Conflicts.ConflictID
FROM Hire_Response,
Conflicts,
Lawyers
WHERE Hire_Response.ConflictID=Conflicts.ConflictID
AND Lawyers.lawyerID=Conflicts.lawyerID
AND firmID = #FirmID
AND HireID IN #HireID
AND isStillaConflict = 1
ORDER BY file_number,
TheirClient,
OurClient,
lawyer_lastname,
lawyer_firstname
The parameter #HireID is a string of comma delimited integers (it doesn't have brackets around it). I want to check if the HireID integer is in the #HireID string. But I don't know how to parse this.
Can anyone help please?
Thanks
If I understand your question, you want to find rows where HireID is in the list #HireID. If the HireID is a consistent length, and the list is delimited, then you could use this:
AND #HireID LIKE '%'+CAST(HireID AS VARCHAR(5))+'%'
You could also use CHARINDEX:
AND CHARINDEX(HireID,#HireID) > 0
Edit: To account for inconsistent length, you could use:
AND (#HireID LIKE '%'+CAST(HireID AS VARCHAR(5))+',%'
OR #HireID LIKE '%,'+CAST(HireID AS VARCHAR(5))+'%')
Try this one -
DECLARE #HireID VARCHAR(100)
SELECT #HireID = '2,18'
;WITH cte AS
(
SELECT ID = t.c.value('.', 'INT')
FROM (
SELECT txml = CAST('<t>' + REPLACE(#HireID, ',', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY txml.nodes('/t') AS t(c)
)
SELECT *
FROM Hire_Response
JOIN Conflicts ON Hire_Response.ConflictID = Conflicts.ConflictID
JOIN Lawyers ON Lawyers.lawyerID = Conflicts.lawyerID
WHERE firmID = #FirmID
AND isStillaConflict = 1
AND HireID in (SELECT ID FROM cte)
ORDER BY file_number, TheirClient, OurClient, lawyer_lastname, lawyer_firstname

Search for multiple values in xml column in SQL

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...
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
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
Is this too trivial?
SELECT * FROM Baskets WHERE BasketFruits LIKE '%<FID>1</FID>%' AND BasketFruits LIKE '%<FID>3</FID>%'

Resources