create new columns and unselect existing columns without altering table schema - database

new to PL/SQL and wondering if I'd be able to create new columns, drop existing columns WITHOUT altering the table schema/source table, so that the source table remains untouched while the procedure returns a new result set that has new columns. for example,
id name type
1 foo apple
2 bar orange
Is it possible to write a stored procedure that unselects the column 'type' and creates new column 'value' that satisfies following condition:
if name = 'foo' then value = 5
elsif name = 'bar' then value = 7
so my result set would look like this:
id name value
1 foo 5
2 bar 7
Can this be done by purely using the pl/sql engine?
Secondly, would it be possible to create a variable that stores the output of a SQL that generates a alter table statement, and execute that variable so the alter table statement gets executed?

Looks like you actually want a view, not any kind of PL/SQL code.
Table:
SQL> create table test as
2 select 1 id, 'foo' name, 'apple' type from dual union all
3 select 2, 'bar', 'orange' from dual;
Table created.
View:
SQL> create or replace view v_test as
2 select id,
3 name,
4 case when name = 'foo' then 5
5 when name = 'bar' then 7
6 end as value
7 from test;
View created.
Result:
SQL> select * from v_test;
ID NAM VALUE
---------- --- ----------
1 foo 5
2 bar 7
SQL>
If you really want PL/SQL, then using a function which returns ref cursor might be one option; you'd do that "logic" stuff in its select statement (just like in the view):
SQL> create or replace function f_test
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for
7 select id,
8 name,
9 case when name = 'foo' then 5
10 when name = 'bar' then 7
11 end as value
12 from test;
13
14 return rc;
15 end;
16 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ID NAM VALUE
---------- --- ----------
1 foo 5
2 bar 7
SQL>
As of your question:
... create new columns, drop existing columns WITHOUT altering the table schema/source table
Nope. That's done with ALTER TABLE.
Finally, as of your second question: yes, you can use a variable and execute it using dynamic SQL, i.e. execute immediate:
SQL> select * from test;
ID NAM TYPE
---------- --- ------
1 foo apple
2 bar orange
SQL> declare
2 l_str varchar2(200);
3 begin
4 l_str := 'alter table test rename column type to value';
5 execute immediate l_str;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL> select * from test;
ID NAM VALUE --> column name has changed
---------- --- ------
1 foo apple
2 bar orange
SQL>

Related

DB script for oracle

I have below table structure
To delete the record user will pass the ID, but we have similar text in Data column all the records should be deleted, for example in the above table if users pass 1, we need to delete 3 records (ID 1,2,3), please help for the single delete query.
Use a subquery:
SQL> select * From test;
ID DAT
---------- ---
1 abc
2 abc
3 abc
4 def
5 ijk
6 lmn
6 rows selected.
SQL> delete from test t
2 where t.data = (select t1.data
3 from test t1
4 where t1.id = &id
5 );
Enter value for id: 1
old 4: where t1.id = &id
new 4: where t1.id = 1
3 rows deleted.
SQL> select * from test;
ID DAT
---------- ---
4 def
5 ijk
6 lmn
SQL>

How I can return two columns into a function?

How I can return two columns into a function?
For example: "EmployeeName | salary" and the values are "Jonas Daniel | $2500$
Using ref cursor is one option:
SQL> create or replace function f_test (par_empno in number)
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for select ename, sal from emp
7 where empno = par_empno;
8 return rc;
9 end;
10 /
Function created.
SQL> select f_test(7839) from dual;
F_TEST(7839)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ENAME SAL
---------- ----------
KING 10000
SQL>
Or, you could try this approach:
SQL> create or replace type t_test_row is object
2 (ename varchar2(20),
3 sal number);
4 /
Type created.
SQL> create or replace type t_test_tab is table of t_test_row;
2 /
Type created.
SQL> create or replace function f_test (par_empno in number)
2 return t_test_tab
3 is
4 l_tab t_test_tab := t_test_tab();
5 begin
6 select t_test_row(ename, sal)
7 bulk collect into l_tab
8 from emp
9 where empno = par_empno;
10
11 return l_tab;
12 end;
13 /
Function created.
SQL> select f_test(7839) from dual;
F_TEST(7839)(ENAME, SAL)
--------------------------------------------------------------------------------
T_TEST_TAB(T_TEST_ROW('KING', 10000))
SQL> -- Or, a nice presentation
SQL> select * from table(f_test(7839));
ENAME SAL
-------------------- ----------
KING 10000
SQL>
Or, you could create a procedure:
SQL> set serveroutput on
SQL> create or replace procedure p_test (par_empno in number,
2 par_ename out varchar2,
3 par_sal out number)
4 is
5 begin
6 select ename, sal
7 into par_ename, par_sal
8 from emp
9 where empno = par_empno;
10 end;
11 /
Procedure created.
SQL> declare
2 l_ename varchar2(20);
3 l_sal number;
4 begin
5 p_test(7839, l_ename, l_sal);
6 dbms_output.put_line('Name = ' || l_ename ||', salary = ' || l_sal);
7 end;
8 /
Name = KING, salary = 10000
PL/SQL procedure successfully completed.
SQL>
But, if you meant to return two scalar values, you might be out of luck.

How to compare two attributes from two different tables in a trigger?

So I have two tables:
CREATE TABLE SALE(
OID_PA NUMBER(*,0) PRIMARY KEY,
Amount INTEGER NOT NULL,
Delivery_DATE DATE NOT NULL,
OID_V NUMBER(*,0) NOT NULL,
CONSTRAINT "AMOUNTCHECK" CHECK(Amount >=0),
FOREIGN KEY (OID_V) REFERENCES TICKET(OID_V));
and
CREATE TABLE PRODUCT
( Code NUMBER(*,0) PRIMARY KEY,
Stock INTEGER NOT NULL,
Price NUMBER(4,2) NOT NULL,
Production_Cost NUMBER NOT NULL,
Model VARCHAR2(50) NOT NULL,
TipoMueble VARCHAR2(50) NOT NULL,
TipoMaterial VARCHAR2(50) NOT NULL,
OID_PA NUMBER(*,0) NOT NULL,
CONSTRAINT "TipoMueble" CHECK(TipoMueble IN
('Canteado','Complementos','Glaciar',
'GrupoDiseño','Tiradores','Vitrinas')),
CONSTRAINT "TipoMaterial" CHECK(TipoMaterial IN ('Aluminio','Lacados',
'Polilaminado','Madera','Cristal','Chapa','Granito','Formica',
'Aglomerado','Marmol','Elementos de ferreteria')),
CONSTRAINT "StockNegativo" CHECK(Stock >=0),
CONSTRAINT "Precio" CHECK(Price>=0),
CONSTRAINT "CosteProduccion" CHECK (Production_cost>=0),
FOREIGN KEY (OID_PA) REFERENCES PAQUETE(OID_PA));
And now I want to create a trigger, the moment I modify the attribute "Amount" in the table "SALE" then it should check the table "PRODUCT".
if "stock" (in PRODUCT) is less than Amount (in SALE) then
RAISE_APPLICATION_ERROR(-20003, 'Not enough stock');
But I don't know how to make the trigger check in both tables at the same time.
right now my code looks likethis right now, it also has some errors that I'm not sure how to fix.
CREATE OR REPLACE TRIGGER TR_ENOUGH_STOCK
AFTER INSERT OR UPDATE OF Amount ON SALE
BEGIN
SELECT a.Amount, b.Stock
FROM ( SALE a inner join PRODUCT b on (a.Code = b.Code))
IF b.Stock < a.Amount THEN
RAISE APPLICATION ERROR(-20003, 'Not enough stock');
END IF;
END;
/
And I'm getting the following errors:
Error(209,5): PL/SQL: SQL Statement ignored
Error(212,5): PL/SQL: ORA-00933: SQL command not properly ended
Error(214,9): PLS-00103: Encountered the symbol "IF" when expecting one of
the following: ; <an identifier> <a double-quoted delimited-identifier>
Here's an example of how you might do that.
Create tables & a trigger:
SQL> create table sale (oid_pa number, amount number);
Table created.
SQL> create table product (code number, stock number, oid_pa number);
Table created.
SQL>
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select stock into l_stock
9 from product
10 where oid_pa = :new.oid_pa;
11
12 if :new.amount > l_stock then
13 raise_application_error(-20000, 'Not enough stock');
14 end if;
15 end;
16 /
Trigger created.
SQL>
Insert sample records into the PRODUCT table:
SQL> insert into product values (1, 100, 10);
1 row created.
SQL> insert into product values (2, 50, 20);
1 row created.
SQL> select * From product;
CODE STOCK OID_PA
---------- ---------- ----------
1 100 10
2 50 20
SQL>
Let's test it:
SQL> -- 90 is less OID_PA = 10 stock value (which is 100)
SQL> insert into sale values (10, 90);
1 row created.
SQL> -- let's try to update it to a value larger than stock value:
SQL> update sale set amount = 200 where oid_pa = 10;
update sale set amount = 200 where oid_pa = 10
*
ERROR at line 1:
ORA-20000: Not enough stock
ORA-06512: at "HR.TRG_BIU_STOCK", line 9
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL> -- how about a value which doesn't exceed the stock value?
SQL> update sale set amount = 5 where oid_pa = 10;
1 row updated.
SQL> select * From sale;
OID_PA AMOUNT
---------- ----------
10 5
SQL>
As of your attempt: apart from trigger code having wrong syntax, it wouldn't work anyway because you can't reference a table trigger is based on because it is right now being changed, i.e. it is mutating. Oracle raises an error in such cases (see an example below). The correct way is to reference that table's columns using :new or :old, as I did (see an example above).
SQL> create or replace trigger trg_biu_stock
2 before insert or update
3 on sale
4 for each row
5 declare
6 l_stock product.stock%type;
7 begin
8 select p.stock into l_stock
9 from product p
10 join sale s
11 on s.oid_pa = p.oid_pa
12 where p.oid_pa = :new.oid_pa;
13
14 if :new.amount > l_stock then
15 raise_application_error(-20000, 'Not enough stock');
16 end if;
17 end;
18 /
Trigger created.
SQL> update sale set amount = 50 where oid_pa = 10;
update sale set amount = 50 where oid_pa = 10
*
ERROR at line 1:
ORA-04091: table HR.SALE is mutating, trigger/function may not see it
ORA-06512: at "HR.TRG_BIU_STOCK", line 4
ORA-04088: error during execution of trigger 'HR.TRG_BIU_STOCK'
SQL>

How to batch insert multiple rows at once into a table with an auto incremented id field in oracle? [duplicate]

How do you do multiple insert with SQL in Oracle 12c when you have an identity column?
INSERT ALL
INTO Table1 (Column2) Values (1)
INTO Table1 (Column2) Values (2)
SELECT * FROM dual;
where Table1 has column1 as an identity, will set the identity column to have the same value which violates the primary key constraint.
CREATE TABLE Table1 (
Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
column2 VARCHAR2(255),
column3 NUMBER,
PRIMARY KEY (Table1Id)
);
INSERT ALL
INTO Table1 (column2, column3) VALUES ('a', '1')
INTO Table1 (column2, column3) VALUES ('b', '2')
SELECT * FROM dual;
--SQL Error: ORA-00001: unique constraint violated
What am I doing wrong with this?
EDIT Added two test cases, and a possible workaround.
Though Insert statement and insert all statement are practically the same conventional insert statement. But when it comes to sequences, they work differently.
Test case 1 : Identity columns
SQL> DROP TABLE table1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 PRIMARY KEY (Table1Id)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES ('1')
3 INTO Table1 (column3) VALUES ('2')
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.SYS_C0010439) violated
SQL>
Let's see what's actually happening under the hood -
SQL> CREATE TABLE Table1 (
2 Table1Id NUMBER GENERATED ALWAYS AS IDENTITY,
3 column3 NUMBER,
4 CONSTRAINT A UNIQUE (Table1Id)
5 );
Table created.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.A) violated
SQL> SELECT * FROM table1;
no rows selected
SQL> ALTER TABLE table1
2 DISABLE CONSTRAINT a;
Table altered.
SQL> INSERT ALL
2 INTO Table1 (column3) VALUES (1)
3 INTO Table1 (column3) VALUES (2)
4 SELECT * FROM dual;
2 rows created.
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
2 2
SQL>
So, the sequence progressed to nextval however there was an unique constraint violation the first time we did an Insert All. Next, we disabled the unique constraint, and the subsequent Insert All reveals that the sequence did not progress to nextval, rather it attempted to insert duplicate keys.
Though the issue doesn't occur with a INSERT-INTO-SELECT statement.
SQL> INSERT INTO table1(column3) SELECT LEVEL FROM dual CONNECT BY LEVEL <=5;
5 rows created.
SQL>
SQL> SELECT * FROM table1;
TABLE1ID COLUMN3
---------- ----------
2 1
3 2
4 3
5 4
6 5
SQL>
Surprisingly, as per the metadata, the sequence is supposed to proceed to nextval automatically, however it doesn't happen with an Insert All statement.
SQL> SELECT COLUMN_NAME,
2 IDENTITY_COLUMN,
3 DATA_DEFAULT
4 FROM user_tab_cols
5 WHERE table_name ='TABLE1'
6 AND IDENTITY_COLUMN='YES';
COLUMN_NAME IDENTITY_COLUMN DATA_DEFAULT
--------------- --------------- ------------------------------
TABLE1ID YES "LALIT"."ISEQ$$_94458".nextval
SQL>
Test Case 2 : Using a sequence explicitly
The INSERT ALL would work the same way whether an identity column is used or an explicit sequence is used.
SQL> DROP SEQUENCE s;
Sequence dropped.
SQL>
SQL> CREATE SEQUENCE s;
Sequence created.
SQL>
SQL> DROP TABLE t PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t (
2 ID NUMBER,
3 text VARCHAR2(50),
4 CONSTRAINT id_pk PRIMARY KEY (ID)
5 );
Table created.
SQL>
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
INSERT ALL
*
ERROR at line 1:
ORA-00001: unique constraint (LALIT.ID_PK) violated
SQL>
SQL> SELECT * FROM T;
no rows selected
SQL>
SQL> ALTER TABLE t
2 DISABLE CONSTRAINT id_pk;
Table altered.
SQL> INSERT ALL
2 INTO t VALUES (s.nextval, 'a')
3 INTO t VALUES (s.nextval, 'b')
4 INTO t VALUES (s.nextval, 'c')
5 INTO t VALUES (s.nextval, 'd')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM T;
ID TEXT
---------- ----------------------------------------
2 a
2 b
2 c
2 d
SQL>
Possible workaround - Using a ROW LEVEL trigger
SQL> CREATE OR REPLACE TRIGGER t_trg
2 BEFORE INSERT ON t
3 FOR EACH ROW
4 WHEN (new.id IS NULL)
5 BEGIN
6 SELECT s.NEXTVAL
7 INTO :new.id
8 FROM dual;
9 END;
10 /
Trigger created.
SQL> truncate table t;
Table truncated.
SQL> INSERT ALL
2 INTO t (text) VALUES ('a')
3 INTO t (text) VALUES ('b')
4 INTO t (text) VALUES ('c')
5 INTO t (text) VALUES ('d')
6 SELECT * FROM dual;
4 rows created.
SQL> SELECT * FROM t;
ID TEXT
---------- -------------------------
3 a
4 b
5 c
6 d
SQL>
Here's a workaround using the UNION ALL method instead of the INSERT ALL method. For some reason the data must be wrapped in a select * from (...) or it will generate the error ORA-01400: cannot insert NULL into ("JHELLER"."TABLE1"."TABLE1ID").
insert into table1(column2, column3)
select *
from
(
select 'a', '1' from dual union all
select 'b', '2' from dual
);

Find out the values between a range in SQL Server 2005(SET BASED APPROACH)?

I have a table like
Id Value
1 Start
2 Normal
3 End
4 Normal
5 Start
6 Normal
7 Normal
8 End
9 Normal
I have to bring the output like
id Value
1 Start
2 Normal
3 End
5 Start
6 Normal
7 Normal
8 End
i.e. the records between Start & End. Records with id's 4 & 9 are outside the Start & End henceforth are not there in the output.
How to do this in set based manner (SQLServer 2005)?
Load a table #t:
declare #t table(Id int,Value nvarchar(100));
insert into #t values (1,'Start'),(2,'Normal'),(3,'End'),(4,'Normal'),(5,'Start'),(6,'Normal'),(7,'Normal'),(8,'End'),(9,'Normal');
Query:
With RangesT as (
select Id, (select top 1 Id from #t where Id>p.Id and Value='End' order by Id asc) Id_to
from #t p
where Value='Start'
)
select crossT.*
from RangesT p
cross apply (
select * from #t where Id>=p.Id and Id<=Id_to
) crossT
order by Id
Note that I'm assuming no overlaps. The result:
Id Value
----------- ------
1 Start
2 Normal
3 End
5 Start
6 Normal
7 Normal
8 End

Resources