Date difference between first and last book per each author - oracle

I would create this table.
create table Books
(
book_id number PRIMARY KEY,
Title number,
yearpub DATE,
author_id number
)
create table Authors
(
author_id number,
book_id number
)
create table Author_name
(
author_id number,
first_name varchar(10),
last_name varchar(10)
)
I have no Oracle installed now but I wanna select
Date difference (number of years) between first and last book per each author.
I think there should be some aggregate function.

SELECT authors.author_id,
max(name.first_name||' '||name.last_name) as full_name,
max(to_char(yearpub,'yyyy')) - min(to_char(yearpub,'yyyy')) as years
from author_name name, books,authors
where
authors.author_id = name.author_id and
authors.author_id = books.author_id and
authors.book_id = books.book_id
group by authors.author_id

Related

How to insert comma delimited values in one column in oracle?

I have a table by the name of personal_info and it contains id,name and phone_number as columns. So the following is table structure which I want to store data.
id
name
phone_number
1
ali
03434444, 03454544, 0234334
So how to store data in phone_number column in comma delimited format and how to filter that column in where clause for example
Select * from personal_info where phone_number = 03454544 ;
And which datatype is suitable for phone_number column.
Well, the real good practice would rather be to have another table PHONE with a 1xN association (for example a PHONE_ID primary key, and ID and PHONE columns.)
You may then have the result you want with a view based on your two tables and using the LISTAGG operator : https://fr.wikibooks.org/wiki/Oracle_Database/Utilisation_de_fonctions/fonction_LISTAGG, but this will be much efficient to work with, especially if you want WHERE clauses based on your phone numbers.
Use LIKE with the delimiters:
Select *
from personal_info
where ', ' || phone_number || ', ' LIKE '%, ' || '03454544' || ', %';
However
You should consider changing your data structure to store the phone numbers in a separate table:
CREATE TABLE phone_numbers (
person_id REFERENCES personal_info (id),
phone_number VARCHAR2(12)
);
And then you can get the data using a JOIN
SELECT pi.*,
pn.phone_number
FROM personal_info pi
INNER JOIN phone_numbers pn
ON (pi.id = pn.person_id)
WHERE pn.phone_number = '03434444'
or, if you want all the phone numbers:
SELECT pi.*,
pn.phone_numbers
FROM personal_info pi
INNER JOIN (
SELECT person_id,
LISTAGG(phone_number, ', ') WITHIN GROUP (ORDER BY phone_number)
AS phone_numbers
FROM phone_numbers
GROUP BY person_id
HAVING COUNT(CASE WHEN phone_number = '03434444' THEN 1 END) > 0
) pn
ON (pi.id = pn.person_id)
db<>fiddle here
VARCHAR2 is suitable for phone numbers.
You can get the values this way:
WITH personal_info AS
(
SELECT 1 AS ID, 'Ali' AS NAME, '03434444, 03454544, 0234334' AS phone_number FROM dual
)
SELECT *
FROM (SELECT id, name, TRIM(regexp_substr(phone_number, '[^,]+', 1, LEVEL)) AS phone_number
FROM personal_info
CONNECT BY LEVEL <= LENGTH (phone_number) - LENGTH(REPLACE(phone_number, ',' )) + 1)
WHERE phone_number = '03454544';
Wrong data model, it isn't normalized. You should create a new table:
create table phones
(id_phone number constraint pk_phone primary key,
id_person number constraint fk_pho_per references person (id_person),
phone_number varchar2(30) not null
);
Then you'd store as many numbers as you want, one-by-one (row-by-row, that is).
If you want to do it your way, store it just like that:
insert into person (id, name, phone_number)
values (1, 'ali', '03434444, 03454544, 0234334');
One option of querying such data is using the instr function:
select * from person
where instr(phone_number, '03434444') > 0;
or like:
select * from person
where phone_number like '%'% || '03434444' || '%'
or split it into rows:
select * from person a
where '03434444' in (select regexp_substr(b.phone_number, '[^,]+', 1, level)
from person b
where b.id_person = a.id_person
connect by level <= regexp_count(b.phone_number, ',') + 1
)
I'd do it my way, i.e. with a new table that contains only phone numbers.

Select queries joins or group by

I have 3 tables and I want to make a select statement and show patient first name and bill status and method how he paid and the amount he paid, but I am having a hard time trying to make that query.
```` Create table patient (
```` Patient_ID Number(9) primary key,
```` First_name varchar2(15),
```` Last_name varchar2(10),
```` Contact number(10),
```` City varchar2(20),
```` Doctor_ID Number(9) references Doctor(Doctor_ID));
This is the payment table
````Payment_ID Number(9) primary key,
````Amount Number(10),
````Pyt_Date Date,
````Method varchar2(15),
````PatientID Number(9) references Patient(Patient_ID));
This is the bill table
````Bill_ID Number(9) primary key,
````Status char(10),
````Amount_paid varchar(30),
````Amount_NotPaid varchar(30),
````PatientID Number(9) references Patient(Patient_ID));
This is the query I wrote but it's not working
```` select p.First_name "Patient", Status, method, Amount_paid
```` from patient p and payment on bill
```` where p.Firstname = status, method = amount_paid
```` group by p.First_name, status, method, amount_paid;
The query you wrote is not syntactically valid. You wrote:
select p.First_name "Patient", Status, method, Amount_paid
from patient p and payment on bill
where p.Firstname = status, method = amount_paid
group by p.First_name, status, method, amount_paid
You've got an AND in the FROM clause where it's not allowed, and you're missing an AND in the WHERE clause where it's needed:
select p.First_name "Patient", b.Status, b.method, b.Amount_paid
from patient p,
bill b
where p.Firstname = b.status AND
b.method = b.amount_paid
group by p.First_name, b.status, b.method, b.amount_paid
But I really doubt that's going to do what you want as I doubt very much that the billing method will match the amount paid, or that the patient's first name will match the billing status.
Perhaps what you meant was
SELECT p.FIRST_NAME, b.STATUS, y.METHOD, b.AMOUNT_PAID
FROM PATIENT p
INNER JOIN BILL b
ON b.PATIENTID = p.PATIENT_ID
INNER JOIN PAYMENT y
ON y.PATIENTID = p.PATIENT_ID
It would help if you'd edit your question to include example data for each table, and the results you'd expect from the sample data. Thanks.

How to implement this trigger on Oracle SQL?

I have found an exercise in the SQL book I study, which is not solved and I can not solve it.
The goal is to implement a trigger that avoids overlapping contracts. If a customer with a current contract signs a new one, the end date of the previous one will be one day before the new start date.
Tables given are:
CREATE TABLE CLIENTS (
clientId VARCHAR2(15),
DNI VARCHAR2(9),
name VARCHAR2(100) NOT NULL,
surname VARCHAR2(100) NOT NULL,
sec_surname VARCHAR2(100),
eMail VARCHAR2(100) NOT NULL,
phoneN NUMBER(12),
birthdate DATE,
CONSTRAINT PK_CLIENTS PRIMARY KEY (clientId),
CONSTRAINT UK1_CLIENTS UNIQUE (DNI),
CONSTRAINT UK2_CLIENTS UNIQUE (eMail),
CONSTRAINT UK3_CLIENTS UNIQUE (phoneN),
);
CREATE TABLE contracts(
contractId VARCHAR2(10),
clientId VARCHAR2(15),
startdate DATE NOT NULL,
enddate DATE,
contract_type VARCHAR2(50),
address VARCHAR2(100) NOT NULL,
town VARCHAR2(100) NOT NULL,
ZIPcode VARCHAR2(8) NOT NULL,
country VARCHAR2(100) NOT NULL,
CONSTRAINT PK_contracts PRIMARY KEY (contractId),
CONSTRAINT FK_contracts1 FOREIGN KEY (clientId) REFERENCES CLIENTS
);
Any suggestions?
I agree with the posted comments that it helps to have some specifics about what is failing in the prior attempts, and I would also recommend not using a TRIGGER at all for this kind of thing.
But as this is for a study exercise, here are some examples that might be a starting place.
I've modified your tables to disallow NULL PRIMARY KEYs in these examples.
To get started, create the tables:
CREATE TABLE CLIENTS (
CLIENTID VARCHAR2(15) NOT NULL,
DNI VARCHAR2(9),
NAME VARCHAR2(100) NOT NULL,
SURNAME VARCHAR2(100) NOT NULL,
SEC_SURNAME VARCHAR2(100),
EMAIL VARCHAR2(100) NOT NULL,
PHONEN NUMBER(12),
BIRTHDATE DATE,
CONSTRAINT PK_CLIENTS PRIMARY KEY (CLIENTID),
CONSTRAINT UK1_CLIENTS UNIQUE (DNI),
CONSTRAINT UK2_CLIENTS UNIQUE (EMAIL),
CONSTRAINT UK3_CLIENTS UNIQUE (PHONEN)
);
CREATE TABLE CONTRACTS (
CONTRACTID VARCHAR2(10) NOT NULL,
CLIENTID VARCHAR2(15) NOT NULL,
STARTDATE DATE NOT NULL,
ENDDATE DATE,
CONTRACT_TYPE VARCHAR2(50),
ADDRESS VARCHAR2(100) NOT NULL,
TOWN VARCHAR2(100) NOT NULL,
ZIPCODE VARCHAR2(8) NOT NULL,
COUNTRY VARCHAR2(100) NOT NULL,
CONSTRAINT PK_CONTRACTS PRIMARY KEY (CONTRACTID),
CONSTRAINT FK_CONTRACTS1 FOREIGN KEY (CLIENTID) REFERENCES CLIENTS
);
Then, create the first CLIENTs:
INSERT INTO CLIENTS VALUES (1,NULL,'Frodo','Baggins',NULL,'the.real.frodo#adventure.com',NULL,NULL);
INSERT INTO CLIENTS VALUES (2,NULL,'Chewbacca','UNKNOWN',NULL,'chewio.#kashyyyk.org',NULL,NULL);
COMMIT;
Then create a TRIGGER. In this first example, the TRIGGER is an AFTER STATEMENT type.
It is simple but inefficient since it evaluates every CLIENT after each INSERT statement.
Against a large data set, or in the face of multiple TRIGGERs, this could be a problem.
This TRIGGER will check for the prior contract and will set its ENDDATE to one day before the new contract, if it is null or after the start of the new contract.
CREATE OR REPLACE TRIGGER CONTRACT_ENDDATE_ADJUSTER
AFTER INSERT ON CONTRACTS
BEGIN
MERGE INTO CONTRACTS
USING (
SELECT CONTRACTID,
CANDIDATE_ENDDATE AS ENDDATE
FROM
(SELECT CONTRACTS.CONTRACTID,
(TRUNC(LEAD(STARTDATE) OVER (PARTITION BY CLIENTID ORDER BY STARTDATE ASC) - 1)) AS CANDIDATE_ENDDATE,
DENSE_RANK() OVER (PARTITION BY CLIENTID ORDER BY STARTDATE DESC) AS CONTRACT_ORDER
FROM CONTRACTS)
WHERE CONTRACT_ORDER = 2) CANDIDATE_CONTRACT
ON (CONTRACTS.CONTRACTID = CANDIDATE_CONTRACT.CONTRACTID)
WHEN MATCHED THEN UPDATE SET CONTRACTS.ENDDATE = CANDIDATE_CONTRACT.ENDDATE
WHERE CONTRACTS.ENDDATE IS NULL OR CONTRACTS.ENDDATE > CANDIDATE_CONTRACT.ENDDATE;
END;
/
Then, test it out.
Add the initial contracts. No enddate changes expected, as these are the first. Frodo's contract here has an end-date already set.
INSERT INTO CONTRACTS VALUES('Break-Ring',1,TO_DATE('19560511','YYYYMMDD'), TO_DATE('19851014','YYYYMMDD'), NULL, 'No 1', 'Doom Mountain', 'MORD', 'Middle-Earth');
INSERT INTO CONTRACTS VALUES('SaveGalaxy',2,TO_DATE('19770615','YYYYMMDD'), NULL, NULL, 'No 75', 'Rwookrrorro', 'RWKR', 'Kashyyyk');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 14-OCT-85
SaveGalaxy 2 15-JUN-77
Then add new contracts.
Frodo's new contract starts before the end of his existing contract, so the enddate will be adjusted.
Chewie's initial contract had no ENDDATE, so it will be adjusted as well.
INSERT INTO CONTRACTS VALUES('GoBackHome',1,TO_DATE('19570219','YYYYMMDD'), NULL, NULL, 'No 13', 'Hobbiton', 'HBTN', 'Middle-Earth');
INSERT INTO CONTRACTS VALUES('DefendHoth',2,TO_DATE('19801115','YYYYMMDD'), NULL, NULL, 'Meteor Crater', 'Ice Ridge', 'METEO', 'Hoth');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80
And as other contracts are signed, the pattern continues:
INSERT INTO CONTRACTS VALUES('GoWedding',2,TO_DATE('19830309','YYYYMMDD'), NULL, NULL, 'Main Hall', 'Grand Palace', 'ALLNC', 'Coruscant');
INSERT INTO CONTRACTS VALUES('Gardening',1,TO_DATE('19570503','YYYYMMDD'), NULL, NULL, 'No 13', 'Hobbiton', 'HBTN', 'Middle-Earth');
SELECT CONTRACTID, CLIENTID, STARTDATE, ENDDATE FROM CONTRACTS ORDER BY CLIENTID ASC, STARTDATE ASC;
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57 02-MAY-57
Gardening 1 03-MAY-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80 08-MAR-83
GoWedding 2 09-MAR-83
To stabilize the workload on this query, a COMPOUND TRIGGER may be used instead. This second example achieves the same result as the first, but only interrogates the CONTRACTs of CLIENTs that have changed:
First, ROLLBACK;
Then, create a type to be used by the TRIGGER:
CREATE OR REPLACE TYPE NUMBER_LIST IS TABLE OF NUMBER;
/
Then create the COMPOUND TRIGGER:
CREATE OR REPLACE TRIGGER CONTRACT_ENDDATE_ADJUSTER
FOR INSERT ON CONTRACTS
COMPOUND TRIGGER
V_CLIENTS NUMBER_LIST;
BEFORE STATEMENT
IS
BEGIN
V_CLIENTS:= NUMBER_LIST();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
V_CLIENTS.EXTEND();
V_CLIENTS(V_CLIENTS.COUNT) := :NEW.CLIENTID;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
MERGE INTO CONTRACTS
USING (
SELECT CONTRACTID,
CANDIDATE_ENDDATE AS ENDDATE
FROM
(SELECT CONTRACTS.CONTRACTID,
(TRUNC(LEAD(STARTDATE) OVER (PARTITION BY CLIENTID ORDER BY STARTDATE ASC) - 1)) AS CANDIDATE_ENDDATE,
DENSE_RANK() OVER (PARTITION BY CLIENTID ORDER BY STARTDATE DESC) AS CONTRACT_ORDER
FROM CONTRACTS
WHERE CONTRACTS.CLIENTID IN (SELECT * FROM TABLE(V_CLIENTS)))
WHERE CONTRACT_ORDER = 2) CANDIDATE_CONTRACT
ON (CONTRACTS.CONTRACTID = CANDIDATE_CONTRACT.CONTRACTID)
WHEN MATCHED THEN UPDATE SET CONTRACTS.ENDDATE = CANDIDATE_CONTRACT.ENDDATE
WHERE CONTRACTS.ENDDATE IS NULL OR CONTRACTS.ENDDATE > CANDIDATE_CONTRACT.ENDDATE;
END AFTER STATEMENT;
END CONTRACT_ENDDATE_ADJUSTER;
/
After repeating the above inserts, the result is the same:
CONTRACTID CLIENTID STARTDATE ENDDATE
Break-Ring 1 11-MAY-56 18-FEB-57
GoBackHome 1 19-FEB-57 02-MAY-57
Gardening 1 03-MAY-57
SaveGalaxy 2 15-JUN-77 14-NOV-80
DefendHoth 2 15-NOV-80 08-MAR-83
GoWedding 2 09-MAR-83

oracle syntax query

select line_number, times
FROM LINE
JOIN LINE_STOP ON LINE.ID=LINE_STOP.LINE_ID
JOIN PASSAGE ON LINE.STOP_ID = PASSAGE.LINE_STOP_ID
where times =
(select count(*)
from passage
where times between 500 and 620);
I am trying to calculate the number of time I have a number between 500 and 620 and sort it by line_number.
It gives me a table with the good column but no value.
these are my tables:
CREATE TABLE Line (
ID NUMBER(5,0) PRIMARY KEY,
Line_Number NUMBER(3,0) NOT NULL,
Direction VARCHAR2(5) NOT NULL,
Day_Week VARCHAR2(3) NOT NULL,
Stop_ID NUMBER(5,0) CONSTRAINT Line_Stop_FK REFERENCES Stop);
CREATE TABLE Line_Stop (
ID NUMBER(5,0) PRIMARY KEY,
Line_ID NUMBER(5,0) NOT NULL CONSTRAINT Line_Stop_Line_FK REFERENCES Line,
Stop_ID NUMBER(5,0) NOT NULL CONSTRAINT Line_Stop_Stop_FK REFERENCES Stop
);
CREATE TABLE Passage (
ID NUMBER(5,0) PRIMARY KEY,
Line_Stop_ID NUMBER(5,0) NOT NULL CONSTRAINT Passage_Line_Stop_FK REFERENCES Line_Stop,
Hours NUMBER(2,0) NOT NULL,
Minutes NUMBER(2,0) NOT NULL,
Times Number(4,0) NOT NULL
);
This filters the join having time between 500 and 620, and then groups it by line_number counting the number of occurences
select line_number, count(times)
FROM LINE
JOIN LINE_STOP ON (LINE.ID=LINE_STOP.LINE_ID AND LINE.STOP_ID=LINE_STOP.STOP_ID)
JOIN PASSAGE ON LINE.STOP_ID = PASSAGE.LINE_STOP_ID
where times between 500 and 620
group by line_number
order by line_number;

Trying to collect data from multiple tables with logic aritmethics

I am trying to collect data from multiple tables with logic aritmethics like: OR AND NOT UNION INTERSECT.
I have 3 tables called: Customers, Flights, Booking.
My aim is to get all the passportno from the customers table which took a flight more than once.
Thnaks for helpers.
Use group by and having clauses. The having limits those rows that are count>1
Schema
create table bookings
(
id int auto_increment primary key,
passportno int not null,
flightnumber int not null,
`day` date not null,
row varchar(10) not null,
seat varchar(10) not null
);
insert bookings(passportno,flightnumber,`day`,row,seat) values (1,2,'2005-01-01',1,1); -- Joe
insert bookings(passportno,flightnumber,`day`,row,seat) values (1,3,'2005-02-01',1,1); -- Joe
insert bookings(passportno,flightnumber,`day`,row,seat) values (2,3,'2005-02-01',2,2); -- Sally
create table customers
(
passportno int primary key,
name varchar(100) not null,
address varchar(100) not null,
phone varchar(20) not null
);
insert customers(passportno,name,address,phone) values (1,'Joe','addr','ph');
insert customers(passportno,name,address,phone) values (2,'Sally','addr','ph');
Query and results
select c.passportno
from customers c
join bookings b
on c.passportno=b.passportno
group by c.passportno
having count(b.id)>1;
+------------+
| passportno |
+------------+
| 1 |
+------------+
Mysql manual page entitled MySQL Handling of GROUP BY
If I understand the question the query should be something like
SELECT c.PASSPORTNO
FROM CUSTOMERS c
WHERE c.PASSPORTNO IN (SELECT DISTINCT PASSPORTNO
FROM (SELECT PASSPORTNO, FLIGHTNUMBER, COUNT(*)
FROM BOOKINGS
GROUP BY PASSPORTNO, FLIGHTNUMBER
HAVING COUNT(*) > 1)
Best of luck.

Resources