need your help
I have a table in Oracle SQL Developer of this kind:
Issue
ID|Subscriber_ID|Book_ID| Taken |Returned
--+-------------+-------+--------+--------
1 | 1 | 2 |01-06-16|05-06-16
2 | 3 | 5 |07-05-16| (null)
3 | 2 | 2 |06-06-16| (null)
4 | 1 | 3 |17-05-16|26-05-16
It's some sort of library book issuing where (null) in Returned column means that this book wasn't returned yet. I need to create validation rule to avoid issuing book that wasn't returned (e.g. I can't take the book #5 at the moment). How can I implement it?
Hmmm. You can't do this with a check constraint, because those only apply to values in one row.
What you want to ensure is that you do not have two returned values for a book. Some databases support filtered unique indexes:
create unique index on unq_issue_bookid on issue(book_id) where returned is null;
But not Oracle. You can do something very similar with a function-based index:
create unique index on unq_issue_bookid_returned
on issue(book_id,
(case when returned is not null then id else -1 end)
);
This will have the same effect of allowing only one NULL value per book.
You can do it with :
CREATE TABLE table_name ( ID, Subscriber_ID, Book_ID, Taken, Returned ) AS
SELECT 1, 1, 2, DATE '2016-06-01', DATE '2016-06-05' FROM DUAL UNION ALL
SELECT 2, 3, 5, DATE '2016-05-07', NULL FROM DUAL UNION ALL
SELECT 3, 2, 2, DATE '2016-06-06', NULL FROM DUAL UNION ALL
SELECT 4, 1, 3, DATE '2016-05-17', DATE '2016-05-26' FROM DUAL;
ALTER TABLE table_name ADD is_borrowed
GENERATED ALWAYS AS ( CASE WHEN returned IS NULL THEN 1 END ) VIRTUAL;
ALTER TABLE TABLE_NAME ADD CONSTRAINT is_borrowed__u
UNIQUE( book_id, is_borrowed );
Then:
INSERT INTO table_name ( ID, Subscriber_ID, Book_ID, Taken )
VALUES ( 5, 2, 5, DATE '2016-06-06' );
Will fail with:
SQL Error: ORA-00001: unique constraint (TEST.IS_BORROWED__U) violated
Related
I have a table and want to find out whether the given condition matches or not with existing table records. I tried with not exists but that doesn't work. What expression would fill the is_exists column like in the expected output below?
create table user1(id number, name varchar2(20), age number);
insert into user1 values(1, 'user1', 1);
insert into user1 values(1, 'user11', 11);
insert into user1 values(2, 'user2', 2);
insert into user1 values(2, 'user22', 22);
insert into user1 values(3, 'user3', 3);
insert into user1 values(4, 'user4', 4);
select id, age, is_exists
from user1
where id = 1 and age in (1, 11, 111);
Expected result:
id age is_exists
-- --- ---------
1 1 true
1 11 true
1 111 false
You need some kind of table for the three set of values; which you can build using select ... union all. Then use left join or exists:
with cte as (
select 1 as id, 1 as age from dual union all
select 1, 11 from dual union all
select 1, 111 from dual
)
select cte.*, case when exists (
select 1
from user1
where user1.id = cte.id and user1.age = cte.age
) then 'yes' else 'no' end is_exists
from cte
SQL is designed to return a subset or transformed set of data from the underlying database. For example, FROM specifies that you want all columns and rows from a particular table. The expression list in the SELECT statement narrows down the number of columns you need from this table. The WHERE statement narrows down the number of rows you receive.
Every row in the result set is based on one or more existing rows in the original data set. In either case the maximum result is always the data that exists in the table.
Because you want one row for the age in the result set, you need one row for the age in the query source. If you don't have a table, creating a temporary one with a series of UNION is a good approach.
This is the same as 'Salman A', but using LEFT JOIN whose result is CASE tested in Select clause. Basicaly, if there is not a join then there is not a matching record...
WITH
fltr AS
(
Select 1 "ID", 1 "AGE" From DUAL UNION
Select 1 "ID", 11 "AGE" From DUAL UNION
Select 1 "ID", 111 "AGE" From DUAL
)
SELECT
f.ID "ID", f.AGE "AGE",
CASE WHEN u.ID Is Not Null THEN 'YES' ELSE 'NO' END "IS_EXISTS"
FROM
fltr f
LEFT JOIN
USER1 u ON(u.ID = f.ID And u.AGE = f.AGE)
--
-- Result
--
-- ID AGE IS_EXISTS
-- 1 1 YES
-- 1 11 YES
-- 1 111 NO
CREATE TABLE member (
member_no NUMBER PRIMARY KEY ,
member_money NUMBER NOT NULL,
);
CREATE TABLE MY_PAY(
pay_no NUMBER PRIMARY KEY,
member_no NUMBER,
pay_price number
);
I want MEMBER.member_money -(MINUS) MY_PAY.pay_price =??
and ?? insert into MEMBER.member_money
so i make this query
UPDATE MEMBER
set MEMBER.member_money = (select sum (MEMBER.member_point) - (MY_PAY.pay_price)
from MY_PAY
where MY_PAY.member_no = MEMBER.member_no
group by MEMBER.member_no)
where MEMBER.member_no =2 ;
but it doesn't work.
I'm not English man so i'm not good at English sorry
but i want your help
You can use:
UPDATE MEMBER m
set member_money = member_money - (select sum(pay_price)
from MY_PAY p
where p.member_no = m.member_no)
where member_no =2 ;
Which, for the sample data:
INSERT INTO member VALUES (2, 2000);
INSERT INTO my_pay
SELECT 1, 2, 100 FROM DUAL UNION ALL
SELECT 2, 2, 200 FROM DUAL;
Then, after the UPDATE:
SELECT * FROM member;
Outputs:
MEMBER_NO
MEMBER_MONEY
2
1700
db<>fiddle here
Data:
USER_ID VIOLATION_DATES
--------------------------------
1 18-Jul-21 > 24-Jul-21
2 05-Aug-21
3 09-Jun-21
1 18-Jul-21
I have a table that has columns for Users and their dates of violations. I want to extract the most recent violation for each user.
This is the query I've written:
select
USR_ID,
max(to_date(VIOLATION_DATES, 'DD-MON-YY')) as Most_Recent_VIOLATIONS
from
table
group by
USR_ID
However I get this error:
ORA-01830: date format picture ends before converting entire input string
I believe it has something to do with the way the most recent violation is appended (18-Jul-21 > 24-Jul-21 ). Can anyone provide any clarity on how I can extract the most recent date for each user? for example:
USER_ID VIOLATION_DATES
--------------------------
1 24-Jul-21
2 05-Aug-21
3 09-Jun-21
I understand that this storage method isn't ideal but this is out of my control.
You can split the multi-value string into separate rows, then "group by" as normal (the with clause is just to provide test data - substitute your actual table):
with demo (user_id, violation_dates) as
( select 1, '18-Jul-21 > 24-Jul-21' from dual union all
select 2, '05-Aug-21' from dual union all
select 3, '09-Jun-21' from dual union all
select 1, '18-Jul-21' from dual )
select user_id
, max(to_date(regexp_substr(d.violation_dates, '[^> ]+', 1, r.rn) default null on conversion error,'DD-MON-YY')) as violation_date
from demo d
cross apply
( select rownum as rn
from dual connect by rownum <= regexp_count(d.violation_dates,'>') +1 ) r
group by user_id
order by 1;
"default null on conversion error" requires Oracle 12.2.
I have selected the following data that I want to insert into the database.
Letter
Value
A
1
A
2
B
3
B
4
Since there is a repetition of "A" and "B" in this format, I want to split data into two separate tables: table1 and table2.
table1:
ID
Letter
1
A
2
B
ID here is automatically inserted by database (using a sequence).
table2:
table1_id
Value
1
1
1
2
2
3
2
4
In this particular example, I don't gain anything on storage but it illustrates in the best way what problem I have encountered.
How can I use SQL or PL/SQL to insert data into table1 and table2?
First populate table1 from the source
insert table1(Letter)
select distinct Letter
from srcTable;
Then load data from the source decoding letter to id
insert table2(table1_id, Value)
select t1.id, src.value
from srcTable src
join table1 t1 on t1.Letter = src.Letter;
You may use multitable insert with workaround to get stable nextval on sequence. Since nextval is evaluated on each row regardless of when condition, it is not sufficient to use it inside values.
insert all
when rn = 1 then into l(id, val) values(seq, letter)
when rn > 0 then into t(l_id, val) values(seq, val)
with a(letter, val) as (
select 'A', 1 from dual union all
select 'A', 2 from dual union all
select 'B', 3 from dual union all
select 'B', 4 from dual union all
select 'C', 5 from dual
)
, b as (
select
a.*,
l.id as l_id,
row_number() over(partition by a.letter order by a.val asc) as rn
from a
left join l
on a.letter = l.val
)
select
b.*,
max(decode(rn, 1, coalesce(
l_id,
extractvalue(
/*Hide the reference to the sequence due to restrictions
of multitalbe insert*/
dbms_xmlgen.getxmltype('select l_sq.nextval as seq from dual')
, '/ROWSET/ROW/SEQ/text()'
) + 0
))
) over(partition by b.letter) as seq
from b
select *
from l
ID | VAL
-: | :--
1 | A
2 | B
3 | C
select *
from t
L_ID | VAL
---: | --:
1 | 1
1 | 2
2 | 3
2 | 4
3 | 5
db<>fiddle here
Principally you need to produce and ID value for the table1 to be inserted into table2. For this, You can use INSERT ... RETURNING ID INTO v_id statement after creating the tables containing some constraints especially unique ones such as PRIMARY KEY and UNIQUE
CREATE TABLE table1( ID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, letter VARCHAR2(1) NOT NULL );
ALTER TABLE table1 ADD CONSTRAINT uk_tab1_letter UNIQUE(letter);
CREATE TABLE table2( ID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, table1_id INT, value INT );
ALTER TABLE table2 ADD CONSTRAINT fk_tab2_tab1_id FOREIGN KEY(table1_id) REFERENCES table1 (ID);
and adding exception handling in order not to insert repeating letters to the first table. Then use the following code block ;
DECLARE
v_id table1.id%TYPE;
v_letter table1.letter%TYPE := 'A';
v_value table2.value%TYPE := 1;
BEGIN
BEGIN
INSERT INTO table1(letter) VALUES(v_letter) RETURNING ID INTO v_id;
EXCEPTION WHEN OTHERS THEN NULL;
END;
INSERT INTO table2(table1_id,value) SELECT id,v_value FROM table1 WHERE letter = v_letter;
COMMIT;
END;
/
and run by changing the initialized values for v_letter&v_value as 'A'&2, 'B'&1,'B'&2 ..etc respectively.
Alternatively you can convert the code block to a stored procedure or function such as
CREATE OR REPLACE PROCEDURE Pr_Ins_Tabs(
v_letter table1.letter%TYPE,
v_value table2.value%TYPE
) AS
v_id table1.id%TYPE;
BEGIN
BEGIN
INSERT INTO table1(letter) VALUES(v_letter) RETURNING ID INTO v_id;
EXCEPTION WHEN OTHERS THEN NULL;
END;
INSERT INTO table2(table1_id,value) SELECT id,v_value FROM table1 WHERE letter = v_letter;
COMMIT;
END;
/
in order to revoke resiliently such as
BEGIN
Pr_Ins_Tabs('A',2);
END;
/
Demo
PS. If your DB verion is prior to 12c, then create sequences(seq1&seq2) and use seq1.nextval&seq2.nextval within the Insert statements as not possible to use GENERATED ALWAYS AS IDENTITY clause within the table creation DDL statements.
Suppose you have the following schema:
CREATE TABLE Data
(
ID INT,
CXL INT
)
INSERT INTO Data (ID, CXL)
SELECT 1, NULL
UNION
SELECT 2, 1
UNION
SELECT 3, 2
UNION
SELECT 5, 3
UNION
SELECT 6, NULL
UNION
SELECT 7, NULL
UNION
SELECT 8, 7
The column CXL is the ID that cancels a particular ID. So, for example, the first row in the table with ID:1 was good until it was cancelled by ID:2 (CXL column). ID:2 was good until it was cancelled by ID:3. ID:3 was good until it was cancelled by ID:5 so in this sequence the last "GOOD" ID was ID:5.
I would like to find all the "GOOD" IDs So in this example it would be:
Latest GOOD ID
5
6
8
Here's a fiddle if you want to play with this:
http://sqlfiddle.com/#!6/68ac48/1
SELECT D.ID
FROM Data D
WHERE NOT EXISTS(SELECT 1
FROM Data WHERE D.ID = CXL)
select Id
from data
where Id not in (select cxl from data where cxl is not null)