How to use variable as table name? - sql

BEGIN
_table_name := 'mytable';
CREATE TEMPORARY TABLE _table_name (
id integer NOT NULL,
name character varying,
active boolean
) ON COMMIT DROP';
-- logic here
RETURN QUERY SELECT table1.id, table1.name FROM _table_name AS table1;
END;
I have simplified my problem. I'm trying to use a variable as the table name.
I know you can do things like SELECT * FROM table1 WHERE id = _id where _id is a declared varaible.
I know I can do this EXECUTE, but then I have a query like:
INSERT INTO table2 (id) SELECT id FROM unnest(_ids) as id where _ids is an array.
Anyway to solve problem 1 with using a variable as table name? and problem 2, using unnest inside EXECUTE?
So the problem is that the queries take _table_name as a literal table name, it doesn't look like it's using 'mytable' as the table name instead.

If you're dynamically changing the table name (i.e. via a variable) then you will need to use EXECUTE. You can use this with arrays and unnest, as long as you cast the arrays to/from a TEXT representation.
DECLARE
_ids INT[] = ARRAY[ 1, 2, 3, 4, 5 ];
_table_name TEXT = 'mytable';
BEGIN
EXECUTE
'INSERT INTO ' || QUOTE_IDENT( _table_name ) || ' (id)
SELECT id
FROM unnest( ' || QUOTE_LITERAL( _ids::TEXT ) || '::INT[] ) AS id';
END;

You have been told about dynamic SQL with EXECUTE in plpgsql. You build the query string dynamically including invariable code and identifiers.
But do not concatenate values. Use the USING clause instead:
DECLARE
_ids INT[] = ARRAY[ 1, 2, 3, 4, 5 ];
_table_name TEXT = 'mytable';
BEGIN
EXECUTE
'INSERT INTO ' || quote_ident(_table_name) || ' (id)
SELECT * FROM unnest($1)'
USING ids;
END;
Avoids error-prone casting back and forth.
Table name as a PostgreSQL function parameter

Related

How do I reuse a variable as a table name in select query

I need to get an id from a table, whose table name is defined by running another query.
I tried to implement it by defining a variable that saves the results from the first query. Then I created a second query that consumes the table name from the first one.
define tablename = (SELECT table_name FROM tables_names WHERE row_id = 147);
define rowid = SELECT row_id FROM &tablename WHERE title is NULL;
select * from &rowid;
However it doesn't work. I also tried to create a stored procedure, but I don't understand how it should work either:
DECLARE
tablename varchar(128);
rowid int;
BEGIN
SELECT table_name INTO tablename FROM tables_names WHERE row_id = 147);
SELECT row_id INTO rowid FROM &tablename WHERE title is NULL;
END;
The output should give me the expected rowid. I'm not an Oracle expert and this seems like a simple thing, but I don't know how to achieve it.
Use execute immediate as following:
DECLARE
rowid_ int; -- dont use oracle reserved words as variable name.
-- added _ after rowid
BEGIN
EXECUTE IMMEDIATE 'SELECT row_id FROM '
|| (SELECT table_name FROM tables_names WHERE row_id = 147)
|| ' WHERE title is NULL' INTO ROWID_;
-- do something with rowid_ or other logic
END;
/
Cheers!!
This is called dynamic sql.
Edit the procedure and have it do
EXECUTE IMMEDIATE
"SELECT row_id INTO :x FROM " || tablename || " WHERE title IS NULL"
USING rowid;
This is from memory so it might not work, but should give you a good start on how to do it

Oracle function with select all from tables

SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
I need to create function with this select, I tried this but it doesn't work.
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN varchar2 IS
list varchar2(2000);
BEGIN
SELECT DISTINCT L.* FROM LABALES L , MATCHES M
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID
return list;
END;
How will I create function that returns all from table L.
Thanks
You may use implicit result using DBMS_SQL.RETURN_RESULT(Oracle12c and above) in a procedure using a cursor to your query.
CREATE OR REPLACE PROCEDURE getSoccerLists
AS
x SYS_REFCURSOR;
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
DBMS_SQL.RETURN_RESULT(x);
END;
/
then simply call the procedure
EXEC getSoccerLists;
For lower versions(Oracle 11g) , you may use a print command to display the cursor's o/p passing ref cursor as out parameter.
CREATE OR REPLACE PROCEDURE getSoccerLists (x OUT SYS_REFCURSOR)
AS
BEGIN
OPEN x FOR SELECT DISTINCT L.* FROM LABALES L
JOIN MATCHES M ON ( 1=1 ) -- join condition
WHERE M.LIST LIKE '%ENG'
ORDER BY L.ID;
END;
/
Then, in SQL* Plus or running as script in SQL developer and Toad, you may get the results using this.
VARIABLE r REFCURSOR;
EXEC getSoccerLists (:r);
PRINT r;
Another option is to use TABLE function by defining a collection of the record type of the result within a package.
Refer Create an Oracle function that returns a table
I guess this questions is a repetition of the your previously asked question, where you wanted to get all the columns of tables but into separate column. I already answered in stating this you cannot do if you call your function via a SELECT statement. If you call your function in a Anoymous block you can display it in separate columns.
Here Oracle function returning all columns from tables
Alternatively, you can get the results separated by a comma(,) or pipe (|) as below:
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN VARCHAR2
IS
list VARCHAR2(2000);
BEGIN
SELECT col1
||','
||col2
||','
||col2
INTO LIST
FROM SOCCER_PREMATCH_LISTS L ,
SOCCER_PREMATCH_MATCHES M
WHERE M.LIST LIKE '%' || (L.SUB_LIST) || '%'
AND (TO_TIMESTAMP((M.M_DATE || ' ' || M.M_TIME), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL
))
ORDER BY L.ID");
Return list;
End;
Note here if the column size increased 2000 chars then again you will lose the data.
Edit:
From your comments
I want it to return a table set of results.
You then need to create a table of varchar and then return it from the function. See below:
CREATE TYPE var IS TABLE OF VARCHAR2(2000);
/
CREATE OR REPLACE
FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
SELECT NSO ||',' ||NAME BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
Note: Here in the function i have used a table called test and its column. You replace your table with its columnname.
Edit 2:
--Create a object with columns same as your select statement
CREATE TYPE v_var IS OBJECT
(
col1 NUMBER,
col2 VARCHAR2(10)
)
/
--Create a table of your object
CREATE OR REPLACE TYPE var IS TABLE OF v_var;
/
CREATE OR REPLACE FUNCTION getSoccerLists
RETURN var
IS
--Initialization
list VAR :=var();
BEGIN
--You above object should have same columns with same data type as you are selecting here
SELECT v_var( NSO ,NAME) BULK COLLECT INTO LIST FROM TEST;
RETURN list;
END;
Execution:
select * from table(getSoccerLists);
This is not an answer on how to build a function for this, as I'd recommend to make this a view instead:
CREATE OR REPLACE VIEW view_soccer_list AS
SELECT *
FROM soccer_prematch_lists l
WHERE EXISTS
(
SELECT *
FROM soccer_prematch_matches m
WHERE m.list LIKE '%' || (l.sub_list) || '%'
AND TO_TIMESTAMP((m.m_date || ' ' || m.m_time), 'DD.MM.YYYY HH24:MI') >
(SELECT SYSTIMESTAMP AT TIME ZONE 'CET' FROM DUAL)
);
Then call it in a query:
SELECT * FROM view_soccer_list ORDER BY id;
(It makes no sense to put an ORDER BY clause in a view, because you access the view like a table, and table data is considered unordered, so you could not rely on that order. The same is true for a pipelined function youd access with FROM TABLE (getSoccerLists). Always put the ORDER BY clause in your final queries instead.)

nzsql - Converting a subquery into columns for another select

Goal: Use a given subquery's results (a single column with many rows of names) to act as the outer select's selection field.
Currently, my subquery is the following:
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'test_table' AND column_name not in ('colRemove');
What I am doing in this subquery is grabbing all the column names from a table (i.e. test_table) and outputting all except for the column name specified (i.e. colRemove). As stated in the "goal", I want to use this subquery as such:
SELECT (*enter subquery from above here*)
FROM actual_table
WHERE (*enter specific conditions*)
I am working on a Netezza SQL server that is version 7.0.4.4. Ideally, I would like to make the entire query executable in one line, but for now, a working solution would be much appreciated. Thanks!
Note: I do not believe that the SQL extensions has been installed (i.e. arrays), but I will need to double check this.
A year too late, here's the best I can come up with but, as you already noticed, it requires a stored procedure to do the dynamic SQL. The stored proc creates a view with the all the columns from the source table minus the one you want to exclude.
-- Create test data.
CREATE TABLE test (firstcol INTEGER, secondcol INTEGER, thirdcol INTEGER);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (1, 2, 3);
INSERT INTO test (firstcol, secondcol, thirdcol) VALUES (4, 5, 6);
-- Install stored procedure.
CREATE OR REPLACE PROCEDURE CreateLimitedView (varchar(ANY), varchar(ANY)) RETURNS BOOLEAN
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
tableName ALIAS FOR $1;
columnToExclude ALIAS FOR $2;
colRec RECORD;
cols VARCHAR(2000); -- Adjust as needed.
isfirstcol BOOLEAN;
BEGIN
isfirstcol := true;
FOR colRec IN EXECUTE
'SELECT ATTNAME AS NAME FROM _V_RELATION_COLUMN
WHERE
NAME=UPPER('||quote_literal(tableName)||')
AND ATTNAME <> UPPER('||quote_literal(columnToExclude)||')
ORDER BY ATTNUM'
LOOP
IF isfirstcol THEN
cols := colRec.NAME;
ELSE
cols := cols || ', ' || colRec.NAME;
END IF;
isfirstcol := false;
END LOOP;
-- Should really check if 'LimitedView' already exists as a view, table or synonym.
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW LimitedView AS SELECT ' || cols || ' FROM ' || quote_ident(tableName);
RETURN true;
END;
END_PROC
;
-- Run the stored proc to create the view.
CALL CreateLimitedView('test', 'secondcol');
-- Select results from the view.
SELECT * FROM limitedView WHERE firstcol = 4;
FIRSTCOL | THIRDCOL
----------+----------
4 | 6
You could have the stored proc return a resultset directly but then you wouldn't be able to filter results with a WHERE clause.

Is there a way to pass a set of values as a parameter in an Oracle SQL Statement

I would like to pass a set of values as a parameter to an Sql Statement (in vb.net).
In my case:
Users are allowed to upload a set of IDs, to check availability of an item. I would like to execute a statement that will return the items that match any of the IDs by doing something like the following:
SELECT * FROM MyTable WHERE id IN ('123','456','789')
But I cannot pass on the value ('123','456','789') as a parameter as it will be taken as an atomic value - a whole string, i.e., this will not work:
SELECT * FROM MyTable WHERE id IN :param
where :param is ('123','456','789')
I cannot concatenate the strings (as shown above) either to avoid client-side sql injection.
Any ideas?
you could pass the values in as XML and parse them using the XMLDOM.
See: here
DECLARE
vXML VARCHAR2 (10000 CHAR) := '<ids><id>1</id><id>2</id><id>3</id></ids>';
BEGIN
OPEN :refc FOR
SELECT c."id"
FROM XMLTABLE ('/ids/id'
PASSING XMLTYPE (vXML)
COLUMNS "id" VARCHAR2 (32)) c;
END;
From VB.net you can pass an "Associative array" to a SQL call.
In PL/SQL create types and procedures like this:
CREATE OR REPLACE TYPE NUMBER_TABLE_TYPE AS TABLE OF NUMBER;
CREATE OR REPLACE PACKAGE My_Package AS
TYPE NUMBER_ARRAY_TYPE IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
PROCEDURE My_Procedure(arr IN NUMBER_ARRAY_TYPE);
END My_Package;
CREATE OR REPLACE PACKAGE BODY My_Package AS
PROCEDURE My_Procedure(arr IN NUMBER_ARRAY_TYPE) IS
nested_table NUMBER_TABLE_TYPE := NUMBER_TABLE_TYPE();
BEGIN
-- First transform "Associative array" to a "Nested Table"
FOR i IN arr.FIRST..att.LAST LOOP
nested_table.EXTEND;
nested_table(nested_table.LAST) := arr(i);
END LOOP;
SELECT *
INTO ...
FROM MyTable
WHERE ID MEMBER OF nested_table;
END My_Procedure;
END My_Package;
In VB.NET it looks like this:
Sub My_Sub(ByVal idArr As Long())
Dim cmd As OracleCommand
Dim par As OracleParameter
cmd = New OracleCommand("BEGIN My_Package.My_Procedure(:arr); END;"), con)
cmd.CommandType = CommandType.Text
par = cmd.Parameters.Add("arr", OracleDbType.Int64, ParameterDirection.Input)
par.CollectionType = OracleCollectionType.PLSQLAssociativeArray
par.Value = idArr
par.Size = idArr.Length
cmd.ExecuteNonQuery()
End Sub
Check Oracle doc for further information: PL/SQL Associative Array Binding
The solution to the question is to ultimately build an SQL statement which would look like this (sorry for the images but I could not paste the XML correctly):
In vb.net (or others I suppose) therefore, you would then replace the XML itself with a parameter as follows:
PASSING XMLTYPE(:1)
where :1 in this case would be the XML text:
(Do remember to build the XML text using a StringBuilder or any other efficient XML string builder).
Why can't you just pass it as one atomic value and then work with the INSTR-function Oracle offers.
For Example:
WITH MyTable AS (
SELECT 'abc' ID FROM dual UNION ALL
SELECT 'abcc' ID FROM dual UNION ALL
SELECT 'bbc' ID FROM dual UNION ALL
SELECT 'def' ID FROM dual UNION ALL
SELECT 'abcdef' ID FROM dual)
select * from MyTable where instr('(''abc'', ''def'')', '''' || id || '''') > 0;

Delphi: how to pass a list as a parameter to a SQL query?

I have a list of integers or of strings and need to pass it as a parameter for a Delphi DataSet. How to do it?
Here is an example. MyQuery is something like:
select * from myTable where intKey in :listParam
I'd set a parameter as a list or array or something else:
MyQuery.ParamByName('listParam').AsSomething := [1,2,3];
and it would result in this query sent to the sql server:
select * from myTable where intKey in (1, 2, 3)
It would be even better if the solution would also work with strings, making this query:
select * from myTable where stringKey in :listParam
become:
select * from myTable where stringKey in ('a', 'b', 'c')
I believe this is a simple question, but "IN" isn't a good keyword for searching the web.
Please answer how I should configure the parameter in the IDE, the query and how to pass the parameters.
I'm using Delphi 7.
Edited: I'm considering the answer is "it isn't possible to do directly". If someone give me a non-hackish answer, the accepted answer will be changed.
AFAIK, it is not possible directly.
You'll have to convert the list into a SQL list in plain text.
For instance:
function ListToText(const Args: array of string): string; overload;
var i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+QuotedStr(Args[i])+',';
result[length(result)] := ')';
end;
function ListToText(const Args: array of integer): string; overload;
var i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+IntToStr(Args[i])+',';
result[length(result)] := ')';
end;
To be used as such:
SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]);
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']);
SQL accepts only single values as parameters so you cannot create a statement with one parameter that can map to a variable number of values, such as the example you gave.
However, you can still use parameterized SQL in this situation. The solution is to iterate over the list of values you have, adding a parameter marker to the SQL and a parameter to the parameter list for each value.
This is easiest to do with positional rather than named parameters but can be adapted for named parameters as well (you may need to adjust this code since I don't have Delphi available and don't remember the Parameter creation syntax):
//AValues is an array of variant values
//SQLCommand is some TDataSet component with Parameters.
for I := Low(AValues) to High(AValues) do
begin
if ParamString = '' then
ParamString = '?'
else
ParamString = ParamString + ', ?';
SQLCommand.Parameters.Add(AValues[I]);
end
SQLCommand.CommandText =
'SELECT * FROM MyTable WHERE KeyValue IN (' + ParamString + ')';
This will produce an injection-safe parameterized query.
There are several options for you but basically you need to get your values into a table. I would suggest a table variable for that.
Here is a version that unpacks an int list.
declare #IDs varchar(max)
set #IDs = :listParam
set #IDs = #IDs+','
declare #T table(ID int primary key)
while len(#IDs) > 1
begin
insert into #T(ID) values (left(#IDs, charindex(',', #IDs)-1))
set #IDs = stuff(#IDs, 1, charindex(',', #IDs), '')
end
select *
from myTable
where intKey in (select ID from #T)
It is possible to have multi-statement queries. The parameter :listParam should be a string:
MyQuery.ParamByName('listParam').AsString := '1,2,3';
You can use the same technique for strings. You just need to change the data type of ID to for instance varchar(10).
Instead of unpacking with a while loop you could make use of a split function
declare #T table(ID varchar(10))
insert into #T
select s
from dbo.Split(',', :listParam)
select *
from myTable
where charKey in (select ID from #T)
A string param could look like this:
MyQuery.ParamByName('listParam').AsString := 'Adam,Bertil,Caesar';
Create a temporary table and insert your values in it. Then use that table as part of a subquery.
For example, create MyListTable in your database. Insert your values into MyListTable. Then do
select * from myTable where keyvalue in (select keyvalue from MyListTable)
This avoids SQL injection attacks. But it's not elegant, is not performance friendly because you have to insert records before running your query, and can lead to concurrency issues.
Not my first choice to deal with your situation but it addresses your concern about sql injection.
If someone still having the same problem, if you are using firedac you can use macros like this:
Query -> "select * from myTable where intKey in (&listParam)"
Setting the macro -> MyQuery.MacroByName('listParam').AsRaw := '1, 2, 3';
I use some "IN" replacement. Here is the query I use:
SELECT * FROM MyTable WHERE CHARINDEX(','+cast(intKey as varchar(10))+',', :listParam) > 0
the code to send parameter:
MyQuery.ParamByName('listParam').AsString := ',1,2,3,';
The array item value can partially match some other values. For instance, "1" can be part of "100". To protect against it, I use comma as delimiter
Why not make a dynamic sql:
Quick and dirty, but still using parameters.
check 10 elements. I don't know how well this scales.
MyQuerySQL.Text:='SELECT * FROM myTable WHERE intKey in (:listParam0'
for i := 1 to 9 do begin
MyQuerySQL.Text := MyQuerySQL.Text + ',:listParam'+IntToStr(i)
end;
MyQuerySQL.Text := MyQuerySQL.Text+')';
for i:=0 to 9 do begin
MyQuery.ParamByName('listParam'+IntToStr(i)).AsInteger := ArrayofInt[0];
end;