Create table with concatenating current year and month - sql

I want to create a table in Postgresql if it is not already there and insert some data in it.
The table has to have the following name convention which is test_yyyymm where yyyymm is current year and month.
I tried various ways and stopped on dynamic sql.
DO
$$
BEGIN
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || 'select concat(''test_'', (select to_char(now(), ''yyyymm'')))::text'|| ' (temp_id INT NOT NULL,
temp_name varchar(150),
PRIMARY KEY (temp_id))';
END;
$$
if I make a select with only this part
select concat('test_', (select to_char(now(), 'yyyymm')))::text
I get the name that I want, but it does not work inside dynamic sql.
I've tried to remove that line and replace it with a static name and it works like a charm, so I am sure that, the problem is in the table name.
DO $$
BEGIN
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || 'test_table' || ' (temp_id INT NOT NULL,
temp_name varchar(150),
PRIMARY KEY (temp_id))';
END;
$$
How can I achieve this in a humane way?
Thanks in advance!

You were close. select isn't needed, just use the conversion as-is.
DO $$
BEGIN
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || concat('Log_',to_char(now(), 'yyyymm')) || ' (temp_id INT NOT NULL,
temp_name varchar(150),
PRIMARY KEY (temp_id))';
END;
$$

Related

Select into record dynamically

I have a type table_array which is a table of t_timestamp records. This record is as below:
TYPE t_timestamp IS RECORD (
table_name VARCHAR2(50),
table_id VARCHAR2(50),
table_timestamp TIMESTAMP(6)
);
I later declare an instance of this table_array as below:
l_tables table_array := table_array();
I would like to create a procedure add_to_record() to easily add to this table_array by doing something like the following:
l_tables.extend;
add_to_record(l_tables, 'table_name', 'CRE_SHRE_CLASS');
add_to_record(l_tables, 'table_id', share_class_id);
add_to_record(l_tables, 'table_timestamp', l_table_timestamp);
Inside add_to_record(), I initially tried using EXECUTE IMMEDIATE to dynamically insert into to the record based on the passed parameters like below:
PROCEDURE add_to_record (tables IN OUT table_array, key IN VARCHAR2, value IN VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE '
SELECT
' || value || '
INTO tables(tables.last).' || key || '
FROM dual'
;
END add_to_record;
However, this didn't work as the correct syntax for inserting into a variable using EXECUTE IMMEDIATE is EXECUTE_IMMEDIATE some_query INTO some_variable. This leaves me in a predicament though. Because I can do the following:
l_select_value_from_dual_query := '
SELECT
' || value || '
FROM dual'
;
But the variable that I want to insert this value into is itself dynamic, and from what I can see, the INTO some_variable part of an EXECUTE IMMEDIATE query cannot be dynamic. So the following doesn't work:
EXECUTE IMMEDIATE l_select_value_from_dual_query INTO tables(tables.last).' || key || ';
Is there any way around this? I'd like to avoid some sort of IF-THEN-ELSE if possible.

HANA + Is it possible to create a local temp table with dynamic name (name including current time)?

As we do in SQL, is it possible to pass variable on create statement as a table name in HANA?
I'm trying the below code but its throwing an error
CREATE PROCEDURE temp_table()
AS
table_name nvarchar(255);
BEGIN
table_name := '#TMP_TABLE'+ CURRENT_TIMESTAMP;
CREATE LOCAL TEMPORARY COLUMN TABLE :table_name(
SCENARIO varchar(64) NULL,
SCENARIO_CY varchar(64) NULL,
MONTH_NO numeric(4, 0) NULL
);
DROP TABLE :table_name;
END;
Please help me to find out a solution.
What you are looking for is Dynamic SQL. You will need to pass your SQL statement as a string to function EXEC or EXECUTE IMMEDIATE. The timestamp dependent name can be assembled by string concatenation.
In your code above your are using '+' to do string concatenation:
table_name := '#TMP_TABLE'+ CURRENT_TIMESTAMP;
IMHO this does not work on HANA. You should use a double pipe '||' instead:
table_name := '#TMP_TABLE' || CURRENT_TIMESTAMP;

Redshift SQL Procedure to create tables

I need to create a number of similar tables using Redshift SQL (PostgreSQL). I imagine calling a procedure like
call create_dc_table('table1');
call create_dc_table('table2');
and so on.
I think I've misunderstood how procedures work in this environment. I did this:
create or replace procedure create_dc_table(p_tblname varchar)
language plpgsql as $$
begin
create table p_tblname
(
indent varchar(50),
name varchar(32),
datatype varchar(30),
datalen varchar(30)
);
end;
$$;
I'm expecting p_tblname to hold the value "table1" that I passed in so that it reads create table table1, but can I do that? If so, how?
Many thanks.
create or replace procedure create_dc_table(p_tblname varchar)
as $$
begin
execute
'drop table if exists '||p_tblname;
execute
'create table '||p_tblname
||'('
||'indent varchar(50), '
||'name varchar(32), '
||'datatype varchar(30), '
||'datalen varchar(30) '
||')';
end;
$$ language plpgsql;
call create_dc_table('test');

How to select all rows from the oracle PL/SQL collection into SYS_REFCURSOR

Note: I have seen many solution and all says I can not use SQL with a PL/SQL type. I must have to use CREATE or REPLACE, but my restriction is I can not use system object for this task.
What I have tried the below example returns only last row.
create or replace PROCEDURE SP_TEST (TEST_cursor OUT SYS_REFCURSOR)IS
TYPE TEMP_RECORD IS RECORD(
entries NUMBER,
name VARCHAR2(50),
update VARCHAR2(200)
);
TYPE TEMP_TABLE IS TABLE OF TEMP_RECORD INDEX BY PLS_INTEGER;
VAR_TEMP TEMP_TABLE;
IDX PLS_INTEGER := 0;
BEGIN
VAR_TEMP(IDX).cur_entries := 1;
VAR_TEMP(IDX).cur_entries := 2;
OPEN TEST_cursor FOR
SELECT VAR_TEMP(idx).cur_entries from dual;
END SP_TEST;
Another way tried.
OPEN TEST_cursor FOR
SELECT * FROM TABLE(VAR_TEMP)
--- It gives compilation error ora-
Given that you can't create an object in the database, the only solution I can think of is to use dynamic SQL:
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
strSql VARCHAR2(32767);
BEGIN
-- Populate the temp table, or pass it in from elsewhere
var_temp.EXTEND();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
FOR i IN 1..var_temp.COUNT LOOP
strSql := strSql ||
CASE
WHEN LENGTH(strSql) > 0 THEN ' UNION ALL '
ELSE NULL
END ||
'SELECT ' || var_temp.ENTRIES || ' ENTRIES,' ||
'''' || var_temp.ENTRY_NAME || ''' ENTRY_NAME FROM DUAL';
END LOOP;
OPEN test_cursor FOR strSql;
END sp_test;
Now, I may have messed up the string concatenation logic there a bit, but the objective is to end up with an SQL string which looks something like
SELECT 1 ENTRIES,'test' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 2 ENTRIES,'test 2' ENTRY_NAME FROM DUAL
UNION ALL
SELECT 3 ENTRIES,'test_3' ENTRY_NAME FROM DUAL
but, of course, without the nice white space and etc.
The 32K limit on dynamic SQL may bite you eventually, but if push comes to shove you can the DBMS_SQL package to handle arbitrarily large SQL text, although that presents its own challenges.
Best of luck.
In order to reference types in SQL (as opposed to PL/SQL), they must be created as objects in the database. This is effectively a scope issue: when you run SQL you are shifting to a different context. Any structures that you have created locally are not available there.
CREATE TYPE temp_record AS OBJECT
(
entries NUMBER,
entry_name VARCHAR2 (50),
update_value VARCHAR2 (200)
);
CREATE TYPE temp_table IS TABLE OF temp_record;
CREATE OR REPLACE PROCEDURE sp_test (test_cursor OUT SYS_REFCURSOR) IS
var_temp temp_table := temp_table ();
BEGIN
var_temp.EXTEND ();
var_temp (var_temp.LAST).entries := 1;
var_temp (var_temp.LAST).entry_name := 'test';
OPEN test_cursor FOR SELECT * FROM TABLE (var_temp);
END sp_test;

Safe way to open cursor with dynamic column name from user input

I am trying write function which open cursor with dynamic column name in it.
And I am concerned about obvious SQL injection possibility here.
I was happy to see in the fine manual that this can be easily done, but when I try it in my example, it goes wrong with
error: column does not exist.
My current attempt can be condensed into this SQL Fiddle. Below, I present formatted code for this fiddle.
The goal of tst() function is to be able to count distinct occurances of values in any given column of constant query.
I am asking for hint what am I doing wrong, or maybe some alternative way to achieve the same goal in a safe way.
CREATE TABLE t1 (
f1 character varying not null,
f2 character varying not null
);
CREATE TABLE t2 (
f1 character varying not null,
f2 character varying not null
);
INSERT INTO t1 (f1,f2) VALUES ('a1','b1'), ('a2','b2');
INSERT INTO t2 (f1,f2) VALUES ('a1','c1'), ('a2','c2');
CREATE OR REPLACE FUNCTION tst(p_field character varying)
RETURNS INTEGER AS
$BODY$
DECLARE
v_r record;
v_cur refcursor;
v_sql character varying := 'SELECT count(DISTINCT(%I)) as qty
FROM t1 LEFT JOIN t2 ON (t1.f1=t2.f1)';
BEGIN
OPEN v_cur FOR EXECUTE format(v_sql,lower(p_field));
FETCH v_cur INTO v_r;
CLOSE v_cur;
return v_r.qty;
END;
$BODY$
LANGUAGE plpgsql;
Test execution:
SELECT tst('t1.f1')
Provides error message:
ERROR: column "t1.f1" does not exist
Hint: PL/pgSQL function tst(character varying) line 1 at OPEN
This would work:
SELECT tst('f1');
The problem you are facing: format() interprets parameters concatenated with %I as one identifier. You are trying to pass a table-qualified column name that consists of two identifiers, which is interpreted as "t1.f1" (one name, double-quoted to preserve the otherwise illegal dot in the name.
If you want to pass table and column name, use two parameters:
CREATE OR REPLACE FUNCTION tst2(_col text, _tbl text = NULL)
RETURNS int AS
$func$
DECLARE
v_r record;
v_cur refcursor;
v_sql text := 'SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)';
BEGIN
OPEN v_cur FOR EXECUTE
format(v_sql, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END);
FETCH v_cur INTO v_r;
CLOSE v_cur;
RETURN v_r.qty;
END
$func$ LANGUAGE plpgsql;
Aside: It's DISTINCT f1- no parentheses around the column name, unless you want to make it a row type.
Actually, you don't need a cursor for this at all. Faster, simpler:
CREATE OR REPLACE FUNCTION tst3(_col text, _tbl text = NULL, OUT ct bigint) AS
$func$
BEGIN
EXECUTE format('SELECT count(DISTINCT %s) AS qty
FROM t1 LEFT JOIN t2 USING (f1)'
, CASE WHEN _tbl <> '' -- rule out NULL and ''
THEN quote_ident(lower(_tbl)) || '.' ||
quote_ident(lower(_col))
ELSE quote_ident(lower(_col)) END)
INTO ct;
RETURN;
END
$func$ LANGUAGE plpgsql;
I provided NULL as parameter default for convenience. This way you can call the function with just a column name or with column and table name. But not without column name.
Call:
SELECT tst3('f1', 't1');
SELECT tst3('f1');
SELECT tst3(_col := 'f1');
Same as for test2().
SQL Fiddle.
Related answer:
Table name as a PostgreSQL function parameter