How to execute dynamic SQL in Teradata - sql

Is there any way to submit dynamically generated SQL to Teradata? I've written a query that will create the code to denormalize a table. Right now, I am pulling the code down to my client (SAS) and resubmitting it in a second step. I am not familiar with either Teradata macros or procedures; would something like that work?
To illustrate, I have a table defined like this:
create multiset table MYTABLE
( RECID integer generated always as identity
( start with 1
increment by 1
minvalue -2147483647
maxvalue 2147483647
no cycle )
, SNAP_DATE date format 'YYYY/MM/DD'
, EMAIL_DATE date format 'YYYY/MM/DD'
, FREQ integer
)
unique primary index ( RECID )
The table is populated every day (SNAP_DATE) and is used to monitor changes to an email_date in another table. The following query returns the code that I can run to create my denormalized view:
select RUN_THIS
from (
select RUN_THIS, RN
from (
select 'select EMAIL_DATE ' (varchar(100)) as RUN_THIS
, 0 (int) as RN
) x
union all
select ', sum( case when SNAP_DATE = date '''
|| (SNAP_DATE (format 'yyyy-mm-dd') (char(10)) )
|| ''' then FREQ else 0 end ) as D'
|| (SNAP_DATE (format 'yyyymmdd') (char(8)) ) as RUN_THIS
, row_number() over ( partition by 1 order by SNAP_DATE ) as RN
from ( select distinct SNAP_DATE
from MYTABLE
where SNAP_DATE > current_date - 30) t1
union all
select RUN_THIS, RN
from (
select 'from MYTABLE group by 1 order by 1;' as RUN_THIS
, 10000 as RN
) y
) t
order by RN
I export the result of the above query to a file on my client, then turn around and submit that file back to Teradata. I'm hoping there is some way to store this complete definition in some Teradata object so it can be executed directly.

You may find success putting this in a stored procedure using the DBC.SysExecSQL command.
Here is an overly simplified example of a stored procedure in Teradata. Obviously in production would want an error handler defined to address things like invalid database objects. Furthermore, you could return the SQLSTATE back as a parameter to test for whether the stored procedure completed successfully or not.
CREATE PROCEDURE SYSDBA.CommentDatabase
(
IN P_Database VARCHAR(30),
IN P_Comment VARCHAR(255),
OUT MSG
)
MAIN: --Label
BEGIN
DECLARE P_SQL_TEXT VARCHAR(4000);
SET P_SQL_TEXT='COMMENT ON DATABASE '||P_DATABASE||' AS '''||P_COMMENT||'''';
CALL dbc.SysExecSQL (:P_SQL_TEXT);
SET MSG = 'Database '||P_DBNAME||' commented successfully!';
END;

Related

SQL Insert Set of Values Optimized

The goal is to create a table with sample data in Teradata for a year. A way to do it, is to copy the first 6 entries of similar data and just alter the timestamp 365 times (for my usecase).
I didn't know better and wrote a procedure
REPLACE PROCEDURE stackoverflow()
BEGIN
DECLARE i INTEGER DEFAULT 0;
DELETE FROM TestTable;
WHILE i < 365 DO
INSERT INTO TestTable
SELECT
TestName
,Name_DT + i
FROM
(SELECT TOP 6
*
FROM TestTable2
WHERE Name_DT = '2021-12-15') AS sampledata;
SET i = i + 1;
END WHILE;
END;
This works, but is awfully slow. Also the IT department doesn't want us to use procedures. Is there a better way to achieve the same result without a procedure?
The generic way to get repeated data is a CROSS JOIN:
SELECT
TestName
,calendar_date
FROM
( SELECT TOP 6 *
FROM TestTable2
WHERE Name_DT = DATE '2015-12-15'
) AS sampledata
CROSS JOIN
( SELECT calendar_date
FROM sys_calendar.CALENDAR
WHERE calendar_date BETWEEN DATE '2011-12-15'
AND DATE '2011-12-15' + 364
) AS cal
;
In your case there's Teradata's proprietary EXPAND ON syntax to create time series:
SELECT TestName, Begin(pd)
FROM
( SELECT TOP 6 *
FROM TestTable2
WHERE Name_DT = DATE '2015-12-15'
) AS sampledata
-- create one row per day in the range
EXPAND ON PERIOD(Name_DT, Name_DT +365) AS pd

LOOP/FOR statement on oracle script

I'm running an ansible playbook that runs an sqlplus script to an Oracle DB.
Basically the script creates a CSV file with some server info. The query is pretty much autogenerated, so it will be difficult change it.
set markup csv on
spool 'playbook-dir/files/servers.csv'
SELECT *
FROM (SELECT DISTINCT server.primary_name SERVER_NAME,
server.arpa_domain ARPA_DOMAIN,
server.impact IMPACT,
instance_definition.category SOLUTION_CATEGORY,
instance_definition.instance_name SOLUTION_NAME,
instance_on_server.ins_instance_name INSTANCE_NAME
FROM server_db.instance_definition, server_db.instance_on_server, server_db.business, server_db.server_customer, server_db.server
WHERE ( server_db.instance_definition.app_id(+) = server_db.instance_on_server.app_id )
AND ( server_db.server.system_id = server_db.instance_on_server.system_id(+) )
AND ( ( instance_definition.instance_name LIKE '%windows%' )
OR ( instance_definition.instance_name LIKE '%centos%' ) )
AND ( instance_on_server.status LIKE 'in production' )
AND ( server_db.business.business_id(+) = server_db.server_customer.business_id )
AND ( server_db.server_customer.system_id(+) = server_db.server.system_id )
AND (( instance_definition.category LIKE 'os' ))
AND (( server.primary_name||'.'||server.arpa_domain LIKE '%' ))
AND business.secure_access_r <> 1)
WHERE ROWNUM <= 600000 + 1
ORDER BY server_name;
spool off
The problem is that this query brings all the 5000 server and I need only 200.
I want to add a LOOP/FOR statement with only the servers I need, but I think I'm doing something wrong.
This is the query with the LOOP:
declare
type table_varchar is table of varchar2(10);
var_table_varchar table_varchar;
begin
var_table_varchar := table_varchar('server1', 'server2', 'server3', 'server4');
for elem in 1 .. var_table_varchar.count loop
SELECT *
FROM (SELECT DISTINCT server.primary_name SERVER_NAME,
server.arpa_domain ARPA_DOMAIN,
server.impact IMPACT,
instance_definition.category SOLUTION_CATEGORY,
instance_definition.instance_name SOLUTION_NAME,
instance_on_server.ins_instance_name INSTANCE_NAME
FROM server_db.instance_definition, server_db.instance_on_server, server_db.business, server_db.server_customer, server_db.server
WHERE ( server_db.instance_definition.app_id(+) = server_db.instance_on_server.app_id )
AND ( server_db.server.system_id = server_db.instance_on_server.system_id(+) )
AND ( ( instance_definition.instance_name LIKE '%windows%' )
OR ( instance_definition.instance_name LIKE '%centos%' ) )
AND ( instance_on_server.status LIKE 'in production' )
AND ( server_db.business.business_id(+) = server_db.server_customer.business_id )
AND ( server_db.server_customer.system_id(+) = server_db.server.system_id )
AND (( instance_definition.category LIKE 'os' ))
AND (( server.primary_name||'.'||server.arpa_domain LIKE '%' ))
AND (( server.primary_name LIKE '%var_assoc_varchar(elem)%' ))
AND business.secure_access_r <> 1)
WHERE ROWNUM <= 600000 + 1
ORDER BY server_name;
end loop;
end;
When I run it I get this error:
Error report -
ORA-06550: line 9, column 5:
PLS-00428: an INTO clause is expected in this SELECT statement
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Sadly, I'm not much of a DBA and my knowledge got me so far. So suggestion will be appreciated
You can not use plain select sql without into clause like this in PL/SQL.
What are you expecting out of the query in PL/SQL?
Your first query executed and filled csv because it is single query and not anonymous block.
You can simply add condition into where clause of your first query as follows:
server.primary_name in ('server1', 'server2', 'server3', 'server4')

Converting SQL Server Query to Oracle

I have the SQL Server query shown below and I am trying to get it to work on Oracle. I have done some looking but am new to Oracle so I am not even sure what I should be looking for. I am hoping that I can make the a query to run adhoc not necessarily a procedure. The basic concept is the following:
Create a temporary table to hold data to be totaled.
Create a date table to get a list of dates.
Build a dynamic query to insert data into the temporary table.
Execute the dynamic query.
Select the summary from the temporary table.
Drop the temporary table
Please remember, I am new to Oracle and I have done some looking. I know that the variables and aliases must be formatted differently and can get the date table but I am not sure the proper Oracle way to create and execute dynamic queries where the table name is the dynamic part. I can create the dynamic string with the correct table name but don't know how to execute it. I have seen some examples but none of them seem to make sense to me for what I am trying to do.
-- Oracle query for dates
with dt (d) as (
select last_day(add_months(sysdate,-2))+1 + rownum - 1
from all_objects
where rownum <= sysdate-last_day(add_months(sysdate,-2))+1+1
)
select 'insert into #tt (cnt, eem, ers, sts) (
select count(1), eem_id, ers_id, sts_id
from event_error' || to_char(D, 'ddmmyy') || ' eve
group by sts_id, eem_id, ers_id); ' "QRY"
from dt;
What I have done in the past is create a bash script which would do the looping through each date and then used the script to summarize the output. This time however I am trying to learn something and I know that there has to be a way to do this in SQL in Oracle.
I appreciate any help or assistance and hope I have explained this well enough.
-- Working SQL Server query
-- declare variables
declare #query varchar(max);
-- create temporary table
create table #tt(cnt int, eem int, ers int, sts int);
-- get a list of dates to process
with dt (d) as
(
select dateadd(month, datediff(month, 0, getdate())-1, 0) as d
union all
select dateadd(dd, 1, d)
from dt
where dateadd(dd, 1, d) <= getdate()
)
-- build the dynamic query
select distinct
#query = stuff ((select 'insert into #tt (cnt, eem, ers, sts) (
select count(1), eem_id, ers_id, sts_id
from event_error' + replace(convert(varchar(5), d, 103), '/', '') + right(year(d), 2) + ' (nolock) eve
group by sts_id, eem_id, ers_id); '
from dt for xml path, type).value(N'.[1]',N'nvarchar(max)')
, 1, 1, N'')
from dt;
-- to execute the dynamic query
execute (#query);
-- query the temporary table
select
[Stream] = sts.sts_name,
[Count] = sum(eve.cnt),
[Error Status] = ers.ers_name,
[Error Number] = eem.eem_error_no,
[Error Text] = eem.eem_error_text
from
#tt eve
inner join
event_error_message eem on eem.eem_id = eve.eem
inner join
error_status ers on ers.ers_id = eve.ers
inner join
stream_stage sts on sts.sts_id = eve.sts
group by
sts.sts_name, eem.eem_error_no, eem.eem_error_text, ers.ers_name
order by
sts.sts_name, eem.eem_error_no, ers.ers_name;
-- drop the temporary table
drop table #tt;
So as I expected, after fighting this all day and finally giving up and asking for help I have an answer. The query below works however if you have improvements or constructive criticism please share as I said, I am trying to learn.
-- create the temporary table
create global temporary table my_tt (
cnt number
, sts number
, eem number
, ers number
)
on commit delete rows;
declare
V_TABL_NM ALL_TABLES.TABLE_NAME%TYPE;
V_SQL VARCHAR2(1024);
begin
for GET_TABL_LIST in (
with dt (d) as (
select last_day(add_months(sysdate,-2))+1 + rownum -1
from all_objects
where rownum <= sysdate-last_day(add_months(sysdate,-2))
)
select 'event_error' || to_char(D, 'ddmmyy') TABLE_NAME from dt
) loop
V_TABL_NM := GET_TABL_LIST.TABLE_NAME;
V_SQL := 'insert into my_tt select count(1), sts_id, eem_id, ers_id from ' || V_TABL_NM || ' group by sts_id, eem_id, ers_id';
execute immediate V_SQL;
end loop;
end;
/
-- the slash is important for the above statement to complete
select
sts.sts_name "Stream"
, sum(eve.cnt) "Count"
, ers.ers_name "Error Status"
, eem.eem_error_no "Error Number"
, eem.eem_error_text "Error Text"
from my_tt eve
inner join event_error_message eem
on eem.eem_id = eve.eem
inner join error_status ers
on ers.ers_id = eve.ers
inner join stream_stage sts
on sts.sts_id = eve.sts
group by sts.sts_name, eem.eem_error_no, eem.eem_error_text, ers.ers_name
order by sts.sts_name, eem.eem_error_no, ers.ers_name;
-- drop the temporary table
drop table my_tt purge;

Transact SQL Subquery calling a function incorrect syntax

I get an incorrect syntax near '.' and can't seem to identify why in the following code:
select
o.object_id,
(select top 1 Zone from dbo.getzone(o.object_id)) as Zone from object as o
getzone is a table-valued Function that works perfectly when I reference it directly, or if I put a specific object_id in, but everytime I try to make it dynamic, I get the syntax error.
What am I missing?
You can't do that. You need to have a scalar version that returns only one result. It can be just a wrapper script if you want. Something like this:
CREATE FUNCTION [dbo].[getSingleZone](#object_id varchar(20))
RETURNS varchar(20)
AS
BEGIN
DECLARE #Zone varchar(20)
select #Zone = max(Zone) from dbo.getzone(#object_id)
return #Zone
END
select
o.object_id,
dbo.getSingleZone(o.object_id) as Zone from object o
I don't know your data types, so I guessed.
Fix your alias
select o.object_id,
(select top 1 Zone from dbo.getzone(o.object_id)) as Zone
from object AS o
Perhaps I'm missing the problem, but this seems to work. Using the name of a built-in function (OBJECT_ID) as a column name might not be helping.
SQL fiddle example or code below.
-- TVF without parameter.
create function dbo.GetZone()
returns table as
return
select Id, Letter
from
( values ( 1, 'Aleph' ), ( 2, 'Beth' ), ( 3, 'Gimmel' ) ) as Letters( Id, Letter );
go
-- TVF with parameter;
create function dbo.GetZone2( #Id as Int )
returns table as
return
select Id, Letter
from dbo.GetZone() where Id = #Id;
go
select * from dbo.GetZone();
select * from dbo.GetZone2( 2 );
-- Sample table and data.
declare #Objects as table ( Id Int Identity, Letter VarChar(16) );
insert into #Objects values ( 'Alpha' ), ( 'Beta' ), ( 'Gamma' );
select * from #Objects;
-- Correlated subquery.
select O.Id, O.Letter as [Greek],
( select top 1 Letter from dbo.GetZone( ) where Id = O.Id ) as [Hebrew]
from #Objects as O;
select O.Id, O.Letter as [Greek],
( select top 1 Letter from dbo.GetZone2( O.Id ) ) as [Hebrew]
from #Objects as O;
-- Houseclean.
drop function dbo.GetZone;
drop function dbo.GetZone2;

Can the Table Valued function that I wrote be turned into a View in Sql Server 2005

I am still so new to all this and I think I may have not done this the best way. I have a Table Valued function that I wrote, but I think that it could be written as a view.
The big catch as to why I used a table val function is that if the select query returns no results then I wanted to return a "default" row that showed empty values along with the timestamp and I didn't know how to do that in a view.
I betting the experts here know how to. Here's the function:
alter FUNCTION [dbo].[GetCurrentRTBindingConstraints]()
RETURNS
#CurrentBindingConstraints table (
CONSTRAINTNAME [nvarchar] (120),
MKTHOUR_EST [dateTime],
MARGINALVALUE [nvarchar] (20)
)
AS
BEGIN
INSERT INTO #CurrentBindingConstraints
select * from
OPENQUERY(UDS9, 'select
CONSTRAINTNAME, MKTHOUR -(5/24) as MKTHOUR_EST,MARGINALVALUE
from UDS9.MKTPLANCONSTRAINT mpc
where MARGINALVALUE != 0.00 and mpc.caseid=(SELECT caseid FROM uds9.MktCase
WHERE casestartinterval=(SELECT MAX(casestartinterval) FROM uds9.MktCase WHERE casestate=5 AND studymodeid=5)
AND casestate=5 AND studymodeid=5)')
DECLARE #cnt INT
SELECT #cnt = COUNT(*) FROM #CurrentBindingConstraints
IF #cnt = 0
INSERT INTO #CurrentBindingConstraints (
[CONSTRAINTNAME],
[MKTHOUR_EST],
[MARGINALVALUE])
VALUES ('None',dbo.RoundTime(dbo.GetGMTtoEST(getutcdate())),'None')
RETURN
END
You can use a common table expression (CTE) and a ranking function as follows:
;with Defaulted as (
select 'none' as Col1,CURRENT_TIMESTAMP as Col2,'none' as Col3,1 as init -- This is your default row
union all
select name,DATEADD(day,-1,CURRENT_TIMESTAMP),name,0 from sys.objects -- This is where you query for real rows
), Ranked as (
select Col1,Col2,Col3,RANK() OVER (ORDER BY init) as rnk from Defaulted
)
select * from Ranked where rnk = 1
The above is just an example - you'd need to replace the two selects inside the first CTE with your real queries, and should use column names rather than select *. It works because the ranking function (RANK()) is able to assess the result set as a whole.
Edit - trying with your actual queries:
create view CurrentRTBindingConstraints
as
;with Defaulted as (
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE,0 as init from
OPENQUERY(UDS9, 'select
CONSTRAINTNAME, MKTHOUR -(5/24) as MKTHOUR_EST,MARGINALVALUE
from UDS9.MKTPLANCONSTRAINT mpc
where MARGINALVALUE != 0.00 and mpc.caseid=(SELECT caseid FROM uds9.MktCase
WHERE casestartinterval=(SELECT MAX(casestartinterval) FROM uds9.MktCase WHERE casestate=5 AND studymodeid=5)
AND casestate=5 AND studymodeid=5)')
union all
select 'None',dbo.RoundTime(dbo.GetGMTtoEST(getutcdate())),'None',1
), Ranked as (
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE,RANK() OVER (ORDER BY init) as rnk from Defaulted
)
select CONSTRAINTNAME,MKTHOUR_EST,MARGINALVALUE from Ranked where rnk = 1