New

Newsroom more...

msg_Gradient_farblos_1 (4)
SQL Code Example 5

Unlocking SSRS

Investigating SSRS with SQL Queries

Introduction

Managing SQL Server Reporting Services (SSRS) in large organizations can quickly become complex. In my experience working with more than 1,000 financial reports spanning multiple regions, countries, databases, and development teams, it became clear that manually tracking report definitions, parameters, subscriptions, and execution history is not sustainable.

As the reporting landscape grows, administrators and developers need deeper insight into how reports are built, scheduled, executed, and consumed. This article focuses on gaining that insight by querying the SSRS ReportServer database directly.

Contact

Gurovich, Julia

Julia Gurovich

Senior IT Consultant

Why Traditional SSRS Management Falls Short

When the number of reports increases, the SSRS web portal becomes increasingly difficult to manage. Manually clicking through folders to inspect report parameters, schedules, or subscription definitions is time-consuming and error prone.

For analysts and developers, relying solely on the SSRS dashboard is not realistic when:

  • Hundreds of reports exist
  • Multiple teams own different reports
  • Reports serve different departments or countries
  • Audits, migrations, or archiving projects are required

What You Need to Know About Your Reports

To manage SSRS effectively, you must be able to analyze every critical aspect of your reports, including:

Which SQL statements each report executes

  • How reports are scheduled
  • Which subscriptions are defined
  • Who receives report outputs
  • Which parameters are used
  • Whether executions succeed or fail
  • How frequently reports are executed

The most reliable way to obtain this information is by querying the ReportServer database.

Why Look Inside the ReportServer Database?

By querying the ReportServer database directly, SSRS administrators can:

  • Audit report queries and data sources
  • Review subscription schedules and delivery methods
  • Identify failing or inactive subscriptions
  • Monitor execution frequency and performance
  • Find reports that are no longer used
  • Extract complete metadata for migration or archiving projects

Over the past decade working with SSRS, I have built a toolkit of SQL queries that provide this visibility. The following sections present the most useful queries and explain when and why to use them.

Real-World Scenario: Migrating a Reporting System

One customer migrated their reporting environment to the cloud and replaced SSRS with a different reporting tool. However, they wanted to retain their existing databases and continue using the exact same SELECT statements that their SSRS reports relied on.

The challenge was to extract complete metadata for all existing reports, including:

  • All SQL queries used by reports
  • All datasets and data sources
  • All parameters and subscriptions
  • Execution and delivery details

This required a systematic approach using the ReportServer database.

Let’s see how I solved these problems.

1. Extracting Report Definitions and SQL Statements

When working manually, report details are typically inspected by opening report definitions in Visual Studio (or report builder). This approach does not scale.

Instead, SSRS stores report definitions in the ReportServer.dbo.Catalog table as compressed XML (RDL format). By decoding this content, we can analyze all reports programmatically.

SQL Query 1

Getting Full Report Definitions

The following query retrieves full RDL definitions along with report metadata such as creation and modification dates:

SELECT

    c.ItemID,

    c.Name AS ReportName,

    c.Path,

    c.CreationDate,

    c.ModifiedDate,

    CAST(CAST(c.Content AS VARBINARY(MAX)) AS XML) AS RDL_XML

FROM ReportServer.dbo.Catalog c

WHERE c.Type = 2;  -- Type 2 = Report

 

This query retrieves the raw RDL definition for each report, including metadata such as creation and modification dates. 

This output provides:

  1. Report ID in the catalog
  2. Report name
  3. Report path in the SSRS portal
  4. Creation date
  5. Modification date
  6. Full RDL XML definition

By inspecting the RDL XML, you can identify data sources, datasets, parameters, descriptions, and SQL queries used by the report.

SQL Query 1

2. Extracting Datasets, SQL Queries, and Data Sources

The next step is extracting core SQL statements, dataset definitions, and data sources directly from RDL files stored in the catalog.

;WITH cte AS (

    SELECT [path], [name] AS Report_Name,

           CONVERT(XML, CONVERT(VARBINARY(MAX), content)) AS rdl

    FROM reportserver.dbo.catalog

)

SELECT 

    LEFT([Path], LEN([path]) - CHARINDEX('/',REVERSE([Path])) + 1) AS Report_Path,

    Report_Name,

    DataSetNameQ.N.value('@Name', 'nvarchar(128)') AS DataSetName,

    DataSrsNameQ.N.value('(*:DataSourceName/text())[1]', 'nvarchar(128)') AS DataSourceName,

    ISNULL(DataSrsNameQ.N.value('(*:CommandType/text())[1]', 'nvarchar(128)'), 'T-SQL') AS CommandType,

    DataSrsNameQ.N.value('(*:CommandText/text())[1]', 'nvarchar(max)') AS CommandText

FROM cte AS mainSelect

CROSS APPLY mainSelect.rdl.nodes('/*:Report/*:DataSets/*:DataSet') AS DataSetNameQ (N)

CROSS APPLY DataSetNameQ.N.nodes('*:Query') AS DataSrsNameQ (N)

ORDER BY Report_Path, Report_Name, DataSetName, DataSourceName, CommandType, CommandText;

 

This query returns:

  • Report path and name
  • Dataset name
  • Data source name
  • Command type (e.g., T-SQL)
  • SQL statement used in the report

This information is invaluable for auditing, refactoring, or migrating large numbers of reports.

3. Investigating Subscriptions

Subscriptions are a critical SSRS feature that automate report delivery. Subscription data is stored across multiple tables, including Subscriptions, ReportSchedule, and Schedule.

Basic Subscription Information

The following query extracts key subscription details:

SELECT

    s.SubscriptionID,

    c.Name AS ReportName,

    c.Path,

    s.Description AS SubscriptionDescription,

    s.LastStatus,

    s.LastRunTime,

    s.DeliveryExtension,

    s.EventType,

    s.MatchData AS ScheduleDefinition, -- XML

    s.Parameters, -- XML

    u.UserName AS CreatedBy

FROM ReportServer.dbo.Subscriptions s

JOIN ReportServer.dbo.Catalog c ON s.Report_OID = c.ItemID

JOIN ReportServer.dbo.Users u ON s.OwnerID = u.UserID

ORDER BY c.Name;

 

From this output, you can determine:

  • Which reports have subscriptions
  • Where report outputs are delivered
  • When subscriptions last ran
  • Whether execution succeeded or failed
  • Who owns each subscription

Readable Schedule Information

To better understand scheduling behavior and execution status, the following query provides readable scheduling details:

SELECT

    c.Name AS ReportName,

    c.Path,

    s.Description,

    s.InactiveFlags,

    sch.NextRunTime,

    sch.LastRunTime,

    sch.LastRunStatus,

    s.Parameters

FROM ReportServer.dbo.Subscriptions s

JOIN ReportServer.dbo.Catalog c ON s.Report_OID = c.ItemID

JOIN ReportServer.dbo.ReportSchedule rs ON s.SubscriptionID = rs.SubscriptionID

JOIN ReportServer.dbo.Schedule sch ON rs.ScheduleID = sch.ScheduleID;

 

This allows administrators to diagnose failed or inactive subscriptions efficiently.

Subscription Inactive Flags

The InactiveFlags column indicates the subscription status or explain why it is not running:

  • 0 – Active
  • 1 – Email or file share removed
  • 2 – Missing data source
  • 4 – Missing parameter
  • 8 – Invalid parameter
  • 128 – Disabled by user

Subscription Parameters and Values

The following query extracts detailed parameter information, and provides visibility into:

  • Parameter names and values
  • Execution timing
  • Delivery location or recipients
  • Subscription language and status

 defined for subscriptions:

 

;WITH

[Sub_Parameters] AS

(

SELECT

[SubscriptionID],

[Parameters] = CONVERT(XML,a.[Parameters])

FROM [Subscriptions] a

),

[MySubscriptions] AS

(

SELECT DISTINCT

[SubscriptionID],

[ParameterName] = QUOTENAME(p.value('(Name)[1]', 'nvarchar(max)')),

[ParameterValue] = p.value('(Value)[1]', 'nvarchar(max)')

FROM

[Sub_Parameters] a

CROSS APPLY [Parameters].nodes('/ParameterValues/ParameterValue') t(p)

),

[SubscriptionsAnalysis] AS

(

SELECT

a.[SubscriptionID],

a.[ParameterName],

[ParameterValue] =

(SELECT

STUFF((

SELECT [ParameterValue] + ', ' as [text()]

FROM [MySubscriptions]

WHERE

[SubscriptionID] = a.[SubscriptionID]

AND [ParameterName] = a.[ParameterName]

FOR XML PATH('')

),1, 0, '')

+'')

FROM [MySubscriptions] a

GROUP BY a.[SubscriptionID],a.[ParameterName]

)

SELECT

a.[SubscriptionID],

c.[UserName]AS Owner,

b.Name,

b.Path,

a.[Locale],

a.[InactiveFlags],

d.[UserName] AS Modified_by,

a.[ModifiedDate],

a.[Description],

a.[LastStatus],

a.[EventType],

a.[LastRunTime],

a.[DeliveryExtension],

a.[Version],

e.[ParameterName],

LEFT(e.[ParameterValue],LEN(e.[ParameterValue])-1) as [ParameterValue],

SUBSTRING(b.PATH,2,LEN(b.PATH)-(CHARINDEX('/',REVERSE(b.PATH))+1)) AS ProjectName

FROM

[Subscriptions] a

INNER JOIN [Catalog] AS b

ON a.[Report_OID] = b.[ItemID]

LEFT OUTER JOIN [Users] AS c

ON a.[OwnerID] = c.[UserID]

LEFT OUTER JOIN [Users] AS d

ON a.MODIFIEDBYID = d.Userid

LEFT OUTER JOIN [SubscriptionsAnalysis] AS e

ON a.SubscriptionID = e.SubscriptionID

order by 17;

4. Monitoring Execution History

Execution history helps identify performance bottlenecks, unused reports, and operational issues.

Execution logs answer questions such as:

  • Which reports ran today?
  • Which executions failed?
  • How long do reports take to run?
  • Which reports are rarely or never used?

Latest Report Executions

The following query retrieves detailed execution information:

SELECT

InstanceName,

C.Name as ReportName,

COALESCE(CASE(ReportAction)WHEN 11 THEN AdditionalInfo.value('(AdditionalInfo/SourceReportUri)[1]', 'nvarchar(max)')

ELSE C.Path END, 'Unknown') AS ItemPath,

    --UserName,

    --ExecutionId,

CASE(RequestType)

        WHEN 0 THEN 'Interactive'

        WHEN 1 THEN 'Subscription'

        WHEN 2 THEN 'Refresh Cache'

        ELSE 'Unknown'

        END AS RequestType,

    -- SubscriptionId,

    Format,

    Parameters,

    CASE(ReportAction)

        WHEN 1 THEN 'Render'

        WHEN 2 THEN 'BookmarkNavigation'

        WHEN 3 THEN 'DocumentMapNavigation'

        WHEN 4 THEN 'DrillThrough'

        WHEN 5 THEN 'FindString'

        WHEN 6 THEN 'GetDocumentMap'

        WHEN 7 THEN 'Toggle'

        WHEN 8 THEN 'Sort'

        WHEN 9 THEN 'Execute'

        WHEN 10 THEN 'RenderEdit'

        WHEN 11 THEN 'ExecuteDataShapeQuery'

        WHEN 12 THEN 'RenderMobileReport'

        ELSE 'Unknown'

        END AS ItemAction,

    TimeStart,

    TimeEnd,

    TimeDataRetrieval,

    TimeProcessing,

    TimeRendering,

    CASE(Source)

        WHEN 1 THEN 'Live'

        WHEN 2 THEN 'Cache'

        WHEN 3 THEN 'Snapshot'

        WHEN 4 THEN 'History'

        WHEN 5 THEN 'AdHoc'

        WHEN 6 THEN 'Session'

        WHEN 7 THEN 'Rdce'

        ELSE 'Unknown'

        END AS Source,

    Status,

    ByteCount,

    [RowCount],

    AdditionalInfo

FROM ExecutionLogStorage EL WITH(NOLOCK)

LEFT OUTER JOIN Catalog C WITH(NOLOCK) ON (EL.ReportID = C.ItemID)

 

  • TimeDataRetrieval, TimeProcessing, and TimeRendering help isolate bottlenecks.
  • Status identifies success or failure.
  • RequestType describe is report executed interactive by user or with subscription
  • Format – render output format
  • AdditionalInfo in xml form help you understand information about scalability time, memory usage, processing, pagination, etc.

 

<AdditionalInfo>

  <ProcessingEngine>2</ProcessingEngine>

  <ScalabilityTime>

    <Pagination>0</Pagination>

    <Processing>0</Processing>

  </ScalabilityTime>

  <EstimatedMemoryUsageKB>

    <Pagination>5</Pagination>

    <Processing>78661</Processing>

  </EstimatedMemoryUsageKB>

  <DataExtension>

    <SQL>1</SQL>

  </DataExtension>

  <Connections>

    <Connection>

      <ConnectionOpenTime>261</ConnectionOpenTime>

      <DataSets>

        <DataSet>

          <Name>Main</Name>

          <RowsRead>48658</RowsRead>

          <TotalTimeDataRetrieval>4167</TotalTimeDataRetrieval>

          <ExecuteReaderTime>4165</ExecuteReaderTime>

        </DataSet>

      </DataSets>

    </Connection>

  </Connections>

</AdditionalInfo>

Reports Not Executed in the Last 90 Days

This query identifies reports that have not been executed for an extended period and helps identify candidates for cleanup or archival. Output provides you additional information who and how it was executed for last time (interactive, per subscription, etc.)

Of course you can choose your own number of days:

 

SELECT DISTINCT a.*, l.UserName

FROM (

    SELECT c.[name] as reportName,

           SUBSTRING(SUBSTRING(path, CHARINDEX('/', path) + 1, LEN(path)),0,

                     CHARINDEX('/',SUBSTRING(path, CHARINDEX('/', path) + 1, LEN(path)))) Folder,

           CASE(RequestType)

                WHEN 0 THEN 'Interactive'

                WHEN 1 THEN 'Subscription'

                WHEN 2 THEN 'Refresh Cache'

                ELSE 'Unknown' END AS RequestType,

           MAX(l.TimeStart) lastRunDate

    FROM Catalog c

    INNER JOIN ExecutionLogStorage l ON l.ReportID = c.ItemID

    WHERE c.Type NOT IN (1,3,5,8)

      AND l.ReportAction IN(1,13)

    GROUP BY c.[name],

             SUBSTRING(SUBSTRING(path, CHARINDEX('/', path) + 1, LEN(path)),0,

             CHARINDEX('/',SUBSTRING(path, CHARINDEX('/', path) + 1, LEN(path)))),

             CASE(RequestType) WHEN 0 THEN 'Interactive' WHEN 1 THEN 'Subscription'

                               WHEN 2 THEN 'Refresh Cache' ELSE 'Unknown' END

    HAVING MAX(l.TimeStart) < getdate() - 90

) a

INNER JOIN ExecutionLogStorage l ON l.TimeStart = a.lastRunDate;

Failed Executions Today

To quickly identify failures, use the following query (one of my daily reports about reports):

 

SELECT

    c.Path AS ReportPath,

    c.Name AS ReportName,

    a.Status,

    a.UserName,

    a.Parameters,

    a.TimeStart,

    a.TimeEnd,

    a.TimeDataRetrieval,

    a.TimeProcessing,

    a.ByteCount,

    a.[RowCount]

FROM dbo.ExecutionLog AS a WITH(NOLOCK)

INNER JOIN dbo.Catalog AS c WITH(NOLOCK) ON a.ReportID = c.ItemID

WHERE CAST(TimeStart AS DATE) = CAST(GETDATE() AS DATE)

AND [Status] NOT IN ('rsSuccess');

User and Report Execution Statistics

Understanding who uses reports and how often is essential for capacity planning and governance.

Who Executed Reports Today

SELECT UserName, COUNT(*) AS 'Executions a day'

FROM dbo.ExecutionLog AS a WITH(NOLOCK)

INNER JOIN dbo.Catalog AS c WITH(NOLOCK) ON a.ReportID = c.ItemID

WHERE CAST(TimeStart AS DATE) = CAST(GETDATE() AS DATE)

GROUP BY UserName

ORDER BY 2 DESC;

Report Execution Counts

SELECT c.Path+'/'+c.Name AS Path, COUNT(*) AS 'Executions a day'

FROM dbo.ExecutionLog AS a WITH(NOLOCK)

INNER JOIN dbo.Catalog AS c WITH(NOLOCK) ON a.ReportID = c.ItemID

WHERE CAST(TimeStart AS DATE) = CAST(GETDATE() AS DATE)

GROUP BY c.Path+'/'+c.Name

ORDER BY 2 DESC;

 

These queries reveal usage patterns across users and reports.

Best Practices

  • Always back up configuration files before making changes
  • Use XML parsing carefully when analyzing RDLs and subscriptions
  • Monitor execution logs regularly
  • Clean up unused reports and subscriptions
  • Document report ownership and dependencies

Final Thoughts

To manage large SSRS environments effectively, administrators must go beyond the graphical interface. Querying the ReportServer database enables:

  • Auditing report queries and data sources
  • Tracking subscription schedules and failures
  • Monitoring execution performance and usage
  • Identifying unused or problematic reports
  • Supporting migrations and archiving project

Do you have any questions? Get in touch!

Gurovich, Julia

Julia Gurovich

Senior IT Consultant

Interested in more insights?

Explore expert perspectives on cloud, data, and digital innovation in our Data & Analytics Blog

Data & Analytics Blog

The utilization of cloud computing services is experiencing a steady rise in adoption, as evidenced by a representative study conducted by Bitkom in 2024.

Data & Analytics Blog

Why the success of a data catalog depends less on technology and more on a clear vision, strong organizational anchoring, and targeted cultural change.

Data & Analytics Blog

Numerous performance challenges can be addressed with a few small tweaks. The tips below illustrate how to effectively optimize queries to maximize their performance potential.

Data & Analytics Blog

As enterprises scale their data estates, they face an overwhelming challenge: understanding what data exists, where it resides, and whether it can be trusted. In response, a variety of solutions branded as Data Catalogs have emerged.

Data & Analytics Blog

Agentic AI enables systems that think, adapt, and collaborate—going far beyond static automation. Learn how four key design patterns turn this concept into real business impact.

Data & Analytics Blog

In this article, we show how msg scaled synthetic data generation with Spark on Databricks — fast, efficient, and fully reproducible. Perfect for real-world benchmarking and performance testing.

Data & Analytics Blog

Learn how to build a real-time fraud detection pipeline using Confluent Kafka, Flink, and AI model inference on Confluent Cloud. This hands-on guide walks you through setup, data ingestion, and deploying a machine learning model with Azure ML.

Data & Analytics Blog

Discover what a ‘data product’ really is and why it’s transforming how businesses manage and use their data. Learn how treating data as a product improves trust, usability, and drives smarter AI solutions.