Is there anyway to find out if a particular table is being used by a report on the reporting server?
USE ReportServer
DECLARE #TEXTTOSEARCH AS VARCHAR(200)
SET #TEXTTOSEARCH = 'urtableorview'
;WITH XMLNAMESPACES
(DEFAULT 'http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition',
'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner' AS rd)
SELECT name
, x.value('CommandType[1]', 'VARCHAR(100)') AS CommandType
, x.value('CommandText[1]','VARCHAR(MAX)') AS CommandText
, x.value('DataSourceName[1]','VARCHAR(150)') AS DataSource
FROM (SELECT name,
CAST(CAST(content AS VARBINARY(MAX)) AS XML) AS reportXML
FROM Catalog
WHERE content IS NOT NULL AND type != 3) AS a
CROSS APPLY reportXML.nodes('/Report/DataSets/DataSet/Query') r(x)
WHERE x.value('CommandType[1]', 'VARCHAR(50)') IS NULL
AND x.value('CommandText[1]','VARCHAR(MAX)') LIKE '%' + #TEXTTOSEARCH + '%'
ORDER BY name
I found this similar but simpler query on a technet article by Ajit Kumar Thakur https://blogs.technet.microsoft.com/dbtechresource/2015/04/04/retrieve-ssrs-report-server-database-information/.
WITH Reports AS
(
SELECT *
, CONVERT( VARCHAR(MAX), CONVERT(VARBINARY(MAX), Content)) AS ReportContent
FROM Catalog
)
SELECT Name, [Path]
FROM Reports
WHERE ReportContent LIKE '%tablename%';
This is very useful when you need "to identify dependency [on] any table, procedure or function in any report. It extracts [the] XML content of each report, converts it to varchar and then search for given object [name]. Catalog table contains XML contents of all RDL files" in your report server.
Related
I have an SSIS package on server 1. It does a SQL query on SQL db located on server 2 via the OLEDB source sql command text. The query is:
SELECT * FROM PRODUCTS
WHERE PRODUCT_NAME IN (?)
This fails, since? is a scalar value and not a table. To fix this there are 2 options:
Use STRING_SPLIT
Create string split function
I can't use option 1 because although it is SQL server 2017, the DB compatibility level is set to 2008 (Level 100). STRING_SPLIT is supported only for a higher compatibility level. I'm not allowed to change this.
I can't use option 2 because I am not allowed to create any new custom functions on that database.
Is there a workaround? I have read about adding the custom function into the master DB, but unsure whether future SQL updates may reset it as user functions are not meant to be placed inside the master DB.
One way would be switch context to a database that does have the required compatibility level (tempdb below).
DECLARE #ProductNames VARCHAR(MAX) = ?
CREATE TABLE #ProductNames
(
PRODUCT_NAME VARCHAR(50) PRIMARY KEY
)
EXEC tempdb.sys.sp_executesql N'INSERT INTO #ProductNames SELECT DISTINCT value FROM STRING_SPLIT(#ProductNames, '','')',
N'#ProductNames VARCHAR(MAX)',
#ProductNames = #ProductNames;
SELECT *
FROM PRODUCTS
WHERE PRODUCT_NAME IN (SELECT pn.PRODUCT_NAME
FROM #ProductNames pn)
DROP TABLE #ProductNames
STRING_SPLIT alternative
As mentioned in the following answer T-SQL split string,
the following SQL query can replace the STRING_SPLIT function:
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value'
FROM
(
SELECT CAST ('<M>' + REPLACE(/*Comma separated value should be placed here*/, ',', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
Using OLE DB Source SQL Command
If you are using an OLE DB Source component, you can use the following SQL Command:
DECLARE #String varchar(100) = ?;
SELECT * FROM PRODUCTS
WHERE PRODUCT_NAME IN (
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value'
FROM
(
SELECT CAST ('<M>' + REPLACE(#String , ',', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
Test
Using the AdventureWorks2017 database, I used the following query to search for the person's information stored in the [Person].[Person] table while the filter is on the PersonType column:
DECLARE #String varchar(100) = ?;
SELECT * FROM [AdventureWorks2017].[Person].[Person]
WHERE PersonType IN (
SELECT LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) 'Value'
FROM
(
SELECT CAST ('<M>' + REPLACE(#String , ',', '</M><M>') + '</M>' AS XML) AS Data
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
)
In the OLE DB Source Editor, if we click on the Parameters button, a parameter is recognized. I will create a new variable and use it as a parameter as shown in the image below:
The variable data type should be set to String and the value is set to EM,SC which are both symbols used in the PersonType column.
Now, if we click on the Preview button in the OLE DB Source Editor, the accurate data is visualized.
You can load you string into an parameter object #ProductNames and then use that as a source and then inner join the product table in another data flow.
1st DF:
Script Component Source with #stringToParse as a variable. Also have output string in DF of ProductName.
Code:
string prods = Variables.stringToParse.Value;
string[] productNames = prods.Split(',');
foreach(string p in productNames)
{
OutputBuffer0.AddRow();
OutputBuffer0.ProductName = p;
}
Then load the results into a recordset destination mapped to #ProductNames.
2nd DF:
2 sources.
Product Table
Your recordset #ProductNames
do a merge join (inner) and you will have your records.
I need to get a list of parameters for a specific SSRS report and show all the possible drop down items available (along with Report parameter name, DataType, and Prompt).
I'm not sure of other options (if there are any). The query returns exactly what I need, it just takes too long to be useful (10-12 seconds). Is there another way to get these results or make this one faster?
USE ReportServer
DECLARE #dbname VARCHAR(25)
SET #dbname='DBName'
DECLARE #rptlistStr VARCHAR(MAX)
DECLARE #reportlist table ( path varchar(500), name varchar(500) )
insert into #reportlist
exec [ADA Master].[dbo].[spGetAdminReports] #dbname
set #rptlistStr = substring((SELECT ( ', ' + Name ) FROM #reportlist WHERE NAME = 'rptReport'
FOR XML PATH( '' )
), 3, 1000 )
SELECT NAME, PATH,
y.r.query ('for $s in *:ParameterValue/*:Value return concat(data($s),"|")') [DropDownItemValue]
, y.r.query ('for $s in *:ParameterValue/*:Label return concat(data($s),"|")') [DropDownItemLabel]
, x.r.value ('#Name', 'VARCHAR(100)') AS ReportParameterName
, x.r.value ('*:DataType[1]', 'VARCHAR(100)') AS DataType
, x.r.value ('*:AllowBlank[1]', 'VARCHAR(50)') AS AllowBlank
, x.r.value ('*:Prompt[1]', 'VARCHAR(100)') AS Prompt
, x.r.value ('*:Hidden[1]', 'VARCHAR(100)') AS Hidden
, x.r.value ('*:MultiValue[1]', 'VARCHAR(100)') AS MultiValue
FROM (
SELECT PATH
, NAME
, CAST(CAST(content AS VARBINARY(MAX)) AS XML) AS ReportXML
FROM ReportServer.dbo.Catalog
join master.dbo.ufn_SplitStringArray(#rptlistStr,',') a on NAME COLLATE DATABASE_DEFAULT = a.Item COLLATE DATABASE_DEFAULT
WHERE CONTENT IS NOT NULL AND TYPE = 2
) C
CROSS APPLY C.ReportXML.nodes('*:Report/*:ReportParameters/*:ReportParameter') x(r)
OUTER APPLY x.r.nodes('*:ValidValues/*:ParameterValues') y(r)
where x.r.value ('*:Prompt[1]', 'VARCHAR(100)') is not null
First and foremost - I would not query ReportServer.dbo.Catalog directly; people do this all the time and it's crazy. Converting img data into VARNBINARY(MAX) then into XML is insanely expensive. If you need this information often I suggest dumping the results of this query into another table (we'll call it "SSRS_RDL") then, as part of your Report Deployment process you would update SSRS_RDL with changes and new records as needed. Even an hourly job that does this will often be enough. This way you're dealing with relational data that is easy to index and can be retreived 1000's of times faster than how you are doing it now.
That said you have a serious design flaw with your code; you are:
executing [ADA Master].[dbo].[spGetAdminReports] to push rows into your #reportlist table
then using XML PATH to turn #reportlist into a string called #rptlistStr
Then using dbo.ufn_SplitStringArray to turn #rptlistStr back into a table
Joining the results of this table to ReportServer.dbo.Catalog
You can get rid of #rptlistStr and the code to populate it from #reportList, and instead join your #reportlist table to ReportServer.dbo.Catalog. In other words, change:
FROM ReportServer.dbo.Catalog AS c
JOIN master.dbo.ufn_SplitStringArray(#rptlistStr,',') a on NAME COLLATE DATABASE_DEFAULT = a.Item COLLATE DATABASE_DEFAULT
To:
FROM ReportServer.dbo.Catalog AS c
JOIN #reportlist AS a ON on c.Name COLLATE DATABASE_DEFAULT = a.name COLLATE DATABASE_DEFAULT
Lastly:
This query will run much faster with a parallel execution plan. Once you've made the changes I just outline consider using OPTION (QUERYTRACEON 8649) at the end of your final SELECT query (or using Make_Parallel() by Adam Machanic.)
I have a table column consist with the XML files. I want to read XML data and display it.
I come up with the following code. But it read only one row in the column
want to display other XML data also
declare #xml xml
select #xml = event_data_XML from #temp
SELECT * FROM (
SELECT
CAST(f.x.query('data(#name)') as varchar(150)) as data_name,
CAST(f.x.query('data(value)') as varchar(150)) as data_value
FROM #xml.nodes('/event') as t(n)
CROSS APPLY t.n.nodes('data') as f(x)) X
PIVOT (MAX(data_value) FOR data_name IN (NTDomainName, DatabaseName, ServerName)) as pvt
Output should be like this(NTDomainName, DatabaseName, ServerName are xml data)
There are a bunch of ways you could do this. I'll show you a way I think you'd find easiest.
To start, here's a table with a little test data:
CREATE TABLE dbo.stuff (
id int identity (1,1) primary key
, event_data_xml xml
, create_date datetime default(getdate())
, is_active bit default(1)
);
INSERT INTO dbo.stuff (event_data_xml)
VALUES ('<event name="thing" package="as">something</event>')
INSERT INTO dbo.stuff (event_data_xml)
VALUES ('<event name="otherthing" package="as">something else</event>')
---All records
SELECT * FROM dbo.[stuff];
Make sense so far? Here's the query I'd use if I wanted to mix XML data and column data:
---Parsed up
SELECT event_data_xml.value('/event[1]', 'nvarchar(max)') AS [parsed element #text]
, event_data_xml.value('/event[1]/#name', 'nvarchar(max)') AS [parsed attribute value]
, create_date --column from table
FROM dbo.stuff
WHERE is_active = 1;
Using the value() function on the XML column passing in an xpath to what I want to display and SQL Server data type for how I want it returned.
Just make sure you're selecting a single value with your xpath expression.
I am attempting to write a SQL Query that will take in an XML object of undefined schema (YAY!) and transform it to a two column table of ElementName, Value columns. I was able to get a simple query down after some time (I am not a SQL person by any means).
DECLARE #strXml XML
SET #strXml = '<xml>
<FirstName>TEST</FirstName>
<LastName>PERSON</LastName>
<DOB>1/1/2000</DOB>
<TestObject>
<SomeProperty>CHECKED</SomeProperty>
<EmbeddedObject>
<SomeOtherProperty>NOT CHECKED</SomeOtherProperty>
</EmbeddedObject>
</TestObject>
</xml>'
DECLARE #XmlMappings TABLE
(
NodeName VARCHAR(64),
Value VARCHAR(128)
)
INSERT INTO #XmlMappings
SELECT doc.col.value('fn:local-name(.)[1]', 'varchar(64)') AS ElementName,
doc.col.value('.', 'varchar(128)') AS Value
FROM #strXml.nodes('/xml/*') doc(Col)
SELECT * FROM #XmlMappings
This query can handle the simple condition of the specified XML with only the first level elements. However elements such as TestObject and EmbeddedObject end up flattened. What I am looking for is to get some type of mapping like
ElementName | Value
=====================================================
FirstName | TEST
LastName | PERSON
DOB | 1/1/2000
TestObject.SomeProperty | CHECKED
TestObject.EmbeddedObject.SomeOtherProperty | NOT CHECKED
The hard part for me is the hierarchical structure with the . operator. I don't care if it is some other delimiter than . that gets output, it is more of just getting the output done, and I don't know enough about XML in SQL to be able to know even what to query.
Please note that I can also not use OPENXML since this is looking to be deployed on SQL Azure which does not support that feature at this time.
With a CTE and cross apply
;with cte as
(
select
convert(varchar(100), x.n.value('fn:local-name(.)','varchar(100)') ) as path,
convert(varchar(100), x.n.value('fn:local-name(.)','varchar(100)') ) AS name,
x.n.query('*') AS children,
x.n.value('.','varchar(1000)') as value
from #strxml.nodes('/xml/*') AS x(n)
union all
select
convert(varchar(100), x.path + '.' + c.n.value('fn:local-name(.)','varchar(100)') ),
convert(varchar(100), c.n.value('fn:local-name(.)','varchar(100)') ) ,
c.n.query('*'),
c.n.value('.','varchar(1000)')
from cte x
cross apply x.children.nodes('*') AS c(n)
)
select path, value from cte where datalength(children) = 5
Ok, I'm a C# ASP.NET dev following orders: The orders are to take a given dataset, shred the XML and return columns. I've argued that it's easier to do the shredding on the ASP.NET side where we already have access to things like deserializers, etc, and the entire complex of known types, but no, the boss says "shred it on the server, return a dataset, bind the dataset to the columns of the gridview" so for now, I'm doing what I was told. This is all to head off the folks who will come along and say "bad requirements".
Task at hand:
Here's my code that works and does what I want it to:
DECLARE #table1 AS TABLE (
ProductID VARCHAR(10)
, Name VARCHAR(20)
, Color VARCHAR(20)
, UserEntered VARCHAR(20)
, XmlField XML
)
INSERT INTO #table1 SELECT '12345','ball','red','john','<sizes><size name="medium"><price>10</price></size><size name="large"><price>20</price></size></sizes>'
INSERT INTO #table1 SELECT '12346','ball','blue','adam','<sizes><size name="medium"><price>12</price></size><size name="large"><price>25</price></size></sizes>'
INSERT INTO #table1 SELECT '12347','ring','red','john','<sizes><size name="medium"><price>5</price></size><size name="large"><price>8</price></size></sizes>'
INSERT INTO #table1 SELECT '12348','ring','blue','adam','<sizes><size name="medium"><price>8</price></size><size name="large"><price>10</price></size></sizes>'
INSERT INTO #table1 SELECT '23456','auto','black','ann','<auto><type>car</type><wheels>4</wheels><doors>4</doors><cylinders>3</cylinders></auto>'
INSERT INTO #table1 SELECT '23457','auto','black','ann','<auto><type>truck</type><wheels>4</wheels><doors>2</doors><cylinders>8</cylinders></auto><auto><type>car</type><wheels>4</wheels><doors>4</doors><cylinders>6</cylinders></auto>'
DECLARE #x XML
SELECT #x = (
SELECT
ProductID
, Name
, Color
, UserEntered
, XmlField.query('
for $vehicle in //auto
return <auto
type = "{$vehicle/type}"
wheels = "{$vehicle/wheels}"
doors = "{$vehicle/doors}"
cylinders = "{$vehicle/cylinders}"
/>')
FROM #table1 table1
WHERE Name = 'auto'
FOR XML AUTO
)
SELECT #x
SELECT
ProductID = T.Item.value('../#ProductID', 'varchar(10)')
, Name = T.Item.value('../#Name', 'varchar(20)')
, Color = T.Item.value('../#Color', 'varchar(20)')
, UserEntered = T.Item.value('../#UserEntered', 'varchar(20)')
, VType = T.Item.value('#type' , 'varchar(10)')
, Wheels = T.Item.value('#wheels', 'varchar(2)')
, Doors = T.Item.value('#doors', 'varchar(2)')
, Cylinders = T.Item.value('#cylinders', 'varchar(2)')
FROM #x.nodes('//table1/auto') AS T(Item)
SELECT #x = (
SELECT
ProductID
, Name
, Color
, UserEntered
, XmlField.query('
for $object in //sizes/size
return <size
name = "{$object/#name}"
price = "{$object/price}"
/>')
FROM #table1 table1
WHERE Name IN ('ring', 'ball')
FOR XML AUTO
)
SELECT #x
SELECT
ProductID = T.Item.value('../#ProductID', 'varchar(10)')
, Name = T.Item.value('../#Name', 'varchar(20)')
, Color = T.Item.value('../#Color', 'varchar(20)')
, UserEntered = T.Item.value('../#UserEntered', 'varchar(20)')
, SubName = T.Item.value('#name' , 'varchar(10)')
, Price = T.Item.value('#price', 'varchar(2)')
FROM #x.nodes('//table1/size') AS T(Item)
So for now, I'm trying to figure out if there's a better way to write the code than what I'm doing now... (I have a part 2 to go along with this one...)
Whether shredding the XML on the server as opposed to doing it on the client is good or bad depends on a variety of factors, the requirements may be perfectly valid. Shredding XML on the server, given the extensive support SQL server 2005 and after have for XML (XPath/XQuery/XML indexes) is often a very sensible approach.
However, what you have in your post is an example of semantic modeling of data, using XML. I recommend you go over a couple of white papers:
Best Practices for Semantic Data Modeling for Performance and Scalability
XML Best Practices for Microsoft SQL Server 2005
Performance Optimizations for the XML Data Type in SQL Server 2005
Performance tips of using XML data in SQL Server
I don't know if the #table1 in your example is just an example or the actual data structure you use in production, but some points will jump out immediately after you read those papers:
use typed XML when possible (add a schema)
use an appropriate XML index for the processing you need
try to shred all XML in one single transformation instead of 3 consecutive steps
And finally, if you need to shred every time you query, perhaps you need to analyze the data model (this is where the first paper in my list is useful).