Warning: preg_match(): Unknown modifier 'b' in /mnt/datadisk/www/src/Controllers/PostController.php on line 206 SQL Server and Power BI - How to Load Stored Procedure Data in SQL Server with DirectQuery — Dirceu ResendeSkip to content
In this post I would like to share with you a situation that happened to me while consulting with a client, where he had a need to have a Power BI report connected to a SQL Server database, but the data source was a Stored Procedure which did several data transformations internally and returned a set of data.
The problem is that this client wanted the data to be updated in real time, that is, if they opened the report, applied a filter or interacted with the graph, the data would have to be updated by executing this Stored Procedure. And it turns out that Power BI NO This is supported 🙂
This is the Stored Procedure code that I would like to run as DirectQuery in Power BI:
CREATE OR ALTER PROCEDURE dbo.stpPBI_DirectQuery_Procedure
AS
BEGIN
IF (OBJECT_ID('tempdb..#Tabelas') IS NOT NULL) DROP TABLE #Tabelas
SELECT *
INTO #Tabelas
FROM sys.tables
WHERE is_ms_shipped = 0
IF (OBJECT_ID('tempdb..#Tabelas_Internas') IS NOT NULL) DROP TABLE #Tabelas_Internas
CREATE TABLE #Tabelas_Internas (
[object_id] INT,
[Descricao] VARCHAR(100)
)
INSERT INTO #Tabelas_Internas
VALUES(965578478, 'Tabela de Confirmados'), (1013578649, 'Tabela de Casos Recuperados')
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
B.Descricao
FROM
#Tabelas A
LEFT JOIN #Tabelas_Internas B ON B.[object_id] = A.[object_id]
WHERE
is_replicated = 0
ORDER BY
[name]
END
Expected execution result:
Simulating the problem
Click here to view this content
The first step when trying to execute a procedure with DirectQuery is to select this type of storage, enter the server name and click the OK button to load the list of objects:
In the list of objects, we can see that it only lists tables, views and functions:
I'll go back to the connection screen and try to enter the execute command manually in the SQL statement screen:
But when trying to do this, an error message is returned:
Let's try again, without adding the EXEC statement:
Oops! It looks like it loaded!
It seemed like it would work, but when trying to load the data...
PowerQuery even shows the database data, but it doesn't let me load it into the model, showing the message from the previous print.
Solution #1 – OpenRowSet
Click here to view this content
The first “solution” that I will present to you is to use OpenRowSet to execute the procedure on the local server and return the data. Since this ends up being a SELECT statement, Power BI must accept execution:
SELECT * FROM OPENROWSET('SQLNCLI', 'Server=.;Trusted_Connection=yes;', 'EXEC eventos.dbo.stpPBI_DirectQuery_Procedure')
The first point to consider is this error message below:
Msg 15281, Level 16, State 1, Line 43
SQL Server blocked access to STATEMENT ‘OpenRowset/OpenDatasource’ of component ‘Ad Hoc Distributed Queries’ because this component is turned off as part of the security configuration for this server. A system administrator can enable the use of ‘Ad Hoc Distributed Queries’ by using sp_configure. For more information about enabling ‘Ad Hoc Distributed Queries’, search for ‘Ad Hoc Distributed Queries’ in SQL Server Books Online.
This message represents nothing more than that the OpenRowset/OpenDatasource feature is disabled on the instance (by default, this setting is disabled due to security reasons). To use Openrowset, you will need to enable this feature, and your DBA probably won't like it:
sp_configure 'show advanced options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO
Once enabled, now just execute our SELECT command. And we come across the 2nd error message:
Msg 11526, Level 16, State 1, Procedure sys.sp_describe_first_result_set, Line 1 [Batch Start Line 53]
The metadata could not be determined because statement ‘INSERT INTO #Tables_Internas
VALUES(965578478, ‘Confirmed Table’), (1013578649, ‘Table’ in procedure ‘stpPBI_DirectQuery_Procedure’ uses a temp table.
Now SQL Server is complaining about the temporary tables used in the Stored Procedure. Let's make some changes to the procedure and replace the temporary tables (#table) with table-type variables (@table):
ALTER PROCEDURE dbo.stpPBI_DirectQuery_Procedure
AS
BEGIN
DECLARE @Tabelas TABLE (
[object_id] INT,
[name] VARCHAR(100),
create_date DATETIME,
lock_escalation_desc VARCHAR(100),
temporal_type_desc VARCHAR(100),
is_replicated BIT
)
INSERT INTO @Tabelas
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
A.is_replicated
FROM
sys.tables A
WHERE
is_ms_shipped = 0
DECLARE @Tabelas_Internas TABLE (
[object_id] INT,
[Descricao] VARCHAR(100)
)
INSERT INTO @Tabelas_Internas
VALUES(965578478, 'Tabela de Confirmados'), (1013578649, 'Tabela de Casos Recuperados')
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
B.Descricao
FROM
@Tabelas A
LEFT JOIN @Tabelas_Internas B ON B.[object_id] = A.[object_id]
WHERE
A.is_replicated = 0
ORDER BY
[name]
END
And let's try again. Another error message:
Msg 7357, Level 16, State 1, Line 54
Cannot process the object “EXEC eventos.dbo.stpPBI_DirectQuery_Procedure”. The OLE DB provider “SQLNCLI11” for linked server “(null)” indicates that either the object has no columns or the current user does not have permissions on that object.
To solve this problem, we will include the “SET NOCOUNT ON” instruction at the beginning of the Stored Procedure, so that the OPENROWSET command can correctly identify the returned data and does not suffer interference with the return of the number of rows returned:
ALTER PROCEDURE dbo.stpPBI_DirectQuery_Procedure
AS
BEGIN
SET NOCOUNT ON
DECLARE @Tabelas TABLE (
[object_id] INT,
[name] VARCHAR(100),
create_date DATETIME,
lock_escalation_desc VARCHAR(100),
temporal_type_desc VARCHAR(100),
is_replicated BIT
)
INSERT INTO @Tabelas
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
A.is_replicated
FROM
sys.tables A
WHERE
is_ms_shipped = 0
DECLARE @Tabelas_Internas TABLE (
[object_id] INT,
[Descricao] VARCHAR(100)
)
INSERT INTO @Tabelas_Internas
VALUES(965578478, 'Tabela de Confirmados'), (1013578649, 'Tabela de Casos Recuperados')
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
B.Descricao
FROM
@Tabelas A
LEFT JOIN @Tabelas_Internas B ON B.[object_id] = A.[object_id]
WHERE
A.is_replicated = 0
ORDER BY
[name]
END
With this change, our SELECT command was executed correctly:
Let's go back to Power BI, repeat the entire process and now we can load the data normally into Power BI:
And it's there! We are consuming a Stored Procedure using DirectQuery in Power BI!
Solution #2 – Using Table-Valued Function
Click here to view this content
The second way to query complex objects using DirectQuery in Power BI is by replacing the Stored Procedure with a Table-Valued Function, which ends up being a much more elegant and correct way of carrying out this query than the previous way.
Below is the function code, which does exactly the same thing as the previous Stored Procedure:
CREATE FUNCTION dbo.fncPBI_DirectQuery_Procedure()
RETURNS @Retorno TABLE (
[object_id] INT,
[name] VARCHAR(100),
create_date DATETIME,
lock_escalation_desc VARCHAR(100),
temporal_type_desc VARCHAR(100),
descricao varchar(200)
)
AS
BEGIN
DECLARE @Tabelas TABLE (
[object_id] INT,
[name] VARCHAR(100),
create_date DATETIME,
lock_escalation_desc VARCHAR(100),
temporal_type_desc VARCHAR(100),
is_replicated BIT
)
INSERT INTO @Tabelas
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
A.is_replicated
FROM
sys.tables A
WHERE
is_ms_shipped = 0
DECLARE @Tabelas_Internas TABLE (
[object_id] INT,
[Descricao] VARCHAR(100)
)
INSERT INTO @Tabelas_Internas
VALUES(965578478, 'Tabela de Confirmados'), (1013578649, 'Tabela de Casos Recuperados'), (338100245, 'Tabela Interna de Log')
INSERT INTO @Retorno
SELECT
A.[object_id],
A.[name],
A.create_date,
A.lock_escalation_desc,
A.temporal_type_desc,
B.Descricao
FROM
@Tabelas A
LEFT JOIN @Tabelas_Internas B ON B.[object_id] = A.[object_id]
WHERE
A.is_replicated = 0
ORDER BY
[name]
RETURN
END
After creating the function, select the “Get Data” > “SQL Server” option and write the SELECT command to read the function data:
After clicking the “OK” button, your data will be loaded normally:
Just click on the familiar “Close and Apply” button and our task is done.
Limitations
The use of Stored Procedures or functions to connect to data with DirectQuery has some limitations that I can highlight:
Cannot pass dynamic parameters to Stored Procedure or Table-valued function dynamically
When using a slicer, for example, the data is executed in the database and then filtered the data returned using the slicers, therefore, this may cause performance problems in very large tables
Unlike a paginated report, which recalculates the entire dataset after user interactions, Power BI does not update the data in the source with each applied filter or interaction in DirectQuery
Both solutions will present performance problems in large volumes of data
The 2 techniques presented require a certain knowledge of T-SQL and only work on SQL Server. This might be a bit complex to work with for a non-IT business user
Comentários (0)
Carregando comentários…