I have a SQL query where I want to specify the names of the columns dynamically.
Let's say I have a table called TABLE_A, and it has a column by name ID. I was wondering if I could do something like:
SELECT (SELECT 'ID'
FROM DUAL)
FROM TABLE_A
Obviously this is not possible. Is there a better way to this?
SQL does not support dynamic column or table names -- you need to use dynamic SQL to get the functionality you want. Dynamic SQL means constructing a string, concatenating as necessary, before submitting the string (containing the query) to the database for interpretation.
Dynamic SQL is supported by most databases, but the syntax is often very different. Oracle provides:
EXECUTE IMMEDIATE
using an implicit cursor
This link provides examples of both.
Lest we forget Little Bobby Tables, dynamic SQL is an increased risk of SQL injection attacks...
You could use dynamic SQL if you are in a PL/SQL environment.
Build your SQL string as a VARCHAR2 before executing it.
DECLARE
v_sql VARCHAR2(4001);
v_column VARCHAR2(30) := 'whatever';
v_sql_result VARCHAR2(4001);
BEGIN
v_sql := 'SELECT '||v_column||' FROM table_a';
EXECUTE IMMEDIATE v_sql
INTO v_sql_result;
EXCEPTION
WHEN ...
THEN
...
END;
This will select the contents of column "whatever" into the v_sql_result.
Of course I ommitted the WHERE clause to ensure only one row was returned for this example but you can add that yourself or lookup how EXECUTE IMMEDIATE works in Oracle.
If you want a dynamic list of columns, you might be better off using dynamic sql. I try to avoid it whenever I can, but this is a prime example of when to use it.
example:
DECLARE #sqlQuery varchar(300)
SET #sqlQuery = 'select '
------ logic loop happens -----
SET #sqlQuery = #sqlQuery + 'columnname, '
------ end loop ------
SET #sqlQuery = #sqlQuery + ' from TABLE_A where '
exec(#sqlQuery)
It's at least a place to start for you.
Related
I'am an SQL rookie and just started my Apprenticeship in the CMD Team of my Company.
It's my first time working with Oracle SQL-dev and I never coded Functions/Procedures in SQL before
So my Sensei gave me a task to solve for myself which is:
To create a stored procedure which generates views for all of my Synonyms in the current Scheme. If executed again it should Replace the current views of those Synonyms.
As second part of the task I should also add a function to DROP all the views i don't have a Synonym for
Yes I already discussed with her if it is useful to create views this way or not
The first thing i found out that it is not possible to create views from a stored procedure the traditional way and that i have to use a workaround method with EXEC() for example
my Sensei gave me a code Snippet to begin with:
FOR KO IN(SELECT * FROM all_synonyms WHERE OWNER = 'CMD_SANDBOX')
She told me i have to fill Variables with the Names of the Synonyms .. so far so good
it makes sense because i have to generate unique names for the views as well as the procedure to know which is the current synonym to create a view of
CREATE OR REPLACE PROCEDURE CREATE_VIEWS AS
DECLARE #viewCommand varchar(1000)
DECLARE #viewName varchar(75)
DECLARE #synonymName varchar(75)
SET #viewCommand = 'CREATE OR REPLACE VIEW' + #viewName + 'AS SELECT * FROM '
+#synonymName
BEGIN
FOR KO IN(SELECT * FROM all_synonyms WHERE OWNER = 'CMD_SANDBOX')
SET #synonymName = <Pointer on Synonym>
SET #viewName = 'v_' + #synonymName
EXEC(#viewCommand)
END LOOP KO;
END CREATE_VIEWS
Long story short...
My questions are:
How do I Point to a Certain Object without using its specific name to fill my #synonymName ?
Is the For Loop header already complete ? I kinda don't get how it works in SQL
How do you pros research this stuff? I feel pretty confident in queries but as it comes to specific things like pointing to objects or similar it is pretty hard to find out.
Your syntax is invalid in Oracle as:
there are multiple DECLAREs but only a single BEGIN/END block;
# is not valid in (unquoted) variable names;
+ is the numeric addition operator and does not concatenate strings. To concatenate strings you want to use the || operator;
assignment in PL/SQL does not require SET and requires := instead of =; and
you want EXECUTE IMMEDIATE and not EXEC.
If you fix all that then there are still issues as VIEW' + #viewName + 'AS should have spaces between the VIEW and AS keywords and the identifier.
The syntax looks more like SQL Server than Oracle.
For Oracle, you want:
CREATE OR REPLACE PROCEDURE CREATE_VIEWS
AS
BEGIN
FOR KO IN(
SELECT synonym_name
FROM all_synonyms
WHERE OWNER = 'CMD_SANDBOX'
)
LOOP
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW v_' || ko.synonym_name
|| ' AS SELECT * FROM ' || ko.synonym_name;
END LOOP;
END CREATE_VIEWS;
/
I'm creating an update statement that generate SHA256 for table columns based on table's name
1st Step: I created a procedure that get the table columns, concatenate it all in one columns, then format to a desired format.
-- Procedure code : Extract table's columns list, concatenate it and format it
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
SELECT concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')') AS Names
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* Result of this procedures :
hash_sha256(
to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0')) )
*/
-- Update Statement needed
UPDATE "SCHEMA_NAME"."TABLE_NAME"
SET "SHA" = CALL "SCHEMA_NAME"."SHA_PREP"('SCHEMA_NAME')
WHERE "ID" = 99 -- a random filter
The solution by #SonOfHarpy technically works but has several issues, namely:
unnecessary use of temporary tables
overly complicated string assignment approach
use of fixed system table schema (SYS.TABLE_COLUMNS) instead of PUBLIC synonym
wrong data type and variable name for the input parameter
An improved version of the code looks like this:
create procedure SHA_PREP (in TABLE_NAME nvarchar(256))
as
begin
declare SQL_STR nvarchar(5000);
SELECT
'UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA"= hash_sha256(' || STRING_AGG(A, ', ') || ')'
into SQL_STR
FROM (
SELECT
'TO_VARBINARY(IFNULL("'|| "COLUMN_NAME" ||'",''0''))' as A
FROM TABLE_COLUMNS
WHERE
"SCHEMA_NAME" = 'SCHEMA_NAME'
AND "TABLE_NAME" = :TABLE_NAME
AND "COLUMN_NAME" != 'SHA'
ORDER BY POSITION
);
-- select :sql_str from dummy; -- this is for debugging output only
EXECUTE IMMEDIATE (:SQL_STR);
end;
By changing the CONCAT functions to the shorter || (double-pipe) operator, the code becomes a lot easier to read as the formerly nested function calls are now simple chained concatenations.
By using SELECT ... INTO variable the whole nonsense with the temporary table can be avoided, again, making the code easier to understand and less prone to problems.
The input parameter name now correctly reflects its meaning and mirrors the HANA dictionary data type for TABLE_NAME (NVARCHAR(256)).
The procedure now consists of two commands (SELECT and EXECUTE IMMEDIATE) that each performs an essential task of the procedure:
Building a valid SQL update command string.
Executing the SQL command.
I removed the useless line-comments but left a debugging statement as a comment in the code, so that the SQL string can be reviewed without having to execute the command.
For that to work, obviously, the EXECUTE... line needs to be commented out and the debugging line has to be uncommented.
What's more worrying than the construction of the solution is its purpose.
It looks as if the SHA column should be used as a kind of shorthand row-data fingerprint. The UPDATE approach certainly handles this as an after-thought activity but leaves the "finger-printing" for the time when the update gets executed.
Also, it takes an essential part of the table design (that the SHA column should contain the fingerprint) away from the table definition.
An alternative to this could be a GENERATED COLUMN:
create table test (aaa int, bbb int);
alter table test add (sha varbinary (256) generated always as
hash_sha256(to_varbinary(IFNULL("AAA",'0'))
, to_varbinary(IFNULL("BBB",'0'))
)
);
insert into test (aaa, bbb) values (12, 32);
select * from test;
/*
AAA BBB SHA
12 32 B6602F58690CA41488E97CD28153671356747C951C55541B6C8D8B8493EB7143
*/
With this, the "generator" approach could be used for table definition/modification time, but all the actual data handling would be automatically done by HANA, whenever values get changed in the table.
Also, no separate calls to the procedure will ever be necessary as the fingerprints will always be current.
I find a solution that suits my need, but maybe there's other easier or more suitable approchaes :
I added the update statement to my procedure, and inserted all the generated query into a temporary table column, the excuted it using EXECUTE IMMEDIATE
Create procedure SHA_PREP (in inp1 nvarchar(20))
as
begin
/* ********************************************************** */
DECLARE SQL_STR VARCHAR(5000);
-- Create a temporary table to store a query in
create local temporary table #temp1 (QUERY varchar(5000));
-- Insert the desirable query into the QUERY column (Temp Table)
insert into #temp1(QUERY)
SELECT concat('UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =' ,concat(concat('hash_sha256(',STRING_AGG(A, ', ')),')'))
FROM (
SELECT concat('to_varbinary(IFNULL("',concat(COLUMN_NAME,'",''0''))')) as A
FROM SYS.TABLE_COLUMNS
WHERE SCHEMA_NAME = 'SCHEMA_NAME' AND TABLE_NAME = :inp1
AND COLUMN_NAME not in ('SHA')
ORDER BY POSITION
);
end;
/* QUERY : UPDATE "SCHEMA_NAME"."TABLE_NAME" SET "SHA" =
hash_sha256(to_varbinary("ID"),to_varbinary(IFNULL("COL1",'0')),to_varbinary(IFNULL("COL2",'0'))) */
SELECT QUERY into SQL_STR FROM "SCHEMA_NAME".#temp1;
--Excuting the query
EXECUTE IMMEDIATE (:SQL_STR);
-- Dropping the temporary table
DROP TABLE "SCHEMA_NAME".#temp1;
/* ********************************************************** */
end;
Any other solution or improvement are well welcomed
Thank you
Is it possible to test for a column before selecting it within a select statement?
This may be rough for me to explain, I have actually had to teach myself dynamic SQL over the past 4 months. I am using a dynamically generated parameter (#TableName) to store individual tables within a loop (apologize for the vagueness, but the details aren't relevant).
I then want to be able to be able to conditionally select a column from the table (I will not know if each table has certain columns). I have figured out how to check for a column outside of a select statement...
SET #SQLQuery2 = 'Select #OPFolderIDColumnCheck = Column_Name From INFORMATION_SCHEMA.COLUMNS Where Table_Name = #TABLENAME And Column_Name = ''OP__FolderID'''
SET #ParameterDefinition2 = N'#TABLENAME VARCHAR(100), #OPFolderIDColumnCheck VARCHAR(100) OUTPUT'
EXECUTE SP_EXECUTESQL #SQLQuery2, #ParameterDefinition2, #TABLENAME, #OPFolderIDColumnCheck OUTPUT
IF #OPFolderIDColumnCheck IS NULL
BEGIN
SET #OP__FOLDERID = NULL
END
ELSE
IF #OPFolderIDColumnCheck IS NOT NULL
BEGIN
...etc
but id like to be able to do it inside of a select statement. Is there a way to check and see if OP__FOLDERID exists in the table?
Id like to be able to do something like this:
SELECT IF 'OP__FOLDERID' EXISTS IN [TABLE] THEN 'OP__FOLDERID' FROM [TABLE]
Thank you for any help or direction you can offer.
I'm afraid there isn't any direct way to do this within a SELECT statement at all. You can determine if a column exists in a table, however, and construct your dynamic SQL accordingly. To do this, use something like this:
IF COL_LENGTH('schemaName.tableName', 'columnName') IS NOT NULL
BEGIN
-- Column Exists
END
You could then set a variable as a flag, and the code to construct the dynamic SQL would construct the expression with/without the column, as desired. Another approach would be to use a string value, and set it to the column name if it is present (perhaps with a prefix or suffix comma, as appropriate to the expression). This would allow you to save writing conditionals in the expression building, and would be particularly helpful where you have more than one or two of these maybe-columns in a dynamic expression.
i want to use the result of a query as an input in another query.
What might make it difficult: The variable is the schema in the database.
CREATE or replace VARIABLE myschema varchar(15) ;
set myschema = (select owner from syscat.tables where tabname = 'xyz');
select count(name) as result from myschema.USR02 where USTYP = 'A';
DROP VARIABLE myschema;
This is my last try, after i failed using declare.
But i get an error, because "myschema" is used as a string, and of course there is no schema with name "myschema". The result of the first query is not used.
If I just run the first two lines, i get the schemaname as result. Do i have to mark the variable in a special way? The goal is just the result of the query in line 3 by using the dynamic value of "myschema".
Unfortunately, you have to use dynamic SQL (forming a custom SQL query through string manipulation) if you want to deal with table, schema, or column names dynamically:
This is the basic idea:
execute immediate 'select * from ' || myschema || '.USR02';
However, you can't just run a bare select in dynamic SQL; you have to put the result in something. And the whole thing must be in a compound SQL block. So the full example would look something like this (simplified query for space).
This query assumes that a table called "result" exists to store the result you are returning.
begin
declare myschema varchar(100) default '';
set myschema = (select owner from syscat.tables where tabname = 'xyz');
execute immediate 'insert into result select count(*) from ' || myschema || '.USR02';
end
select * from result;
Note that within the block, you can simply declare a variable (as shown in my example). So you don't have to declare a global variable for this purpose, unless you want it to persist beyond this one statement.
I am dynamically building a search query with bind variables with at least 1 and at most 7 different potential criteria. I know I can do this -
EXECUTE IMMEDIATE sql USING bind_var1, bind_var2 or
EXECUTE IMMEDIATE sql USING bind_var3, bind_var5, bind_var7.
Is it possible to include the bind variables within the sql?
sql = 'SELECT * FROM table WHERE id = :bind_var1 AND name = :bind_var2 USING bind_var1, bind_var2'
and do
EXECUTE IMMEDIATE sql?
I want and need to dynamically build the USING piece instead of writing a lot of IF THEN statements.
According to your tags, I assume this will be used inside some kind of PL/SQL block. So, maybe are you looking for the open for statement.
This allows you to get a cursor on an dynamic query:
sql := 'SELECT * FROM table WHERE id = :bind_var1 AND name = :bind_var2';
open my_cursor for sql using bind_var1, bind_var2';
-- do whatever you need with your cursor
your USING bind_var1, bind_var2 pice of code should be out side os your sql string and come at the end of execute immediate statement and also for select senarios try to use dynamic sql for select with a cursor unless you want to select into a variable