Schema as a parameter in a function - sql-server

Is it possible to have a schema as a parameter in a function such as
CREATE FUNCTION get_data_for_weekly_front_end_collections_summary(#schema VARCHAR)
RETURNS TABLE
AS RETURN
(
SELECT *
FROM #schema.db1
)
The error:

TSQL doesn't support parametrizing object names in queries, and user-defined functions don't support dynamic SQL. But you can do something like this:
CREATE FUNCTION get_data_for_weekly_front_end_collections_summary(#schema NVARCHAR(200))
RETURNS TABLE
AS RETURN
(
select *
from a.db1
where #schema = 'a'
union all
select *
from b.db1
where #schema = 'b'
)

Related

Unable to find User-defined Function

I have a UDF defined as follows:
Create Or Replace Function Minder_Mvp.Sprint4.Get_Customer_Key(Store_Key VARCHAR)
Returns Table (Customer_Key BIGINT)
AS
'Select Customer_Key From CustomerBrand
Where CustomerBrand.Brand_Key = 1'
;
And then I attempt to call it like this:
Select Minder_Mvp.Sprint4.Get_Customer_Key('457');
When I do so, I get the following error:
Unknown user-define function Minder_MVP.SPRINT4.GET_CUSTOMER_KEY
Must be something obvious but I am not seeing it.
It is an user defined table function:
A UDTF can be accessed in the FROM clause of a query.
Select Minder_Mvp.Sprint4.Get_Customer_Key('457');
=>
SELECT *
FROM TABLE(Minder_Mvp.Sprint4.Get_Customer_Key('457')) s;

Combine multiple tables into one in Snowflake

Let's say I have the following monthly tables with table names formatted such that the number after the underscore refers to the month. What I want to do is to combine these 12 tables into one without having to write 10-30 insert/union all statements
table_1
table_2
table_3
table_4
table_5
table_6
table_7
table_8
table_9
table_10
table_11
table_12 -- (only 12 in this instance but could be as many as 36)
My current approach is to first create the master table with data from table_1.
create temporary table master_table_1_12 as
select * -- * to keep it simple for this example
from table_1;
Then use variables such that I can simply keep hitting the run button until it errors out with "table_13 does not exist"
set month_id=(select max(month_id) from master_table_1_12) + 1;
set table_name=concat('table_',$month_id);
insert into master_table_1_12
select *
from identifier($table_name);
Note: All monthly tables have a month_id column
Sure it saves some space on the console(compared to multiple inserts), but I still have to run it 12 times. Are Snowflake Tasks something I could use for this? I couldn't find a fitting example from their documentation to code that up but, if anyone had success with that or with a Javascript based SP for a problem like this, please enlighten.
Here's a stored procedure that will insert into master_table_1_12 from selects on table_1 through table_12. Modify as required:
create or replace procedure FILL_MASTER_TABLE()
returns string
language javascript
as
$$
var rows = 0;
for (var i=1; i<=12; i++) {
rows += insertRows(i);
}
return rows + " rows inserted into master_table_1_12.";
// End of main function
function insertRows(i) {
sql =
`insert into master_table_1_12
select *
from table_${i};`;
return doInsert(sql);
}
function doInsert(queryString) {
var out;
cmd1 = {sqlText: queryString};
stmt = snowflake.createStatement(cmd1);
var rs = stmt.execute();;
rs.next();
return rs.getColumnValue(1);
}
$$;
call fill_master_table();
By the way, if you don't have any processing to do and just need to consolidate the tables, you can do something like this:
insert into master_table_1_12
select * from table_1
union all
select * from table_2
union all
select * from table_3
union all
select * from table_4
union all
select * from table_5
union all
select * from table_6
union all
select * from table_7
union all
select * from table_8
union all
select * from table_9
union all
select * from table_10
union all
select * from table_11
union all
select * from table_12
;
Can you not create a view on top of these 12 tables. The view will be an union of all these tables.
Based on the comments below, I further elaborated my answer. please try this approach. It will provide better performance when your table is large. Partitioning it will improve performance. This is based on real experience.
CREATE TABLE SALES_2000 (REGION VARCHAR, UNITS_SOLD NUMBER);
CREATE TABLE SALES_2001 (REGION VARCHAR, UNITS_SOLD NUMBER);
CREATE TABLE SALES_2002 (REGION VARCHAR, UNITS_SOLD NUMBER);
CREATE TABLE SALES_2003 (REGION VARCHAR, UNITS_SOLD NUMBER);
INSERT INTO SALES_2000 VALUES('ASIA', 25);
INSERT INTO SALES_2001 VALUES('ASIA', 50);
INSERT INTO SALES_2002 VALUES('ASIA', 55);
INSERT INTO SALES_2003 VALUES('ASIA', 65);
CREATE VIEW ALL_SALES AS
SELECT * FROM SALES_2000
UNION
SELECT * FROM SALES_2001
UNION
SELECT * FROM SALES_2002
UNION
SELECT * FROM SALES_2003;
SELECT * FROM ALL_SALES WHERE UNITS_SOLD = 25;
I ended up creating a UDF that spits out a create view statement and a stored procedure that executes it to create a temporary view. I work with tables following specific naming convention, so you might have to tweak this solution a little for your use case. The separation of UDF and stored proc actually helps with that as you'd mostly need to tweak the SQL UDF. I am sharing a simplified version of what I actually have in the interest of keeping it representative of the tables I listed in my question.
SQL UDF FOR GENERATING A CREATE VIEW STATETEMENT
create or replace function sandbox.public.define_view(table_pattern varchar, start_month varchar, end_month varchar)
returns table ("" varchar) as
$$
with cte1(month_id) as
(select start_month::int + row_number() over (order by 1) - 1
from table(generator(rowcount=> end_month::int - start_month::int + 1)))
,cte2(month_id,statement) as
(select 0,
concat('create or replace temporary view master_',
split_part(table_pattern,'.',-1),
start_month,
'_',
end_month,
' as ')
union all
select month_id,
concat('select * from ',
table_pattern,
month_id,
case when month_id=end_month::int then ';' else ' union all ' end)
from cte1)
select listagg(statement, '\n') within group (order by month_id) as create_view_statement
from cte2
$$;
PROCEDURE FOR EXECUTING THE OUTPUT OF THE UDF ABOVE
create or replace procedure sandbox.public.create_view(TABLE_PATTERN varchar, START_MONTH varchar,END_MONTH varchar)
returns varchar not null
language Javascript
execute as caller
as
$$
sql_command = 'select * from table(sandbox.public.define_view(:1, :2, :3))';
var stmt = snowflake.createStatement({sqlText: sql_command ,binds: [TABLE_PATTERN, START_MONTH, END_MONTH]}).execute();
stmt.next();
var ddl = stmt.getColumnValue(1);
var run=snowflake.createStatement({sqlText: ddl}).execute();
run.next();
var message=run.getColumnValue(1);
return "Temporary " + message;
$$;
USAGE DEMO
set table_pattern ='sandbox.public.table_';
set start_month ='1';
set end_month = '12';
set master_view='master_'||split_part($table_pattern,'.',-1)||$start_month||'_'||$end_month;
call create_view($table_pattern, $start_month, $end_month);
select top 100 *
from identifier($master_view);

Postgresql stored procedure return select result set

In Microsoft SQL server I could do something like this :
create procedure my_procedure #argument1 int, #argument2 int
as
select *
from my_table
where ID > #argument1 and ID < #argument2
And that would return me table with all columns from my_table.
Closest thing to that what I managed to do in postgresql is :
create or replace function
get_test()
returns setof record
as
$$ select * from my_table $$
language sql
or i could define my table type, but manually recreating what technically already exists is very impractical.
create or replace function
get_agent_summary()
returns table (
column1 type, column2 type, ...
)
as
$$
begin
return query select col1, col2, ... from my_existing_table;
...
and it is pain to maintain.
So, how can I easily return resultset without redefining defining every single column from table that I want to return?
In Postgres a table automatically defines the corresponding type:
create or replace function select_my_table(argument1 int, argument2 int)
returns setof my_table language sql as $$
select *
from my_table
where id > argument1 and id < argument2;
$$;
select * from select_my_table(0, 2);
The syntax is more verbose than in MS SQL Server because you can create functions in one of several languages and functions may be overloaded.

Calling User-Defined Function in select statement returning xml data

I created a user-defined function in SQL Server 2012 that returns XML. I would like to call the function in a SELECT statement. Is this possible?
When I try doing it, I get the error:
The FOR XML clause is not allowed in a ASSIGNMENT statement.
I want the SELECT statement to return a set of these named methods that have dependencies of other named methods within their logic.
In the main CTE, I get the latest versions of methods that have dependencies. The UDF goes thru the logic of each method and returns any methods called within it. So, I want to call the UDF in the SELECT statement and return XML of the dependent method names.
The function works and returns XML data. This is the function:
ALTER FUNCTION [dbo].[GetCalledMLMs]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
RETURNS XML
AS
BEGIN
-- Declare the return variable here
DECLARE #CalledMLMs XML
Declare #MLMTbl table (pos int, endpos int, CalledMLM nvarchar(200))
--Logic to get the data...
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
-- Return the result of the function
RETURN #CalledMLMs
END
This is the CTE that calls the UDF.
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from MLM_T
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
group by Name
)
select MLM2.Name, LatestVersion,
dbo.GetCalledMLMs(MLM2.Logic) as CalledMLMs
from cte join MLM_T MLM2 on cte.Name = MLM2.Name
and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
When running this query I get the error that XML is not allowed to be used in assignment statement.
Is there any way to call a function in the SELECT statment that returns an XML data type?
If you want to set a variable to a value you have to use SET and a scalar value on the right side.
The syntax SELECT #SomeVariable=SomeColumn FROM SomeTable is not possible with FOR XML (and rather dangerous anyway...), because the XML is not a column of the SELECT but something after the process of selecting.
Your problem is situated here:
Select #CalledMLMs = CalledMLM from #MLMTbl FOR XML PATH
Try to change this to
SET #CalledMLMs = (SELECT CalledMLM FROM #MLMTbl FRO XML PATH);
I solved the problem by changing the function to return a table, not XML.
So it looks like this:
FUNCTION [dbo].[GetCalledMLMsTbl]
(
-- Add the parameters for the function here
#MLM_Txt nvarchar(MAX)
)
--RETURNS XML
RETURNS #MLMTbl TABLE
(
pos int,
endpos int,
CalledMLM nvarchar(200)
)
AS
BEGIN
--logic here
insert into #MLMTbl (pos, endpos, CalledMLM) Values (#startpos, #endpos, #MLM_name)
RETURN
END
Then I called the function in the 'from' clause in the select
;with cte as
(
select distinct Name, max(ID) as LatestVersion
from CV3MLM
where Logic like '%:= MLM %' and Logic not like '%standard_libs := mlm%'
--and Name not like '%V61_CCC'
group by Name
)
select MLM2.Name, LatestVersion, C.CalledMLM
from cte join MLM_tbl MLM2 on cte.Name = MLM2.Name and cte.LatestVersion = MLM2.ID
and MLM2.Active = 1 and MLM2.Status in (3, 4)
cross apply dbo.GetCalledMLMsTbl(MLM2.Logic) C
order by MLM2.Name, LatestVersion

How do sql can understand or convert a nvarchar is name of a table in sql server

I Waint run code in SQL SERVER
ALTER FUNCTION return_table (#table nvarchar(250))
RETURNS TABLE
AS
RETURN
(
SELECT * FROM #table
)
none using PROCEDURE . THANK FOR HELP
The only way to make this work is truly horrible for both aesthetic and (probably) performance reasons. It also assumes that all tables that might be passed as parameters have the same structure:
ALTER FUNCTION return_table (#table sysname)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM TableA where #table = 'TableA'
UNION ALL
SELECT * FROM TableB where #table = 'TableB'
UNION ALL
SELECT * FROM TableC where #table = 'TableC'
/* add more UNIONs and SELECTs for each table that you want to use */
)
A function requires an explicit definition of the return value type, e.g. the columns being returned. The SQL statement you provided will not work in such a manner for the following reasons:
ALTER FUNCTION return_table (#table nvarchar(250)) -- You have declared a parameter of type table, this is not the right way of doing this
RETURNS TABLE -- you haven't defined the structure of the table being returned, this needs explicitly defining
AS
RETURN
(
SELECT * FROM #table -- using SELECT * is bad practice as the structure of the table may change and then conflict with the structure of the table being returned
)
The first part of the problem is declaring a parameter of type TABLE; this question has good examples on how to get around doing this. Quick summary: you need to declare the table as a type before passing in the type as the parameter to your function:
CREATE TYPE MyTableParameterDefinition AS TABLE (
[ColumnName] NVARCHAR(250) NULL
)
This type can then be passed as a parameter to your function:
CREATE FUNCTION myFunctionName (
#TableVariable MyTableParameterDefinition READONLY
)...--INSERT CODE HERE--
I'm uncertain whether you can use a similar method for the return type and would suggest against this given the implication of the contract defined by the function. Better practice would be to define the structure of the table being returned and explicitly select the columns from the table:
ALTER FUNCTION return_table (
#table MyTableParameterDefinition
)
RETURNS TABLE
(
-- Explicitly define columns returned by the function
[ColumnName] NVARCHAR(250) NULL
)
AS
RETURN
(
SELECT
[ColumnName]
FROM
#table
)

Resources