Reading Table Type Output in HANA - hana

How can I read the content of an out table type parameter of a procedure in SAP HANA SQL Script?
Sample Procedure:
create procedure "MYSCHEMA".ReturnTypeTest(out OUTPUT_TABLE "MYSCHEMA"."RESOUT")
as
begin
create local temporary table #temp ("COL1" bigint, "COL2" bigint, "COL3" bigint);
insert into #temp values(1, 2, 3);
insert into #temp values(4, 5, 6);
insert into #temp values(7, 8, 9);
OUTPUT_TABLE = select * from #temp;
drop table #temp;
end;
Table Type (Out Parameter):
create type "MYSCHEMA"."RESOUT" as table ("COL1" bigint, "COL2" bigint, "COL3" bigint);
When I call the procedure as below, it displays entire content in SAP HANA Studio's result pane but how can I get it programmatically?
call "MYSCHEMA"."RETURNTYPETEST"(?);

Output variables from procedures can only be assigned to variables in an SQLScript context.
An exception to this is the default resultset that gets bound to the last SELECT command executed in the procedure.
If your intention is to produce something that can be SELECTed, you may want to use a table typed user defined function (TUDF) instead.
Two comments to your example code:
using temporary tables is not a good idea if performance is of concern for your application. While imperative code often appears
to be more intuitive, it really tends to block parallelism during
statement execution.
It's very (too) easy to overload a single procedure function wise by including data manipulation, computation and resultset
returns. If possible, rather opt for smaller functional units and
split up the functionality into multiple objects.
Ok, after you clarified that you actually just want to access the resultset in SQLScript and not in plain SQL, I can add this to my answer:
Check what I wrote in the first sentence! You can simply assign any output variable from a procedure to a corresponding variable.
The documentation has examples on that HANA documentation: CALL.
For example, if your output structure is a table that contains user information it may look like this:
DECLARE uaccounts TABLE (USERID bigint, USERNAME NVARCHAR(256), CREATED date);
DECLARE expdate date := current_date;
/* In this example the procedure 'get_expired_useraccounts_by date' has got
the IN parameter expiry_date (date) and
the OUT parameter expired_accounts (table structure).
By assigning the variable uaccounts to the OUT parameter, the result set
automatically gets bound to uaccounts.*/
call get_expired_useraccounts_by_date (:expdate, :uaccounts);
/* from here you can use :uaccounts like a table variable*/
SELECT count(*) FROM :uaccounts;
All this is, of course, part of the reference documentation and the developer guides...

Could you please check following SQLScript
declare lt_list "MYSCHEMA"."RESOUT";
call "MYSCHEMA"."RETURNTYPETEST"(lt_list);
select * from :lt_list;
This should display the output parameter table using the last SELECT statement

The answer, after understanding the context with Lars' Q&A, is: define a table variable in your caller procedure code
DECLARE temp TABLE (n int);
DECLARE temp MY_TABLE_TYPE;
Then assign the output param of the callee to it.
https://help.sap.com/viewer/de2486ee947e43e684d39702027f8a94/2.0.01/en-US/ea5065d06d14426799d879234d8e3e7b.html

You can query the system views for metadata
Please check following SQLScript Select
select table_type_schema, table_type_name, *
from PROCEDURE_PARAMETERS
where
schema_name = UPPER('MYSCHEMA') and
procedure_name = UPPER('ReturnTypeTest') and
parameter_name = UPPER('OUTPUT_TABLE')
I hope it helps

Related

How to pass declared table to procedure as parameter?; How to access inside procedure the result cursor of another procedure?

I want to pass a declared table (or a cursor) to a procedure (or a function) as a parameter.
For example, in the following code, I want to pass the declared table temp_table to the procedure calculate_something_2 as a parameter.
I want to get inside a procedure the result cursor of another procedure.
For example, in the following code, I want to get inside calculate_something_1 the result cursor of calculate_something_2.
I have seen similar things being done in other databases, but is it possible to do any of these in HSQLDB?
I have looked at the documentation but could not find any way to do it.
The following code is the basic idea of what I want to achieve. However, it does not work.
create procedure calculate_something_1(in data int)
modifies sql data
dynamic result sets 1
begin atomic
declare table temp_table (id int, quantity int);
insert into temp_table values
(1, 10),
(2, 20),
(3, 30);
declare result_cursor cursor for
select id from (call calculate_something_2(temp_table)) where id > 10;
open result_cursor;
end;
create procedure calculate_something_2(in t table)
modifies sql data
dynamic result sets 1
begin atomic
declare result_cursor cursor for
select id from t where id > 5;
open result_cursor;
end;
It is not possible to pass cursors or tables among PROCEDUREs.
You can define the shared tables as CREATE GLOBAL TEMPORARY TABLE and reference them in different procedures.

Pass a user-defined table as a select statement to a Stored Procedure or Function [duplicate]

Using SQL Server 2012, is it possible to eliminate the need to declare a table-valued parameter (TVP) just to pass it into a stored procedure? Below is a really simple example of a stored procedure (SP) that takes a TVP and a working example to execute that SP where I have to declare the TVP, populate it and then pass it into the SP. I would like to be able to simply pass in the population criteria directly to the EXEC call. Is this possible?
Scenario Setup:
-- Create a sample Users table
CREATE TABLE Users (UserID int, UserName varchar(20))
INSERT INTO Users VALUES (1, 'Bob'), (2, 'Mary'), (3, 'John'), (4, 'Mark')
-- Create a TVP Type
CREATE TYPE UserIdTableType AS TABLE (UserID int)
-- Create SP That Uses TVP Type
CREATE PROCEDURE GetUsers
#UserIdFilter UserIdTableType READONLY
AS
SELECT * FROM #UserIdFilter WHERE UserID > 2
Working Method to Execute:
DECLARE #MyIds AS UserIdTableType
INSERT INTO #MyIds SELECT UserID FROM Users
EXEC GetUsers #MyIds
Requested Method to Execute:
EXEC GetUsers (SELECT UserID FROM Users)
No, you cannot create a TVP inline or CAST / CONVERT it. It is not a "Data Type" like INT, VARCHAR, DATETIME, etc.; it is a "Table Type" which is entirely different. The User-Defined Table Type (UDTT) is just meta-data that is used as the definition/schema for the declaration of a Table Variable. When such a Table Variable is used as an input parameter, that usage is considered a TVP (Table-Valued Parameter). But the thing is still a Table Variable which has its definition stored in tempdb. This is a physical structure, not a memory structure, and you can't CAST or CONVERT a Table, whether it is real, temporary, or a variable.
While the example given in the Question is simplistic for the sake of just getting the idea across, it does seem like your overall goal is code-reuse / creating subroutines (else you could have easily done SELECT * FROM Users WHERE UserID > 2). Unfortunately T-SQL doesn't allow for really elegant / clean code, so you will have to accept a certain level of repetition and/or clunkiness.
It is possible, however, to make slightly generic handlers for result sets, provided they at least have the required fields. You could either
pass in an XML parameter, or
dump the results to a temp table and just refer to it in the sub-proc call (doesn't need to be dynamic SQL) and hence no need to pass in any parameter (at least not one for the dataset / results / query)
In both of those cases, the structure is more flexible than using a TVP since the TVP has to be those exact fields. But referencing a temp table that is assumed to exist allows for something similar to the following:
Proc_1
SELECT *
INTO #MyTemp
FROM sys.tables;
EXEC dbo.Proc_4 #StartsWith = 'a', #HowMany = 10;
Proc_2
SELECT *
INTO #MyTemp
FROM sys.columns;
EXEC dbo.Proc_4 #StartsWith = 'bb', #HowMany = 20;
Proc_3
SELECT *
INTO #MyTemp
FROM sys.views;
EXEC dbo.Proc_4 #StartsWith = 'ccc', #HowMany = 33;
Proc_4
SELECT TOP (#HowMany) tmp.*
FROM #MyTemp tmp
WHERE tmp.[name] LIKE #StartsWith + '%'
ORDER BY tmp.[object_id] ASC;

StoredProc manipulating Temporary table throws 'Invalid column name' on execution

I have a a number of sp's that create a temporary table #TempData with various fields. Within these sp's I call some processing sp that operates on #TempData. Temp data processing depends on sp input parameters. SP code is:
CREATE PROCEDURE [dbo].[tempdata_proc]
#ID int,
#NeedAvg tinyint = 0
AS
BEGIN
SET NOCOUNT ON;
if #NeedAvg = 1
Update #TempData set AvgValue = 1
Update #TempData set Value = -1;
END
Then, this sp is called in outer sp with the following code:
USE [BN]
--GO
--DBCC FREEPROCCACHE;
GO
Create table #TempData
(
tele_time datetime
, Value float
--, AvgValue float
)
Create clustered index IXTemp on #TempData(tele_time);
insert into #TempData(tele_time, Value ) values( GETDATE(), 50 ); --sample data
declare
#ID int,
#UpdAvg int;
select
#ID = 1000,
#UpdAvg = 1
;
Exec dbo.tempdata_proc #ID, #UpdAvg ;
select * from #TempData;
drop table #TempData
This code throws an error: Msg 207, Level 16, State 1, Procedure tempdata_proc, Line 8: Invalid column name "AvgValue".
But if only I uncomment declaration AvgValue float - everything works OK.
The question: is there any workaround letting the stored proc code remain the same and providing a tip to the optimizer - skip this because AvgValue column will not be used by the sp due to params passed.
Dynamic SQL is not a welcomed solution BTW. Using alternative to #TempData tablename is undesireable solution according to existing tsql code (huge modifications necessary for that).
Tried SET FMTONLY, tempdb.tempdb.sys.columns, try-catch wrapping without any success.
The way that stored procedures are processed is split into two parts - one part, checking for syntactical correctness, is performed at the time that the stored procedure is created or altered. The remaining part of compilation is deferred until the point in time at which the store procedure is executed. This is referred to as Deferred Name Resolution and allows a stored procedure to include references to tables (not just limited to temp tables) that do not exist at the point in time that the procedure is created.
Unfortunately, when it comes to the point in time that the procedure is executed, it needs to be able to compile all of the individual statements, and it's at this time that it will discover that the table exists but that the column doesn't - and so at this time, it will generate an error and refuse to run the procedure.
The T-SQL language is unfortunately a very simplistic compiler, and doesn't take runtime control flow into account when attempting to perform the compilation. It doesn't analyse the control flow or attempt to defer the compilation in conditional paths - it just fails the compilation because the column doesn't (at this time) exist.
Unfortunately, there aren't any mechanisms built in to SQL Server to control this behaviour - this is the behaviour you get, and anything that addresses it is going to be perceived as a workaround - as evidenced already by the (valid) suggestions in the comments - the two main ways to deal with it are to use dynamic SQL or to ensure that the temp table always contains all columns required.
One way to workaround your concerns about maintenance if you go down the "all uses of the temp table should have all columns" is to move the column definitions into a separate stored procedure, that can then augment the temporary table with all of the required columns - something like:
create procedure S_TT_Init
as
alter table #TT add Column1 int not null
alter table #TT add Column2 varchar(9) null
go
create procedure S_TT_Consumer
as
insert into #TT(Column1,Column2) values (9,'abc')
go
create procedure S_TT_User
as
create table #TT (tmp int null)
exec S_TT_Init
insert into #TT(Column1) values (8)
exec S_TT_Consumer
select Column1 from #TT
go
exec S_TT_User
Which produces the output 8 and 9. You'd put your temp table definition in S_TT_Init, S_TT_Consumer is the inner query that multiple stored procedures call, and S_TT_User is an example of one such stored procedure.
Create the table with the column initially. If you're populating the TEMP table with SPROC output just make it an IDENTITY INT (1,1) so the columns line up with your output.
Then drop the column and re-add it as the appropriate data type later on in the SPROC.
The only (or maybe best) way i can thing off beyond dynamic SQL is using checks for database structure.
if exists (Select 1 From tempdb.sys.columns Where object_id=OBJECT_ID('tempdb.dbo.#TTT') and name = 'AvgValue')
begin
--do something AvgValue related
end
maybe create a simple function that takes table name and column or only column if its always #TempTable and retursn 1/0 if the column exists, would be useful in the long run i think
if dbo.TempTableHasField('AvgValue')=1
begin
-- do something AvgValue related
end
EDIT1: Dang, you are right, sorry about that, i was sure i had ... this.... :( let me thing a bit more

SELECT Query selecting values based on a value in another table

I have 2 tables
Account(AccountId, Encoding)
DeviceAccountMap(AccountId, DeviceId)
Now I need to fetch the devices from the DeviceAccountMap. I pass a list of AccountId to a stored procedure and while fetching the DeviceId from the DeviceAccountMap table I need to compare the Encoding value for each account with a particular value.
Which is the easy way to do this? I am totally lost.
The select clause in the stored procedure will look something like this:
DECLARE #Accounts [usp].[Array]
and [usp].[Array] is defined as below
CREATE TYPE [usp].[Array] AS TABLE
(
Value VARCHAR(36) NULL
)
SELECT
DeviceId,
AccountEncoding = A.Encoding
FROM
usp.DeviceControllerAccountMap DCAM
INNER JOIN
usp.Account A ON (DCAM.AccountId = A.AccountId)
WHERE
DCAM.AccountId IN (SELECT Value From #AccountIds)
AND DCAM.IsShared = 1
AND AccountEncoding LIKE A.Encoding + '.%'
In other words I need to fetch the encoding value for each account and use that in this where clause.
So you can look up information on Table-Valued Parameters (TVPs) in T-SQL.
Here is an article by Erland Sommarskog.
You can refer to this StackOverflow answer to see an example of C# code calling a stored procedure that uses a TVP. I believe TVPs require SQL Server 2008 or higher.
TVPs, as far as I understand, provide a way to make your own data type in sql server that gets treated as if it was a table. You're doing this when you declare your Array type and then when you use the #AccountIds in your stored procedure's select statement.
CREATE TYPE [usp].[Array] AS TABLE -- maybe choose a more descriptive name than 'Array'
(
Value VARCHAR(36) NULL -- choose a more descriptive name than 'Value'
)
CREATE PROCEDURE [usp].[your_procedure_name]
#AccountIds [usp].[Array] READONLY -- use TVP as a parameter
AS
SELECT …
It is not clear form your question details whether you also mean to have a parameter in the stored procedure for the Encoding. It seems like you're looking for accounts whose Encodings start with a period '.'.
So first, create your type, like you're doing.
Then create your stored procedure.
Then test your stored procedure, something like this:
DECLARE #mylist Array -- make TVP sample data
INSERT #mylist(Value) VALUES(1),(11),(27),(123) -- insert some values
exec your_procedure_name #mylist -- run stored procedure
The following line is completely unnecessary. The JOIN to Account does this filter for you.
DCAM.AccountId IN (SELECT Value From #AccountIds)
Or am I missing something?

SQL Server Stored Procedure Multiple Insert in a single table from Array

I am using a stored procedure to insert records into a table. And do this at least 12 times in a loop to insert multiple records which is very inefficient.
here is the procedure as CREATED
Create PROC [dbo].[SP_INSERT_G_SAMPLING]
#GameID INT,
#ScoreID INT
as
begin
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#GameID, #ScoreID)
end
I pass on the values ex(1,3) and loop with more values from the website.
I want to however pass on all the values at one time like (1,3),(4,5),(8,9)
and then alter the above procedure to receive and insert multiple rows.
ALTER PROC [dbo].[SP_INSERT_G_SAMPLING]
#totalinsert nvarchar(Max)
INSERT INTO GAMESCORE (GAMEID, SCOREID) VALUES
(#totalinsert)
with #totalinsert being like (1,3),(4,5),(8,9) pushed from the webpage.
any help is greatly appreciated
What you're going to have to do is write a table valued function which accepts the multi-value string and breaks it out into a table object. If you can change your source to use a record delimiter instead of having comma sets it would be slightly easier to process. An example of that would look like this.
The below is pure psuedo and has not been validated in any way, just meant to give you a rough idea of where to go.
ex: #TotalInsert = 1,2|4,5|8,9
DECLARE #Results TABLE
(
value1 INT,
value2 INT
)
DECLARE #setlist VARCHAR(max);
WHILE Len(#TotalInsert) > 0
BEGIN
SET #setlist = LEFT(#totalinsert, Charindex('|', #totalinsert))
INSERT INTO #results
SELECT LEFT(#setlist, Charindex(',', #setlist) - 1),
RIGHT(#setlist, Charindex(',', Reverse(#setlist)) + 1)
SET #totalinsert = RIGHT(#totalinsert, Len(#totalinsert) - Len(#setlist))
END
I'm assuming you're using .NET for your website since you're also using SQL Server.
Have a look at table valued parameters, this page also includes a nice example of how to use the table valued parameters in .NET.
Check here for a better example of making a stored procedure with a table valued parameter in T-SQL.
Here is the full discussion:
http://www.sommarskog.se/arrays-in-sql-2005.html#XMLlist%20of%20values
Personally, I sent xml to the stored procedure, I "shred it" into #variable or #temp tables, then I do my INSERT/UPDATE/MERGE/DELETE from there.
Here is a fuller discussion on xml-shredding.
http://pratchev.blogspot.com/2007/06/shredding-xml-in-sql-server-2005.html
My personal trick is to create a strong dataset, populate the strong dataset with rows, and use the ds.GetXml() to send the xml down to the TSQL. With a strong dataset, I get strong-typing when populating the values. But at the end of the day, dataset is just some super fancy xml.