Dynamic Creation of External Tables in Synapse - stored-procedures

I have a json string which is the result of Get Metadata activity in data factory in the following format: {"structure": [{"name": "Id","type": "String"},{"name": "IsDeleted","type": "Boolean"},{"name": "RowVersion","type": "Byte[]"}, {"name": "ModelId","type": "String"}]
This json is showing the schema of parquet file. I pass this json along with the parquet filename and uctnow() to a stored procedure to create a table of schema for that file with the following code
CREATE OR ALTER PROCEDURE [CreateExternalTables] (
#schema NVARCHAR (MAX), #tableName NVARCHAR(MAX), #ExecTime NVARCHAR(MAX)
) AS
BEGIN
IF OBJECT_ID('tempdb..#tables_to_create', 'U') IS NOT NULL
DROP TABLE #tables_to_create
CREATE TABLE #tables_to_create (
tableName NVARCHAR (MAX),
fieldOrder NVARCHAR (MAX),
fieldName NVARCHAR (MAX),
fieldType NVARCHAR (MAX),
translatedType NVARCHAR (MAX),
executeTime NVARCHAR (MAX)
)
BEGIN
WITH Fields (fieldOrder, fieldName, fieldType) AS (
SELECT
[key] AS fieldOrder,
JSON_VALUE([value], '$.name') AS fieldName,
JSON_VALUE([value], '$.type') AS fieldType
FROM
OPENJSON(#schema)
)
INSERT INTO
#tables_to_create(
tableName,
fieldOrder,
fieldName,
fieldType,
translatedType,
executeTime
)
SELECT
#tableName,
fieldOrder,
fieldName,
fieldType,
CASE
WHEN fieldType = 'Single' THEN 'real'
WHEN fieldType = 'Boolean' THEN 'bit'
WHEN fieldType = 'Double' THEN 'float'
WHEN fieldType = 'Int64' THEN 'bigint'
ELSE NULL
END AS translatedType,
#ExecTime
FROM
Fields
END
END;
The overall setup is like below:
But I receive the following error which I'm not sure why:
What I would like to do is to create this temo table in stored procedure in order to create external tables in a automatic way.
To be precise I'm following this link to do it:
create-external-dataset
Any help is highly appreciated.
UPDATE
After solving the first problem with #wBob 's help I follow the rest of the link to create the external tables with the following which I inserted into my stored procedure:
FROM
Fields
END
Declare #sqlCommand nvarchar(max);
Declare #folderPath nvarchar(max);
SET
#sqlCommand = 'IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N''[dbo].[' + #tableName + ']'') AND type in (N''U''))
CREATE EXTERNAL TABLE [dbo].[' + #tableName + '] ('
WHILE((SELECT COUNT(*) FROM #tables_to_create) > 0)
BEGIN
DECLARE #key int
SELECT
#key = MIN(fieldOrder)
FROM
#tables_to_create
WHERE
executeTime = #ExecTime
DECLARE #fieldName VARCHAR(50)
DECLARE #translatedType VARCHAR(50)
SELECT
#fieldName = fieldName,
#translatedType = translatedType
FROM
#tables_to_create
WHERE
fieldOrder = #key
AND executeTime = #ExecTime
SET
#sqlCommand = #sqlCommand + '
[' + #fieldName + '] [' + #translatedType + '] NULL'
DELETE FROM
#tables_to_create
WHERE
fieldOrder = #key
AND executeTime = #ExecTime
IF((SELECT COUNT(*) FROM #tables_to_create WHERE executeTime = #ExecTime) > 0)
SET
#sqlCommand = #sqlCommand + ', '
END
SET
#sqlCommand = #sqlCommand + '
)
WITH
(
LOCATION = ''/' + /main/json/ + ''',
DATA_SOURCE = DataLakeStaged,
FILE_FORMAT = StagedParquet
)'
EXEC(#sqlCommand)
END;
However I receive the following error:

As per the error message, queries are not supported in the conditions of WHILE statement in Azure Synapse Analytics. Dedicated SQL pools are MPP systems that have some slight differences from box-product SQL Server and Azure SQL DB and more generally, loops are a pattern that don't translate that well. You should consider creating a stored procedure that does not loop and leave any looping required to the For Each activity in Azure Data Factory (ADF) / Synapse Pipelines which can loop in parallel.
If you do require a loop, there are multiple examples of doing loops in dedicated SQL pools online, but there is a good example in the official documentation, reproduced here in the event the link moves:
-- First, create a temporary table containing a unique row number used to identify the individual statements:
CREATE TABLE #tbl
WITH
( DISTRIBUTION = ROUND_ROBIN
)
AS
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Sequence
, [name]
, 'UPDATE STATISTICS '+QUOTENAME([name]) AS sql_code
FROM sys.tables
;
-- Second, initialize the variables required to perform the loop:
DECLARE #nbr_statements INT = (SELECT COUNT(*) FROM #tbl)
, #i INT = 1
;
-- Now loop over statements executing them one at a time:
WHILE #i <= #nbr_statements
BEGIN
DECLARE #sql_code NVARCHAR(4000) = (SELECT sql_code FROM #tbl WHERE Sequence = #i);
EXEC sp_executesql #sql_code;
SET #i +=1;
END

Related

update a column inside a stored procedure

I have two snowflake tables as below
TEST1
create OR REPLACE table TEST1 (
id varchar(100),
name varchar(100),
org VARCHAR(64)
);
INSERT INTO TEST1 values (100, 'ABC', null);
INSERT INTO TEST1 values (200, 'XYZ', null);
INSERT INTO TEST1 values (300, 'VBN', null);
CONTROL
create OR REPLACE table CONTROL (
KEY_COLUMNS VARCHAR,
TABLE_NAME VARCHAR,
org VARCHAR
);
INSERT INTO CONTROL values ('id,name, address','TEST1','Z');
INSERT INTO CONTROL values ('id,name, address','TEST2','T');
I have created a stored procedure which updates column 'org' in TEST1 table with the values from CONTROL table (column org) where table name is TEST1.
CREATE OR REPLACE PROCEDURE TEST(source_tbl VARCHAR)
RETURNS string
LANGUAGE SQL
AS
$$
DECLARE
query1 STRING;
BEGIN
QUERY1 := 'update TEST1 set ORG = (select org from CONTROL WHERE TABLE_NAME = ''source_tbl'');';
EXECUTE IMMEDIATE :QUERY1;
RETURN :QUERY1;
END;
$$;
call TEST((select TABLE_NAME from CONTROL WHERE TABLE_NAME = 'TEST1'));
expected output
My output
The parameter should be bound variable instead of passing as the string constant ''source_tbl'':
CREATE OR REPLACE PROCEDURE TEST(source_tbl VARCHAR)
RETURNS string
LANGUAGE SQL
AS
$$
DECLARE
query1 STRING;
BEGIN
QUERY1 := 'update TEST1 set ORG = (select org from CONTROL WHERE TABLE_NAME = ?);';
EXECUTE IMMEDIATE :QUERY1 USING (SOURCE_TBL);
RETURN :QUERY1;
END;
$$
Related: EXECUTE IMMEDIATE:
EXECUTE IMMEDIATE '<string_literal>'
[ USING (bind_variable_1 [, bind_variable_2 ...] ) ] ;

How to Execute Macro through Stored Procedure

I have created one macro in Teradata, now wish to call that macro through Stored Procedure (in Teradata only). The SQL part belongs to macro.
I wrote this procedure in case if execution of macro is not possible through procedure.
Kindly suggest both the option.
CREATE PROCEDURE MDM_STAGE.match_sqls_proc
(
in fname VARCHAR(30),
in match_frst_name VARCHAR (100),
in lname VARCHAR(30),
in match_last_name VARCHAR (100),
in addr1 VARCHAR(1000),
in zip_cd_base varchar(10),
in email VARCHAR(30),
in phone VARCHAR(30),
out msgs VARCHAR(10)
-- INOUT errstr VARCHAR(30)
)
begin
DECLARE SQLTEXT2 VARCHAR (10) ;
DECLARE TBL_COUNT INT ;
select count (*) into :TBL_COUNT from
(
SELECT
CUST.CUST_ID,
CUST.FRST_NAME,
CUST.MATCH_FRST_NAME,
CUST.LAST_NAME,
CUST.MATCH_LAST_NAME,
CUST.GNDR_TYPE_CD,
STREET_ADDR.ADDR_LN_1_TXT,
STREET_ADDR.ADDR_LN_2_TXT,
STREET_ADDR.ADDR_LN_3_TXT,
cast (coalesce (STREET_ADDR.ADDR_LN_1_TXT,'') || ' ' || coalesce
(STREET_ADDR.ADDR_LN_2_TXT,'' ) as varchar (1000)) as addr,
STREET_ADDR.CITY_NAME,
STREET_ADDR.POSTL_CD,
STREET_ADDR.STREET_ADDR_ID,
STREET_ADDR.ADDR_SBTYPE_CD,
ELCTRNC_ADDR.ELCTRNC_ADDR_ID,
ELCTRNC_ADDR.ELCTRNC_ADDR_SBTYPE_CD,
ELCTRNC_ADDR.ELCTRNC_ADDR_TXT,
TLPHN_NUM.TLPHN_LN_NUM,
TLPHN_NUM.TLPHN_NUM_ID
FROM
MDM_STAGE.REF_CUST_V CUST
LEFT OUTER JOIN
MDM_STAGE.REF_STREET_ADDR_V STREET_ADDR
ON
CUST.CUST_ID = STREET_ADDR.CUST_ID
AND
--RDM_ADDRSIM (STREET_ADDR.ADDR_LN_1_TXT ,'2285Main Street' ) =1
RDM_ADDRSIM ( cast (coalesce (STREET_ADDR.ADDR_LN_1_TXT,'') || ' ' ||
coalesce (STREET_ADDR.ADDR_LN_2_TXT,'' ) as varchar (1000)) ,:addr1) =1
AND
SUBSTR(STREET_ADDR.POSTL_CD,0,6) = (:zip_cd_base)
LEFT OUTER JOIN
MDM_STAGE.REF_ELCTRNC_ADDR_V ELCTRNC_ADDR
ON
CUST.CUST_ID = ELCTRNC_ADDR.CUST_ID
/*AND
STREET_ADDR.ADDR_SBTYPE_CD = ELCTRNC_ADDR.ELCTRNC_ADDR_SBTYPE_CD*/
AND
ELCTRNC_ADDR.ELCTRNC_ADDR_TXT= (:email)
LEFT OUTER JOIN
MDM_STAGE.REF_TLPHN_NUM_V TLPHN_NUM
ON
CUST.CUST_ID = TLPHN_NUM.CUST_ID
/*AND
STREET_ADDR.ADDR_SBTYPE_CD = TLPHN_NUM.ADDR_SBTYPE_CD*/
AND
TLPHN_NUM.TLPHN_LN_NUM = (:phone)
WHERE
(RDM_sndx(COALESCE(CUST.Match_FRST_NAME,CUST.CUST_ID))=RDM_sndx(:match_frst_name)
AND
RDM_sndx(COALESCE(STRTOK(CUST.Match_LAST_NAME,' ',1),CUST.CUST_ID))=RDM_SNDX(:match_last_name)
)
AND
(
TLPHN_NUM.CUST_ID IS NOT NULL
OR
ELCTRNC_ADDR.CUST_ID IS NOT NULL
OR
STREET_ADDR.CUST_ID IS NOT NULL
) A
;
IF ( TBL_COUNT > 0 ) THEN SET SQLTEXT2 = 'Match' ;
ELSE
SET Msgs = 'Non-Match' ;
END IF;
end;
Error message -
SPL1027:E(L88), Missing/Invalid SQL statement'E(3707):Syntax error, expected something like an 'EXCEPT' keyword or an 'UNION' keyword or a 'MINUS' keyword between ')' and the word 'A'.'.
No idea, what to add between the word 'A' and ')'. Tried all the possibility but not successful. Not sure how to pass the value of SQL into the 'count' as later call procedure condition is based on that count only.

Firebird and stored procedures: if exists then else

I'm trying to create a stored procedure for firebird 2.1 (this is the version which is to be used)
But am getting a bit stuck, so any help is appreciated.
The final version should compare 4 values agains the table, and retreive either the primaryid if the value exists, or create the new entry in the table, and return the new primaryid.
But I get stuck with only one value lookup, and it's not even using the variable yet.
SET TERM ^ ;
CREATE PROCEDURE TESTSP
( A Varchar(64) )
RETURNS
( RESULT Integer )
AS
BEGIN
IF (EXISTS (SELECT PRIMARYID FROM TABLENAME WHERE FIELD = 'Some string')) then
SELECT PRIMARYID FROM TABLENAME WHERE FIELD = 'Some string' into :primaryid;
result = PRIMARYID;
ELSE
INSERT INTO TABLENAME (FIELD) VALUES ('Some string');
result = gen_id(GEN_TABLEID, 0);
END^
SET TERM ; ^
I get a "Token unknown" for the Else command.
Update after responses:
Now I want to use the 4 variables and return the 4 results.
I think I need a for loop to do so, but with firebird, the for function means something else.
So what would be the way to go?
SET TERM ^ ;
CREATE PROCEDURE TESTSP
( value1 Varchar(64) )
RETURNS
( RESULT1 Integer )
AS
BEGIN
IF (EXISTS (SELECT PRIMARYID FROM TABLENAME WHERE FIELD = :value1)) then
SELECT PRIMARYID FROM TABLENAME WHERE FIELD = value1 into :result1;
ELSE BEGIN
result1 = gen_id(GEN_TABLEID, 1);
INSERT INTO TABLENAME (PRIMARYID, FIELD) VALUES (:result1, :value1);
END
suspend;
END^
SET TERM ; ^
As Tico already answered you have to use begin / end to group multiple statements in then / else part. The error abut column PRIMARYID being unknown is because you reference to it without having declared a local variable for it. Try this:
CREATE PROCEDURE TESTSP ( A Varchar(64) )
RETURNS ( RESULT Integer )
AS
BEGIN
-- initialize the result
Result = NULL;
-- check is the string already in table
SELECT PRIMARYID FROM TABLENAME WHERE FIELD = 'Some string' into :Result;
IF (Result is NULL) then
INSERT INTO TABLENAME(PRIMARYID, FIELD) VALUES(gen_id(GEN_TABLEID, 1), 'Some string') RETURNING PRIMARYID INTO :Result;
END^
I think your stored procedure should look like this:
SET TERM ^ ;
CREATE PROCEDURE TESTSP
( A Varchar(64) )
RETURNS ( result Integer )
AS
BEGIN
IF (EXISTS (SELECT PRIMARYID
FROM TABLENAME
WHERE FIELD = 'Some string')) then
SELECT PRIMARYID
FROM TABLENAME
WHERE FIELD = 'Some string'
into :result;
ELSE BEGIN
result = gen_id(GEN_TABLEID, 1);
INSERT INTO TABLENAME
(PRIMARYID, FIELD)
VALUES (:result, 'Some string');
END
END^
SET TERM ; ^
If you have multiple instruction for then and/ or else clause you must use BEGIN ... END-block!
SET TERM ^ ;
CREATE PROCEDURE TESTSP
( A Varchar(64) )
RETURNS
( RESULT Integer )
AS
BEGIN
IF (EXISTS (SELECT PRIMARYID FROM TABLENAME WHERE FIELD = 'Some string')) then
BEGIN
SELECT PRIMARYID FROM TABLENAME WHERE FIELD = 'Some string' into :primaryid;
result = PRIMARYID;
END
ELSE
BEGIN
INSERT INTO TABLENAME (FIELD) VALUES ('Some string');
result = gen_id(GEN_TABLEID, 0);
END
END^
SET TERM ; ^

Trying to write an UPDATE comm in a Stored Procedure with Dynamic SQL

Here goes, second post, first didn't go to well....
I am calling an SP from a Gridview in ASP. I pass in a table name as a variable along with some other variables. I need to UPDATE my original table that the Gridview attaches to but also build a Datalist, I have used a Fetch and got it working fine. Cycle through those records and INSERT data into third table. I was told (in first post) I need to build the SQL string first then execute it. when I write it that way the second part of the insert does not work.
Here is the code in its sliced up form because of efforts to find a succesfull structure....
#currTable varchar(100),
#ID int,
#short_Text varchar(250),
#brief_Descrip varchar(250) = Null,
#needsTranslation varchar(10) = Null,
#prev_LangString varchar(250) = Null,
#lang_String varchar(250) = Null,
#original_lang_String varchar(250) = Null,
#StringID_from_Master varchar(250),
#GUID varchar(250) = Null
/*
*/
AS
SET NOCOUNT ON;
DECLARE #userTable AS VARCHAR(200);
SET #userTable = #currTable
DECLARE #submitDate1 DATETIME;
SET #submitDate1 = GETDATE()
SET #prev_LangString = #original_lang_String
SET #needsTranslation = 'false'
DECLARE #sql varchar(max)
-- Establish update to the language tabel of user and prepare to search DB for all strings that will need to be updated.
BEGIN
-- DECLARE #sql nvarchar(4000)
SELECT #sql = ' UPDATE ' + #currTable +
' SET [lang_String] = ' + #lang_String +
' WHERE (ID = ' + #ID + ' ';
EXEC sp_executesql #sql, N'#ID nvarchar(10)', #ID
-- UPDATE #userTable
-- SET [lang_String] = #lang_String, [date_Changed] = #submitDate1, [prev_LangString] = #prev_LangString, [needsTranslation] = #needsTranslation, [brief_Descrip] = #brief_Descrip
-- WHERE (ID = #ID)
END
BEGIN
DECLARE usedIN_DBScursor CURSOR
FOR
SELECT tblUniquetblStringsMaster_ID, Database_Name, dbKeyID_ofStringName
FROM tblDBUsage
WHERE (tblUniquetblStringsMaster_ID = #StringID_from_Master );
-- Declare the variables to store the values returned by FETCH.
DECLARE #tblUniquetblStringsMaster_ID AS INT;
DECLARE #dbKEYID as INT;
DECLARE #dbName as varchar(100);
OPEN usedIN_DBScursor;
-- Perform the first fetch and store the values in variables.
-- Note: The variables are in the same order as the columns
-- in the SELECT statement.
FETCH NEXT FROM usedIN_DBScursor
INTO #tblUniquetblStringsMaster_ID, #dbName, #dbKEYID;
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
-- Update pending strings table with translation.
BEGIN
INSERT INTO tblPendingDBUpdates
(stringMasterID, databaseName, databaseStringID, englishText, foreignLangText, submitDate, GUID)
VALUES (#StringID_from_Master, #dbName, #dbKEYID, #short_Text, #lang_String, #submitDate1, #GUID);
END
-- SET #sql = ''
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM usedIN_DBScursor
INTO #tblUniquetblStringsMaster_ID, #dbName, #dbKEYID;
END
CLOSE usedIN_DBScursor;
DEALLOCATE usedIN_DBScursor;
END
RETURN
It seems that your procedure could be re-written as follows:
ALTER PROCEDURE dbo.procedure_name
#currTable varchar(100),
#ID int,
#short_Text varchar(250),
#brief_Descrip varchar(250) = Null,
#needsTranslation varchar(10) = Null,
#prev_LangString varchar(250) = Null,
#lang_String varchar(250) = Null,
#original_lang_String varchar(250) = Null,
#StringID_from_Master varchar(250),
#GUID varchar(250) = Null
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N' UPDATE ' + QUOTENAME(#currTable) + ' SET [lang_String] = '''
+ REPLACE(#lang_String,'''','''''') + ''' WHERE ID = ' + RTRIM(#ID) + ';';
EXEC sp_executesql #sql;
INSERT tblPendingDBUpdates
(
stringMasterID,
databaseName,
databaseStringID,
englishText,
foreignLangText,
submitDate,
[GUID]
)
SELECT
#StringID_from_Master,
Database_Name,
dbKeyID_ofStringName,
#short_Text,
#lang_String,
#submitDate1,
#GUID
FROM
tblDBUsage
WHERE tblUniquetblStringsMaster_ID = #StringID_from_Master;
END
GO
However, I am not sure why you need a cursor in your original version, or what you mean by "does not work." Can you explain?
I don't quite understand your problem, but I see one thing that's obviously wrong: In the SQL you're building a string literal is missing its quotes and there's a missing parenthesis. The SQL you're generating would look like this:
UPDATE table_name SET [lang_String] = lang_string WHERE (ID = 123
So it should be:
DECLARE #sql nvarchar(4000)
SELECT #sql = 'UPDATE ' + #currTable + ' SET [lang_String] = ''' + #lang_String + ''' WHERE ID = ' + #ID
Which generates this SQL:
UPDATE table_name SET [lang_String] = 'lang_string' WHERE ID = 123

sql server 2000 stored procedure

I have created a store procedure for updating the patient name in multiple tables. When I execute it, shows the error like
Server: Msg 170, Level 15, State 1,
Line 1 Line 1: Incorrect syntax near
'='.
This is my code. How to recover it? Please help me
create procedure uppatname #pid varchar(150),#pname varchar(150)
as begin
declare #i as integer
declare #i1 as integer
declare #ttnm as varchar(100)
declare #tblnam as varchar(100)
drop table tbname
SELECT IDENTITY(int, 1,1) AS RowNumber, table_name
INTO tbname
FROM information_schema.columns
WHERE column_name = 'pid'
AND table_catalog = 'hospital'
AND table_name NOT LIKE 'T%'
SET #i = (select count(*) from information_schema.columns
where column_name='pid' and table_catalog='hospital'
and table_name not like 'T%')
SET #i1 = 1
WHILE #i1 <= #i
BEGIN
SET #tblnam = (select table_name from tbname where rownumber = #i1)
SET #ttnm = ('select * from ' + #tblnam + 'where pid = ' + #pid)
EXEC (#ttnm)
SET #i1 = #i1 + 1
END
END
What is the need for this task? Instead of storing denormalised redundant copies of the same name that you then have to manually keep up-to-date can't you store it in one place then join on it?
Some random comments about the code.
Why are you dropping and recreating the table tbname each time instead of just using truncate?
Although why are you using a permanent table for this at all? If you have concurrent executions of the stored procedure you could end up updating the wrong patient record with the wrong name. You could just use a #temp table or a #table variable
You don't need to do the SELECT COUNT(*)... and run the query again get the rowcount. You can just do ##rowcount
You should use quote_name around the table name and schema qualify it. As pid is not numeric it would need to be quoted in the query though actually you should use sp_executesql to allow you to parameterise pid
CREATE PROCEDURE uppatname #pid VARCHAR(150),
#pname VARCHAR(150)
AS
BEGIN
DECLARE #i AS INTEGER
DECLARE #i1 AS INTEGER
DECLARE #dyn_sql AS NVARCHAR(4000)
DECLARE #tblnam AS SYSNAME
DECLARE #schema_name SYSNAME
DECLARE #tbname TABLE (
RowNumber INT IDENTITY(1, 1) PRIMARY KEY,
schema_name SYSNAME,
table_name SYSNAME )
INSERT INTO #tbname
SELECT TABLE_SCHEMA,
TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'pid'
AND TABLE_CATALOG = 'hospital'
AND TABLE_NAME NOT LIKE 'T%'
SET #i = ##ROWCOUNT
SET #i1 = 1
WHILE #i1 <= #i
BEGIN
SELECT #tblnam = table_name,
#schema_name = schema_name
FROM #tbname
WHERE RowNumber = #i1
SET #dyn_sql = N'SELECT *
FROM ' + QUOTENAME(#schema_name) + '.'
+ QUOTENAME(#tblnam) + '
WHERE pid = #pid';
EXEC sp_executesql
#dyn_sql,
N'#pid VARCHAR(150)',
#pid=#pid
SET #i1 = #i1 + 1
END
END
NB: You could also concatenate the whole script in a single query and execute it but that technique isn't 100% guaranteed so I haven't used it above.

Resources