Related
Context:
I have a view in SQL Server that tracks parameters a user inputs when they run an SSRS report (ReportServer.dbo.ExecutionLog). About 50 report parameters are saved as a string in a single column with ntext datatype. I would like to break this single column up into multiple columns for each parameter.
Details:
I query the report parameters like this:
SELECT ReportID, [Parameters]
FROM ReportServer.dbo.ExecutionLog
WHERE ReportID in (N'redacted')
and [Status] in (N'rsSuccess')
ORDER BY TimeEnd DESC
And here's a small subset of what the results look like:
alpha=123&bravo=9%2C33%2C76%2C23&charlie=91&delta=29&echo=11%2F2%2F2018%2012%3A00%3A00%20AM&foxtrot=11%2F1%2F2030%2012%3A00%3A00%20AM
Quesitons:
How can I get the results to look like this:
SQL Server 2017 is Python friendly. Is Python a better language to use in this scenario just for parsing purposes?
I've seen similar topics posted here, here & here. The parameters are dynamic so parsing via SQL string functions that involve counting characters doesn't apply. This question is relevant to more people than just me because there's a large population of people using SSRS. Tracking & formatting parameters in a more digestible way is valuable for all users of SSRS.
Here is a way using the built in STRING_SPLIT. I'm just not sure what the logic is for the stuff AFTER the date, so I would discarded it but I left it for you to decide.
DEMO
declare #table table (ReportID int identity(1,1), [Parameters] varchar(8000))
insert into #table
values
('alpha=123&bravo=9%2C33%2C76%2C23&charlie=91&delta=29&echo=11%2F2%2F2018%2012%3A00%3A00%20AM&foxtrot=11%2F1%2F2030%2012%3A00%3A00%20AM')
,('alpha=457893&bravo=9%2C33%2C76%2C23&charlie=91&delta=29&echo=11%2F2%2F2018%2012%3A00%3A00%20AM&foxtrot=11%2F1%2F2030%2012%3A00%3A00%20AM')
select
ReportID
,[Parameters]
,alpha = max(iif(value like 'alpha%',substring(value,charindex('=',value) + 1,99),null))
,bravo = max(iif(value like 'bravo%',substring(value,charindex('=',value) + 1,99),null))
,charlie = max(iif(value like 'charlie%',substring(value,charindex('=',value) + 1,99),null))
,delta = max(iif(value like 'delta%',substring(value,charindex('=',value) + 1,99),null))
,echo = max(iif(value like 'echo%',substring(value,charindex('=',value) + 1,99),null))
,foxtrot = max(iif(value like 'foxtrot%',substring(value,charindex('=',value) + 1,99),null))
from #table
cross apply string_split(replace(replace([Parameters],'%2C',','),'%2F','/'),'&')
group by ReportID, [Parameters]
Or, if they aren't static you can use a dynamic pivot. It'll take some massaging to get your columns in the correct order.
DEMO
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(substring([value],0,charindex('=',[value])))
from myTable
cross apply string_split(replace(replace([Parameters],'%2C',','),'%2F','/'),'&')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols
set #query = 'SELECT ReportID, ' + #cols + ' from
(
select ReportID
, ColName = substring([value],0,charindex(''='',[value]))
, ColVal = substring([value],charindex(''='',[value]) + 1,99)
from myTable
cross apply string_split(replace(replace([Parameters],''%2C'','',''),''%2F'',''/''),''&'')
) x
pivot
(
max(ColVal)
for ColName in (' + #cols + ')
) p '
execute(#query)
Split the string on the ampersand character.
Further split each row into two columns on the equals character.
In the second column, replace %2C with the comma character, and %2F with the forward-slash character, and so on with any other replacements as needed.
Use a dynamic-pivot to query the above in the format that you want.
Here's a method that starts with a lot of replaces.
To url-decode the string and transform it into an XML type.
Then it uses the XML functions to get the values for the columns.
Example snippet:
declare #Table table ([Parameters] varchar(200));
insert into #Table ([Parameters]) values
('alpha=123&bravo=9%2C33%2C76%2C23&charlie=91&delta=29&echo=11%2F2%2F2018%2012%3A00%3A00%20AM&foxtrot=11%2F1%2F2030%2012%3A00%3A00%20AM');
select
x.query('/x[key="alpha"]/val').value('.', 'int') as alpha,
x.query('/x[key="bravo"]/val').value('.', 'varchar(30)') as bravo,
x.query('/x[key="charlie"]/val').value('.', 'varchar(30)') as charlie,
x.query('/x[key="delta"]/val').value('.', 'varchar(30)') as delta,
convert(date, x.query('/x[key="echo"]/val').value('.', 'varchar(30)'), 103)as echo,
convert(date, x.query('/x[key="foxtrot"]/val').value('.', 'varchar(30)'), 103) as foxtrot
from #Table
cross apply (select cast('<x><key>'+
replace(replace(replace(replace(replace(
replace([Parameters],
'%2C',','),
'%2F','/'),
'%20',' '),
'%3A',':'),
'=','</key><val>'),
'&','</val></x><x><key>')
+'</val></x>' as XML) as x) ca
Test on db<>fiddle here
I have a question about SQL Server: I have a database column with a pattern which is like this:
up to 10 digits
then a comma
up to 10 digits
then a semicolon
e.g.
100000161, 100000031; 100000243, 100000021;
100000161, 100000031; 100000243, 100000021;
and I want to extract within the pattern the first digits (up to 10) (1.) and then a semicolon (4.)
(or, in other words, remove everything from the semicolon to the next semicolon)
100000161; 100000243; 100000161; 100000243;
Can you please advice me how to establish this in SQL Server? Im not very familiar with regex and therefore have no clue how to fix this.
Thanks,
Alex
Try this
Declare #Sql Table (SqlCol nvarchar(max))
INSERT INTO #Sql
SELECT'100000161,100000031;100000243,100000021;100000161,100000031;100000243,100000021;'
;WITH cte
AS (SELECT Row_number()
OVER(
ORDER BY (SELECT NULL)) AS Rno,
split.a.value('.', 'VARCHAR(1000)') AS Data
FROM (SELECT Cast('<S>'
+ Replace( Replace(sqlcol, ';', ','), ',',
'</S><S>')
+ '</S>'AS XML) AS Data
FROM #Sql)AS A
CROSS apply data.nodes('/S') AS Split(a))
SELECT Stuff((SELECT '; ' + data
FROM cte
WHERE rno%2 <> 0
AND data <> ''
FOR xml path ('')), 1, 2, '') AS ExpectedData
ExpectedData
-------------
100000161; 100000243; 100000161; 100000243
I believe this will get you what you are after as long as that pattern truly holds. If not it's fairly easy to ensure it does conform to that pattern and then apply this
Select Substring(TargetCol, 1, 10) + ';' From TargetTable
You can take advantage of SQL Server's XML support to convert the input string into an XML value and query it with XQuery and XPath expressions.
For example, the following query will replace each ; with </b><a> and each , to </a><b> to turn each string into <a>100000161</a><a>100000243</a><a />. After that, you can select individual <a> nodes with /a[1], /a[2] :
declare #table table (it nvarchar(200))
insert into #table values
('100000161, 100000031; 100000243, 100000021;'),
('100000161, 100000031; 100000243, 100000021;')
select
xCol.value('/a[1]','nvarchar(200)'),
xCol.value('/a[2]','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
-------------------------
A1 A2
100000161 100000243
100000161 100000243
value extracts a single value from an XML field. nodes returns a table of nodes that match the XPath expression. The following query will return all "keys" :
select
a.value('.','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
cross apply xCol.nodes('a') as y(a)
where a.value('.','nvarchar(200)')<>''
------------
100000161
100000243
100000161
100000243
With 200K rows of data though, I'd seriously consider transforming the data when loading it and storing it in indivisual, indexable columns, or add a separate, related table. Applying string manipulation functions on a column means that the server can't use any covering indexes to speed up queries.
If that's not possible (why?) I'd consider at least adding a separate XML-typed column that would contain the same data in XML form, to allow the creation of an XML index.
Here is my column looks like, named Sets.
My Column:
{C=Pass, D=Fail, E=Pass, F= Pass, G=Pass}
{C=Pass, D=Fail, E=NoApplication}
Expected
{C=Pass, E=Pass, F= Pass, G=Pass}
{C=Pass, E=NoApplication}
I wish to delete the "Fail" in my column.
Please help.
You can try this
UPDATE Use my approach within an updateable CTE
Changes the value in your table. Just replace #tbl with your actual table's name and `YourColumn with the column's name. Carefull with real data!
DECLARE #tbl TABLE(YourColumn VARCHAR(100));
INSERT INTO #tbl VALUES
('{C=Pass, D=Fail, E=Pass, F= Pass, G=Pass}')
,('{C=Pass, D=Fail, E=NoApplication}');
WITH Casted AS
(
SELECT CAST('<x>' + REPLACE(SUBSTRING(t.YourColumn,2,LEN(YourColumn)-2),', ','</x><x>') + '</x>' AS xml) AsXml
,t.YourColumn AS OldValue
FROM #tbl AS t
)
,UpdateableCTE AS
(
SELECT '{'
+ STUFF((
SELECT ', ' + a.value('.','nvarchar(100)')
FROM Casted.AsXml.nodes('/x') AS A(a)
WHERE RIGHT(a.value('.','nvarchar(100)'),4)<>'Fail'
FOR XML PATH('')
),1,2,'')
+ '}' AS NewValue
,OldValue
FROM Casted
)
UPDATE UpdateableCTE SET OldValue=NewValue;
SELECT * FROM #tbl;
The string is splitted into its elements. Then it is re-concatenated without the ones with "Fail".
You may looking for Update Command
UPDATE TableName
SET Name = CASE WHEN CHARINDEX('=Fail,',Name ) > 1
THEN STUFF(Name,CHARINDEX('=Fail,',Name)-1,LEN('=Fail,')+1,'')
ELSE Name
END
How can I return the values of MainEmail in the query below, delimited by commas and still count MDCselect?
declare #MainHospital varchar(50)='hospital 1'
select distinct mainhospital , f.Item, count(*) Count
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
To be clear the above returns this: http://i.imgur.com/F1oPU6P.jpg
So there were 3 separate entries/people that selected "02-Eye". I want to list out their emails(MainEmail) comma delimited. Please let me know if I am unclear.
Assuming from your use of CROSS APPLY that you are using SQL Server, and that it is at least version 2005, you can use XML to do the concatenation as follows:
declare #MainHospital varchar(50)='hospital 1';
select mainhospital , f.Item, count(*) Count
,Stuff(
(select distinct ', ' + m.MainEmail
from SurveyPicList m
where m.MainHospital = #MainHospital
and ':' + m.MDCselect + ':' like '%:' + f.Item + ':%'
FOR XML PATH ('')),
1, 2, '') as Emails
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
From the name I am assuming that splitstrings splits its first argument into items separated by its second argument. Hence I used like to check for f.Item in m.MDCselect in the WHERE condition of the subselect. Actually, what this WHERE condition is doing is collecting all the rows from another instance of the same table that match one record in the final grouped output.
I currently have the following select statement, but I wish to move to full text search on the Keywords column. How would I re-write this to use CONTAINS?
SELECT MediaID, 50 AS Weighting
FROM Media m JOIN #words w ON m.Keywords LIKE '%' + w.Word + '%'
#words is a table variable filled with words I wish to look for:
DECLARE #words TABLE(Word NVARCHAR(512) NOT NULL);
If you are not against using a temp table, and EXEC (and I realize that is a big if), you could do the following:
DECLARE #KeywordList VARCHAR(MAX), #KeywordQuery VARCHAR(MAX)
SELECT #KeywordList = STUFF ((
SELECT '"' + Keyword + '" OR '
FROM FTS_Keywords
FOR XML PATH('')
), 1, 0, '')
SELECT #KeywordList = SUBSTRING(#KeywordList, 0, LEN(#KeywordList) - 2)
SELECT #KeywordQuery = 'SELECT RecordID, Document FROM FTS_Demo_2 WHERE CONTAINS(Document, ''' + #KeywordList +''')'
--SELECT #KeywordList, #KeywordQuery
CREATE TABLE #Results (RecordID INT, Document NVARCHAR(MAX))
INSERT INTO #Results (RecordID, Document)
EXEC(#KeywordQuery)
SELECT * FROM #Results
DROP TABLE #Results
This would generate a query like:
SELECT RecordID
,Document
FROM FTS_Demo_2
WHERE CONTAINS(Document, '"red" OR "green" OR "blue"')
And results like this:
RecordID Document
1 one two blue
2 three red five
If CONTAINS allows a variable or column, you could have used something like this.
SELECT MediaID, 50 AS Weighting
FROM Media m
JOIN #words w ON CONTAINS(m.Keywords, w.word)
However, according to Books Online for SQL Server CONTAINS, it is not supported. Therefore, no there is no way to do it.
Ref: (column_name appears only in the first param to CONTAINS)
CONTAINS
( { column_name | ( column_list ) | * }
,'<contains_search_condition>'
[ , LANGUAGE language_term ]
)