SELECT INTO STATEMENT INSIDE A SQL PROCEDURE THROWING AN ERROR - sql

Hi I want to store a avalue of select statement into a variable and then update my table using that variable within the procedure but there is an error . I still dont know its only returning one column then also below error exists.
exact fetch returns more than requested number of rows.
Here is the example of demo code.Can anyone please give me an alternative of what else I can do here to make it work since I have many such plsql statement to populate table columns
create or replace procedure pcountry (country IN Varchar) is
var_date Date;
begin
select date into var_date from countrytable where country=country;
update newtable
set date=var_date
where country=country
commit;
end pcountry;

You need to change the name of your procedure'input argument since you have a column with the same name in your table. Oracle is interpretting your where clause
where country = country as where 1 = 1 which is always true. So it returns more rows instead of one row.
create or replace procedure pcountry (country_in IN Varchar) is
var_date Date;
begin
select date into var_date from countrytable where country = country_in ;
update newtable
set date=var_date
where country= country_in
commit;
end pcountry;

Skip select, switch to merge.
CREATE OR REPLACE PROCEDURE pcountry (par_country IN varchr2)
IS
BEGIN
MERGE INTO newtable n
USING countrytable c
ON (c.country = n.country)
WHEN MATCHED
THEN
UPDATE SET n.date_column = c.date_column
WHERE n.country = par_country;
END;
/

Don't name your PL/SQL variables/arguments with the same name as columns. The SQL engine will look for the country value in the local SQL scope in preference to the outer PL/SQL scope so country=country is (assuming non-NULL values) the same as 1=1 and you will match all rows.
Assuming each country is unique then:
create or replace procedure pcountry (
v_country IN COUNTRY_TABLE.COUNTRY%TYPE
) is
var_date COUNTRY_TABLE."DATE"%TYPE;
begin
select "DATE"
into var_date
from countrytable
where country=v_country;
update newtable
set "DATE"=var_date
where country=v_country
end pcountry;
/
Also, DATE is a reserved word and you cannot use is as an unquoted identifier.
You can combine it into a single SQL statement using MERGE:
CREATE PROCEDURE pcountry (
v_country IN COUNTRY_TABLE.COUNTRY%TYPE
) is
var_date COUNTRY_TABLE."DATE"%TYPE;
BEGIN
MERGE INTO newtable n
USING (SELECT *
FROM countrytable
WHERE country = v_country) c
ON (c.country = n.country)
WHEN MATCHED THEN
UPDATE
SET "DATE" = c."DATE";
END pcountry;
/

The issue, as has been explained in other answers, is that it makes no sense to expect country in a condition like country = country to mean different things on the two sides of the equal sign. The name country has more than one meaning - then a set of rules is applied to figure out which meaning is to be accepted each time the name is used. That is usually the narrowest context ("scope") in which the name exists; in this case, the name exists in the table referenced in the SQL statement, so that's what country means there.
One solution is simple - use a different name for the parameter used in the procedure. This has also been shown in the other answers.
There is another solution though. It might be preferred if your procedure was already very long, it used a parameter name like country, and now you would need to add some code where you need to use this name in a SQL statement. It would be pretty time-consuming to change the parameter name everywhere. Happily, PL/SQL understands qualified names. country (where you used it in the where clause) is the column name for the table referenced in the query. But if you write pcountry.country on the right-hand side, qualifying the variable name with the name of the procedure, no confusion would arise anymore.
... where country = pcountry.country
will achieve the same result as the other proposed answers in this thread. The right-hand side is the parameter or variable coming from the procedure, not the column name from the table.
Note that you could also qualify the left-hand side:
... where countrytable.country = pcountry.country
and perhaps this would be clearer to future readers.
However, this would not help:
... where countrytable.country = country
can you see why?

Related

Store Procedure compiling but value are not getting updated or inserted

I have created below stored procedure in Db2 Server Platform - LUW and version is v10.1.0.6. The stored procedure is getting created absolutely fine.
CREATE OR REPLACE PROCEDURE TAS_TEST
(
IN I_LOGIN_ID VARCHAR(20),
IN I_NAME VARCHAR(100),
IN I_C_NAME VARCHAR(100),
IN I_USER VARCHAR(20),
IN I_DEBUG SMALLINT DEFAULT 0
)
MODIFIES SQL DATA
LANGUAGE SQL
BEGIN
DECLARE V_TOKEN_EXPIRY_DATE TIMESTAMP;
DECLARE V_TOKEN VARCHAR(36);
SET I_LOGIN_ID = TRIM(I_LOGIN_ID);
IF I_LOGIN_ID IS NULL THEN
SIGNAL SQLSTATE VALUE '20000'
SET MESSAGE_TEXT = 'LOGIN ID IS MUST:';
END IF;
IF I_NAME IS NULL THEN
SIGNAL SQLSTATE VALUE '20000'
SET MESSAGE_TEXT = 'NAME IS MUST:';
END IF;
IF I_C_NAME IS NULL THEN
SIGNAL SQLSTATE VALUE '20000'
SET MESSAGE_TEXT = 'C_NAME IS MUST:';
END IF;
IF I_USER IS NULL THEN
SIGNAL SQLSTATE VALUE '20000'
SET MESSAGE_TEXT = 'USER IS MUST:';
END IF;
MERGE INTO TOKEN_DETAILS AS TD
USING(
SELECT
I_LOGIN_ID,
I_TOKEN,
I_NAME,
I_C_NAME,
I_TOKEN_EXPIRY_DATE,
FROM TOKEN_DETAILS
) AS TEMP
ON(TD.I_LOGIN_ID = TEMP.I_LOGIN_ID)
WHEN MATCHED AND TEMP.I_TOKEN IS NOT NULL THEN
UPDATE
SET
I_TOKEN = V_TOKEN,
I_TOKEN_EXPIRY_DATE = V_TOKEN_EXPIRY_DATE
WHEN NOT MATCHED THEN
INSERT INTO TOKEN_DETAILS(
I_LOGIN_ID,
I_TOKEN,
I_NAME,
I_C_NAME,
I_TOKEN_EXPIRY_DATE,
I_CREATED_DATE,
I_CREATED_BY
)VALUES(
I_LOGIN_ID,
V_TOKEN,
I_NAME,
I_C_NAME,
V_TOKEN_EXPIRY_DATE,
CURRENT_TIMESTAMP,
I_USER
);
SELECT I_TOKEN "TOKEN" INTO V_TOKEN FROM TOKEN_DETAILS;
SELECT I_TOKEN_EXPIRY_DATE "TOKEN_EXPIRY_DATE" INTO V_TOKEN_EXPIRY_DATE FROM TOKEN_DETAILS;
END;
GRANT EXECUTE TAS_TEST TO ADMIN;
However, while calling/executing the stored procedure, it's not either updating or inserting the value in the table
CALL TAS_TEST('testuser','service','component','testuser',0);
Can someone please let me know where I might be wrong.
Multiple problems...
You use parameters with the same names as table column names. If you don't qualify them, then you must know the rules used by Db2 to determine, if it's a column or variable or parameter.
References to SQL parameters, SQL variables, and global variables:
Names that are the same should be explicitly qualified. Qualifying a
name clearly indicates whether the name refers to a column, SQL
variable, SQL parameter, row variable field, or global variable. If
the name is not qualified, or qualified but still ambiguous, the
following rules describe whether the name refers to a column, an SQL
variable, an SQL parameter, or a global variable:
If the tables and views specified in an SQL routine body exist at the time the routine is created, the name is first checked as a column name. If not found as a column, it is then checked as an SQL variable in the compound statement, then checked as an SQL parameter, and then, finally, checked as a global variable.
Back to your example, especially the following code:
MERGE INTO TOKEN_DETAILS AS TD
USING(
SELECT
I_LOGIN_ID,
I_TOKEN,
I_NAME,
I_C_NAME,
I_TOKEN_EXPIRY_DATE,
FROM TOKEN_DETAILS
) AS TEMP
ON (TD.I_LOGIN_ID = TEMP.I_LOGIN_ID)
WHEN MATCHED AND TEMP.I_TOKEN IS NOT NULL THEN
UPDATE ...
You merge into the same table and compare every row by the same I_LOGIN_ID table column. Every row of TOKEN_DETAILS has match ON (TD.I_LOGIN_ID = TEMP.I_LOGIN_ID) (unless the table is empty). The UPDATE works only for rows where I_TOKEN column IS NOT NULL setting the value to NULL since V_TOKEN variable is not initialized before MERGE.
Your SELECT INTO statements at the end will fail, if TOKEN_DETAILS contains more than 1 row.

Is it possible to pass variable tables through procedures in SQL DEV?

set serveroutput on;
CREATE OR REPLACE PROCEDURE test_migrate
(
--v_into_table dba_tables.schema#dbprd%TYPE,
--v_from_table dba_tables.table#dbprd%TYPE,
v_gid IN NUMBER
)
IS
BEGIN
select * INTO fx.T_RX_TXN_PLAN
FROM fx.T_RX_TXN_PLAN#dbprd
WHERE gid = v_gid;
--and schema = v_into_table
--and table = v_from_table;
COMMIT;
END;
I thought that SELECT * INTO would create a table in the new database from #dbprd. However, the primary issue is just being able to set these as variables and the goal is to EXEC(INTO_Table,FROM_Table,V_GID) to run the above code.
Error(9,19): PLS-00201: identifier 'fx.T_RX_TXN_PLAN' must be
declared  Error(10,5): PL/SQL: ORA-00904: : invalid identifier
If your goal is to copy data from table in "another" database into a table that resides in "this" database (regarding database link you used), then it it INSERT INTO, not SELECT INTO.
For example:
CREATE OR REPLACE PROCEDURE test_migrate (v_gid in number)
IS
BEGIN
insert into fx.t_rx_txn_plan (col1, col2, ..., coln)
select col1, col2, ..., coln
from fx.t_rx_txn_plan#dbprod
where gid = v_gid;
END;
Last sentence you wrote looks like you'd want to make it dynamic, i.e. pass table names and v_gid (whatever that might be; looks like all tables that should be involved into this process have it). That isn't a simple task.
If you plan to use insert into select * from, that's OK but not for production system. What if someone alters a table and adds (or drops) a column or two? Your procedure will automatically fail. Correct way to do it is to enumerate all columns involved, but that requires fetching data from user_tab_columns (or all_ or dba_ version of the same), which complicates it even more.
Therefore, if you want to move data from here to there, why don't you do it using Data Pump Export & Import? Those utilities are designed for such a purpose, and will do the job better than your procedure. At least, I think so.
This way you should be returning a row. If so, add an OUT type parameter to the procedure with
CREATE OR REPLACE PROCEDURE test_migrate(
--v_into_table dba_tables.schema#dbprd%TYPE,
--v_from_table dba_tables.table#dbprd%TYPE,
i_gid IN NUMBER,
o_RX_TXN_PLAN OUT fx.T_RX_TXN_PLAN#dbprd%rowtype
) IS
BEGIN
SELECT *
INTO RT_RX_TXN_PLAN
FROM fx.T_RX_TXN_PLAN#dbprd
WHERE id = v_gid;
--and schema = v_into_table
--and table = v_from_table;
END;
and call the procedure such as
declare
v_rx_txn_plan fx.T_RX_TXN_PLAN#dbprd%rowtype;
v_gid number:=5345;
begin
test_migrate(v_gid => v_gid, rt_rx_txn_plan => v_rx_txn_plan);
dbms_output.put_line(v_rx_txn_plan.col1);
dbms_output.put_line(v_rx_txn_plan.col2);
end;
to print out the returning values for some columns of the table. to be able to create a new table from this, not SELECT * INTO ... syntax, but
CREATE TABLE T_RX_TXN_PLAN AS
SELECT *
INTO RT_RX_TXN_PLAN
FROM fx.T_RX_TXN_PLAN#dbprd
WHERE ...
is used.
But neither of the cases to issue a COMMIT since there's no DML exists within them.
To create a table you must use the CREATE TABLE statement, and to use any DDL statement in PL/SQL you have to use EXECUTE IMMEDIATE:
CREATE OR REPLACE PROCEDURE test_migrate
(
v_gid IN NUMBER
)
IS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE FX.T_RX_TXN_PLAN AS
SELECT *
FROM fx.T_RX_TXN_PLAN#dbprd
WHERE gid = :GID'
USING IN v_gid;
END;

How to find any lower case data/field in all columns of a table (tsql)

I have a table with about 10 columns and contains about 5000 rows of data. I want to figure out if any field of any column is populated in lower case, then I need to fix it. Because, I need all columns to be in upper case for the ETL process.
I guess if there is a query that turns all alphanumeric columns or any column in upper case should work. But I'm curious to know as well how to find the lower case fields in the whole table for all columns so I know what needs to be changed.
Take this as an example table
Table: Student;
Columns: f_name, l_name, id, address, city, state, zipcode
OP mentioned he wanted to see the changes before changing them. This creates a temporary table which displays the changes and then rolls back the UPDATE query.
BEGIN TRAN
DECLARE #Changes TABLE (
OLD_f_name VARCHAR(MAX)
,NEW_f_name VARCHAR(MAX)
)
UPDATE Student
SET f_name = UPPER(f_name)
OUTPUT deleted.f_name
,inserted.f_name
INTO #Changes(OLD_f_name, NEW_f_name);
SELECT * FROM #Changes
ROLLBACK TRAN
To actually do the update, use COMMIT TRAN instead of ROLLBACK TRAN.
You can do this query for every column you want, I suggest to do them one by one to avoid any unexpected mistake :
UPDATE table_name SET column_name = UPPER(column_name)
Using UPPER function, you can convert a character expression with lowercase character data converted to uppercase. In this case, below SQL should work.
UPDATE Table SET ColumnName = UPPER (ColumnName)
To check, use below SQL:
SELECT * FROM Table WHERE UPPER(ColumnName) != ColumnName
COLLATE Latin1_General_CS_AS

Holding a select row into a variable and accessing its fields with informix

Is there a way in Informix to create a ROW type named or unnamed that you can put a row resulted from a select into? I'm working in a stored procedure.
What I want is something like this:
DEFINE ROW rowVar;
SELECT * INTO rowVar FROM myTableName;
Haven't been able to find the correct syntax so far. I want the row object to behave sort-of like it would be SAMEAS with the table columns.
I do this regularly with Informix.
define o_Row row(cs_nr int not null, addr_nr int, last_name varchar(255));
foreach
select cs into o_Row from cs where cs_nr = 1234
end foreach;
if you are returning more than one row or are not in a foreach you can use a multiset.
define o_Row multiset(row(cs_nr int not null, addr_nr int, last_name varchar(255))not null);
It is not possible to use an "undefined" ROWtype in Informix Stored Procedure Language (SPL).
If you try the following (using Informix 12.10.FC8DE):
CREATE PROCEDURE sp_dummy();
DEFINE generic_row ROW;
END PROCEDURE;
It returns the following error:
-999 Not implemented yet.
The Informix manual does not seem to be correct:
The following statements show examples of generic ROW variables and
named ROW variables:
DEFINE d ROW; -- generic ROW variable
If you define the fields of the ROW then you can use it inside the SPL.

Stored Procedure to obtain part of the records name from a table?

Imagine that I have a table named "TableA" and this table has the columns "IDtable", "Tabname" and "Suffix".
IDtable and Tabname are correctly filled, but the suffix has all the records null.
I have to develop a stored procedure which allows to fill the Suffix column. For that, I know that I have to filter the name of the records in tabname.
Example:
Tabname: a_type_price
Suffix: price
Tabname: a_d_ser_sales
Suffix: sales
I think that I have to develop a for cycle, which looks for the " _ " in the names of Tabname and filters everything after the last " _ ".
Anyone have any idea of the best way to perform this stored procedure?
You'll want to update the table - you don't even really need a stored procedure unless this is something you ahve to run as a job or frequently.
You could do so like this:
CREATE PROCEDURE usp_Upd_TableASuffix()
AS
BEGIN
SET NOCOUNT ON
UPDATE TableA SET Suffix = RIGHT(Tabname, CHARINDEX('_', REVERSE(Tabname)) - 1)
END
Quick explanation of that: You want the RIGHT part of the string after the last _ character - to get the last one, use CHARINDEX on the REVERSE of the string.
You can't do this with a default constraint since you'd have to reference another column, but you could use a trigger, assuming IDtable is your primary key:
CREATE TRIGGER trg_TableA_Suffix
ON TableA
AFTER INSERT, UPDATE
AS
UPDATE TableA SET Suffix = RIGHT(i.Tabname, CHARINDEX('_',REVERSE(i.Tabname))-1)
FROM inserted i
WHERE TableA.IDTable = i.IDTable
GO
Note that this won't work if IDTable is non-unique, or if your table lacks a primary key entirely.
One last option - and this is probably the best if you really want Suffix to only ever contain the last part of Tabname and never want to change it - you could make Suffix a computed column.
ALTER TABLE TableA DROP COLUMN Suffix
ALTER TABLE TableA ADD Suffix AS RIGHT(Tabname, CHARINDEX('_',REVERSE(Tabname))-1) PERSISTED
For informix.
As #dan-field said, and explain, you can do the update using:
UPDATE tablea
SET suffix = RIGHT(tabname, CHARINDEX('_', REVERSE(tabname)) - 1);
Another way is:
UPDATE tablea
SET suffix = SUBSTR(tabname, INSTR(tabname, '_', -1)+1);
INSTR will give you the index of the first occurrence from the right (-1) of ''. SUBSTR will give you the substring starting from the index you pass, in this case we add 1 because we don't want the '' to be outputted.
Bear in mind that RIGHT, CHARINDEX, REVERSE and INSTR functions are only available from 11.70 onward.
On 11.50 you can try this ugly solution:
CREATE PROCEDURE test()
DEFINE i_IDtable LIKE tablea.IDtable;
DEFINE c_suffix LIKE tablea.tabname;
DEFINE i INT;
FOREACH cur1 WITH HOLD FOR
SELECT IDtable ,Tabname
INTO i_IDtable, c_suffix
FROM tablea
LET i = LENGTH(c_suffix);
WHILE i > 0
IF SUBSTR(c_suffix,i) LIKE '\_%' THEN
LET c_suffix = SUBSTR(c_suffix,i+1);
EXIT WHILE;
ELSE
LET i = i -1;
END IF
END WHILE;
UPDATE tablea
SET suffix = c_suffix,
WHERE idtable = i_IDtable;
END FOREACH;
END PROCEDURE;
But if suffix is always this part of the tabname it's not a good practice to store on the table.
You can easily get it from the already stored tabname. And even programmatically is easy to deal with it.