SQL Query (maybe simple) - sql

So i'm having this problem.
I have two tables (Oracle), one is called Destination and the other one Reserve. Reserve has a foreign key to the id of Destination (Because on reserve has one destination). And reserve tuples means all the reserves that all the users have done. I need a way to check the top 3 most visited Destinations (based on the foreign key in the table Reserve).
How can I do that with SQL in Oracle. I know that I need to search within the Reserve tables for the 3 most repeated Destinations ID's, and later join that with the Destination table to get the details of the top 3 destinations.
Any help is valid. Thank you very much.
SCHEMA:
--------------------------------------------------------
-- File created - martes-septiembre-15-2009
--------------------------------------------------------
--------------------------------------------------------
-- DDL for Table DESTINO
--------------------------------------------------------
CREATE TABLE "S2501A29"."DESTINO"
( "PK_ID_DESTINO" NUMBER(10,0),
"FK_COD_UBICACION_GEOGRAFICA" NUMBER(10,0),
"NOMBRE" VARCHAR2(10),
"FOTO" VARCHAR2(30),
"DESCRIPCION" VARCHAR2(50)
) ;
--------------------------------------------------------
-- DDL for Table LUGAR_ESTADIA
--------------------------------------------------------
CREATE TABLE "S2501A29"."LUGAR_ESTADIA"
( "PK_ID_ESTADIA" NUMBER(10,0),
"NOMBRE" VARCHAR2(10),
"TIPO" VARCHAR2(10),
"DESCRIPCION" VARCHAR2(50),
"COSTO_SERVICIOS" NUMBER,
"DESCRIPCION_ALOJAMIENTO" VARCHAR2(100),
"DESCRIPCION_ALIMENTACION" VARCHAR2(100)
) ;
--------------------------------------------------------
-- DDL for Table OPCION_TRANSPORTE
--------------------------------------------------------
CREATE TABLE "S2501A29"."OPCION_TRANSPORTE"
( "PK_ID_VIAJE" NUMBER(10,0),
"MEDIO_TRANSPORTE" VARCHAR2(10),
"RESPONSABLE" VARCHAR2(10),
"CIUDAD_ORIGEN" VARCHAR2(10),
"CIUDAD_DESTINO" VARCHAR2(10),
"COSTO" NUMBER
) ;
--------------------------------------------------------
-- DDL for Table RESERVA
--------------------------------------------------------
CREATE TABLE "S2501A29"."RESERVA"
( "PK_ID_RESERVA" NUMBER(10,0),
"FK_COD_DESTINO" NUMBER(10,0),
"FK_COD_ESTADIA" NUMBER(10,0),
"FK_COD_VIAJE" NUMBER(10,0),
"TARJETA_CREDITO" VARCHAR2(12),
"FECHA_SALIDA" DATE,
"FECHA_REGRESO" DATE,
"NOMBRE_USUARIO" VARCHAR2(50)
) ;
--------------------------------------------------------
-- DDL for Table UBICACION_GEOGRAFICA
--------------------------------------------------------
CREATE TABLE "S2501A29"."UBICACION_GEOGRAFICA"
( "PK_ID_UBICACION" NUMBER(10,0),
"CIUDAD" VARCHAR2(10),
"PAIS" VARCHAR2(10),
"CONTINENTE" VARCHAR2(10)
) ;
Just a note:
RESERVE is Reserva
DESTINATION is Destino
Because the DB is in spanish. Thanks!

Maybe I'm missing something, but what about grouping by foreign key and sorting by the resulting values?
Edit: Something like:
select FK_COD_DESTINO, count(*) as qty from RESERVA group by FK_COD_DESTINO order by qty desc limit 3

Oracle doesn't support limit. This should work for you.
select A.FK_COD_DESTINO
, A.COUNT
, A.RANK
, B.*
from (select FK_COD_DESTINO
, count(*) as COUNT
, rank() over (order by count(*) desc) as RANK
from RESERVA
group by FK_COD_DESTINO) A
join DESTINO B on B.PK_ID_DESTINO = A.FK_COD_DESTINO
where A.RANK <= 3

you can try HAVING rownum <=3

How about:
WITH CountsByDestination
AS
(
SELECT FK_COD_DESTINO,
COUNT(*) CNT
FROM Reservations
GROUP BY DestinationId
),
RankingByDestination
AS
(
SELECT FK_COD_DESTINO,
CNT,
RANK() OVER (ORDER BY CNT) RNK
FROM CountsByDestination
)
SELECT *
FROM RankingByDestination R
INNER JOIN Destinations D ON D.PK_ID_DESTINO = R.FK_COD_DESTINO
WHERE R.RNK BETWEEN 1 AND 3
ORDER BY R.RNK
This retains ties, so if two or more destinations share the same counts they should appear as part of the result set.

Related

Oracle Count and Group By Optimized Report Query

Oracle 19c
We have a requirement where we want to provide some report data from Event table, Event and Item table has schema like this
CREATE TABLE EVENT
(
"ID" NUMBER(19,0) NOT NULL ENABLE,
"CREATED" TIMESTAMP (6) NOT NULL,
"CUSTOMER_ID" VARCHAR2(255 CHAR) NOT NULL,
"CONF_ID" VARCHAR2(255 CHAR),
"STATE" VARCHAR2(255 CHAR) NOT NULL,
"ITEM_ID" VARCHAR2(255 CHAR) NOT NULL
...
)
CREATE TABLE ITEM
(
"ID" NUMBER(19,0) NOT NULL ENABLE,
"NAME" VARCHAR2(255 CHAR) NOT NULL
....
primary key (ID)
)
alter table EVENT
add constraint EVENT_FK_ITEM_BID
foreign key (ITEM_ID)
references ITEM;
where events are created with different states as per real time occurrance. Events are bound to Item Table with Item_id.
What we want to achieve select count of Event States (only consider the latest state per item_id), grouped by CUSTOMER_ID and CONF_ID.
Event table could have more than 2 million rows.
result should look like
CUSTOMER_ID CONF_ID ACIVATED DEACTIVATED SUSPENDED
---------- ------- -------- ------------- ---------
1 2 50000 20000 5000
1 1 70000 30000 2000
2 1 80000 10000 10000
2 2 50000 20000 5000
Could you please guide us building an efficient query?
You can find the latest rows using the RANK analytic function (or the ROW_NUMBER analytic function if there will only ever be one latest row for each customer_id, conf_id, item_id tuple) and then PIVOT:
SELECT *
FROM (
SELECT customer_id,
conf_id,
state
FROM (
SELECT customer_id,
conf_id,
state,
RANK() OVER (PARTITION BY customer_id, conf_id, item_id ORDER BY created DESC) AS rnk
FROM event
WHERE state IN ('ACTIVATED', 'SUSPENDED', 'DEACTIVATED')
)
WHERE rnk = 1
)
PIVOT (
COUNT(*)
FOR state IN (
'ACTIVATED' AS activated,
'SUSPENDED' AS suspended,
'DEACTIVATED' AS deactivated
)
);
fiddle

Copy oracle table data from two tables to another table

Here are my tables:
create table country(
country_id number(5) primary key,
country varchar2(36)
);
create table city(
city_id number(5) primary key,
country_id number(5) constraint city_country_fk references country(country_id) NOT NULL,
city varchar2(36)
);
SQL> desc airportdemodata;
Name Null? Type
----------------------------------------- -------- ----------------------------
AIRPORT_ID NOT NULL NUMBER(5)
IATA_CODE VARCHAR2(3)
CITY VARCHAR2(36)
COUNTRY VARCHAR2(36)
AIRPORT VARCHAR2(58)
I inserted countries from airportdemodata to country table as under:
INSERT INTO country (country)
SELECT unique(country) from airportdemodata;
it worked fine.
Now I am trying to copy airportdemodata.city into city.city(country_id,city) by matching airportdemodata.country with country.country. I tried like this:
SQL> SELECT a.unique(city), b.country_id FROM airportdemodata a, country b where a.country=b.country;
SELECT a.unique(city), b.country_id FROM airportdemodata a, country b where a.country=b.country
*
ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification
Right; inventing your own syntax usually leads to the outcome you experienced. Did you mean to use distinct?
SELECT DISTINCT a.city,
b.country_id
FROM airportdemodata a
JOIN country b ON a.country = b.country;

How to check if hotel room is booked using Oracle SQL constraints

I am an absolute beginner when it comes to SQL, and I'm trying to figure out how to do a simple integrity check. I am modelling a hotel registration system with four tables: Hotel, Room, Booking and Guest. What I'm interested in is in the booking table, which has attributes hotelNo, guestNo, dateFrom, dateTo, roomNo, where the first three are a composite primary key. Now then, the issue at hand is that under the current system, two people might have the same room booked at the same time, which would obviously be a problem in real life. I imagine that a solution might start to look something like
CREATE TABLE Booking(
-- All the attribute definitions go here...
CONSTRAINT OneGuestAtATime
CHECK (NOT EXISTS(SELECT(dateFrom FROM Booking ...))) -- I become unsure of what to do around here
);
Bear in mind that while I am a computer engineering student, I've never done SQL before, so a hand-holdy walk-through would be appreciated :)
Edit: I think my problem would be solved by a constraint to the effect of
CASE 1 (Overlap): "If the dateFrom or dateTo attributes of the record I am trying to insert falls between the dateFrom and dateTo attributes of a given record previously in the table, reject this insertion since there is some overlap between the two bookings."
CASE 2 (Superset): "If I am attempting to insert record X and there is a record already in the table named Y such that its Y.dateFrom > X.dateFrom and Y.dateTo < X.dateTo, then X is a superset of Y and should be rejected."
I'm not sure of how to translate that to SQL, though.
Edit 2: The tables
CREATE TABLE Hotel (
hotelNo NUMBER NOT NULL,
hotelName VARCHAR2(1024) NOT NULL,
city VARCHAR2(1024) NOT NULL,
--
PRIMARY KEY (hotelNo)
);
CREATE TABLE Room (
roomNo NUMBER(4,0) NOT NULL,
hotelNo NUMBER(5,0) NOT NULL,
type VARCHAR2(1024),
price NUMBER(6,2) NOT NULL,
--
PRIMARY KEY (roomNo, hotelNo),
FOREIGN KEY (hotelNo) REFERENCES Hotel
);
CREATE TABLE Guest(
guestNo NUMBER(8,0) NOT NULL,
guestName VARCHAR(1024) NOT NULL,
guestAddress VARCHAR(1024) NOT NULL,
--
PRIMARY KEY (guestNo)
);
CREATE TABLE Booking(
hotelNo NUMBER(8,0) NOT NULL,
guestNo NUMBER(8,0) NOT NULL,
dateFrom DATE NOT NULL,
dateTo DATE NOT NULL,
roomNo NUMBER(4,0) NOT NULL,
--
PRIMARY KEY (hotelNo, guestNo, dateFrom),
FOREIGN KEY (hotelNo) REFERENCES Hotel,
FOREIGN KEY (guestNo) REFERENCES Guest,
FOREIGN KEY (hotelNo, roomNo) REFERENCES Room(hotelNo, roomNo),
--
CONSTRAINT DateIntegrity
CHECK (dateFrom < dateTo)
In your model, the HOTEL and GUEST tables are okay (they may need some more columns later, but that's not a problem). For ROOM, you have decided to use a composite PK. However, a single column as ID would suffice. In BOOKING, the foreign key referencing HOTEL is redundant. Guests are booking rooms (which have a unique ID, and are tied to a HOTEL already) at certain days.
It may help (your learning) to auto-generate the IDs, and define different "start" values for them - when querying the tables at a later stage, you will recognise the IDs immediately eg HOTELs
could have 1000+, rooms could have 2000+ etc (see DDL code below).
When following the link provided by #Abra, you have seen that triggers etc can be used for solving the problem. The solution below is inspired by this answer (also mentioned as "option 4" here), and uses the idea of breaking down bookings into days ("slots"), which can then be used for unique (or PK) constraints. Please read the comments, as they contain more explanations.
DDL code
create table hotels (
id number generated always as identity start with 1000 primary key
, name_ varchar2( 100 )
) ;
create table rooms (
id number generated always as identity start with 2000 primary key
, name_ varchar2( 100 )
, hotelid number references hotels( id )
) ;
create table guests (
id number generated always as identity start with 3000 primary key
, last_name varchar2( 100 )
) ;
-- additional table, populated 500 days "into the future"
-- (no bookings _before_ the sysdate) due to FK constraint in bookings
create table days ( slot primary key )
as
select trunc( sysdate ) + level
from dual
connect by level <= 500 ; -- Oracle only!
create table bookings (
roomid number references rooms( id )
, slot date references days( slot ) not null
, guestid number references guests( id ) not null
, constraint bookings_pk primary key( roomid, slot )
) ;
Model
Populate HOTELS, ROOMS, GUESTS
-- For populating HOTELS, ROOMS, and GUESTS, we are just using a little PL/SQL script.
-- You can also use single INSERTs.
begin
-- insert one hotel
insert into hotels ( name_ ) values ( 'Tiny Hotel' ) ;
-- insert 8 rooms
for r in 1 .. 8
loop
insert into rooms( name_, hotelid ) values ( 'room_' || to_char( r ), 1000 ) ;
end loop ;
-- insert 9 guests
for g in 1 .. 9
loop
insert into guests( last_name ) values ( 'guest_' || to_char( g ) ) ;
end loop ;
commit ;
end ;
/
Data in HOTELS, ROOMS, GUESTS
SQL> select * from hotels ;
ID NAME_
1000 Tiny Hotel
SQL> select * from rooms ;
ID NAME_ HOTELID
2000 room_1 1000
2001 room_2 1000
2002 room_3 1000
2003 room_4 1000
2004 room_5 1000
2005 room_6 1000
2006 room_7 1000
2007 room_8 1000
SQL> select * from guests ;
ID LAST_NAME
3000 guest_1
3001 guest_2
3002 guest_3
3003 guest_4
3004 guest_5
3005 guest_6
3006 guest_7
3007 guest_8
3008 guest_9
Testing
-- tests for bookings - unique (roomid, slot)
-- guest 3000 books room 2000, 2 days
-- these 2 inserts must succeed
insert into bookings ( roomid, guestid, slot )
values ( 2000, 3000, date '2020-10-10' ) ;
insert into bookings ( roomid, guestid, slot )
values ( 2000, 3000, date '2020-10-10' + 1 ) ; -- + 1 here could be + i in a loop ...
-- INSERT must fail - guest 3000 cannot book room 2000 twice (on the same day)
insert into bookings ( roomid, guestid, slot )
values ( 2000, 3000, date '2020-10-10' ) ;
--ERROR at line 1:
--ORA-00001: unique constraint (...BOOKINGS_PK) violated
-- this INSERT must fail
-- guest 3001 cannot have room 2000 on the same day as guest 3000
insert into bookings ( roomid, guestid, slot )
values ( 2000, 3001, date '2020-10-10' + 1 ) ;
--ERROR at line 1:
--ORA-00001: unique constraint (...BOOKINGS_PK) violated
-- guest 3001 can have a different room at the same date, though
-- this insert must succeed
insert into bookings ( roomid, guestid, slot )
values ( 2001, 3001, date '2020-10-10' + 1 ) ;
-- 1 row created.
You can insert more dates for testing by using PL/SQL and coding a loop: see dbfiddle.
Simple query
-- all current bookings
select
H.name_
, R.name_
, G.last_name
, B.slot
from hotels H
join rooms R on H.id = R.hotelid
join bookings B on R.id = B.roomid
join guests G on G.id = B.guestid
;
-- result
NAME_ NAME_ LAST_NAME SLOT
Tiny Hotel room_1 guest_1 10-OCT-20
Tiny Hotel room_1 guest_1 11-OCT-20
Tiny Hotel room_1 guest_4 01-DEC-20
Tiny Hotel room_1 guest_4 02-DEC-20
Tiny Hotel room_1 guest_4 03-DEC-20
...
Query with more test data (INSERTs: see dbfiddle)
-- query that returns
-- all current bookings with nights_booked etc
-- CAUTION: for recurring bookings (same ROOM and GUEST but different slots)
-- this query will give us misleading results
select
H.name_
, R.name_
, G.last_name
, count( B.slot) nights_booked
, min( B.slot ) arrival_date
, max( B.slot ) + 1 departure_date
from hotels H
join rooms R on H.id = R.hotelid
join bookings B on R.id = B.roomid
join guests G on G.id = B.guestid
group by H.name_, R.name_, G.last_name
;
-- result
NAME_ NAME_ LAST_NAME NIGHTS_BOOKED ARRIVAL_DATE DEPARTURE_DATE
Tiny Hotel room_1 guest_1 2 10-OCT-20 12-OCT-20
Tiny Hotel room_1 guest_4 21 01-DEC-20 22-DEC-20
Tiny Hotel room_2 guest_2 1 11-OCT-20 12-OCT-20
As your question was more about modelling and constraints, the queries will probably need more work.

Problem in querying specific data based on values present in different rows of the same column

I am creating a database of a college and the tables are given as:
create table depts(
deptcode char(3) primary key,
deptname char(70) not null);
create table students(
rollno number(2) primary key,
name char(50),
bdate date check(bdate < TO_DATE('2004-01-01','YYYY-MM-DD')),
deptcode char(3) references depts(deptcode)
on delete cascade,
hostel number check(hostel<20),
parent_inc number(8,1));
create table faculty(
fac_code char(2) primary key,
fac_name char(50) not null,
fac_dept char(3) references depts(deptcode)
on delete cascade);
//for courses offered by the college
create table crs_offrd(
crs_code char(5) primary key,
crs_name char(35) not null,
crs_credits number(2,1),
crs_fac_cd char(2) references faculty(fac_code)
on delete cascade);
// for course registered by students*
create table crs_regd(
crs_rollno number(2) references students(rollno),
crs_cd char(5) references crs_offrd(crs_code)
on delete cascade,
marks number(5,2),
primary key(crs_rollno,crs_cd));
I am trying to find out name , subject and marks of students who have marks more than rollno 92005102 for course CS103 and CS106.
I believe the table should look like this:
Name Subject Marks
XYZ CS103 92
XYZ CS106 95
I am confused how to check for marks in both CS103 and CS106 at the same time as the marks for two subjects are present in the same column in a different row, and query processes one row at a time.
I followed the question posted here but it works only for a column and it would list values separated by , but I need to get the corresponding subject name in which that marks was obtained.
If any other information required, please comment.
There are probably many ways to achieve this. One is this:
select
crs_rollno,
max(case when crs_cd = 'CS103' then marks end) as marks103,
max(case when crs_cd = 'CS106' then marks end) as marks106
from crs_regd cr
where crs_cd in ('CS103', 'CS106')
and marks >
(
select marks
from crs_regd cr92005102
where cr92005102.crs_rollno = 92005102 -- student 92005102
and cr92005102.crs_cd = cr.crs_cd -- same class
)
group by crs_rollno
having count(*) = 2 /* both courses better than 92005102 */ ;
You can join to the students table to get their name.
Just for the fun of it another approach:
with cs103 as (select * from crs_regd where crs_cd = 'CS103')
, cs106 as (select * from crs_regd where crs_cd = 'CS106')
select crs_rollno, cs103.marks as cs103_marks, cs106.marks as cs106_marks
from cs103 join cs106 using (crs_rollno)
where cs103.marks > (select marks from cs103 where crs_rollno = 92005102)
and cs106.marks > (select marks from cs106 where crs_rollno = 92005102);
You can use group by with having as following:
-- CTE IS USED TO FETCH THE REQUIRED DATA FROM TABLE USING JOINS
WITH CTE AS (
SELECT
S.NAME,
CO.CRS_NAME,
CO.CRS_CODE,
CR.MARKS,
S.ROLLNO
FROM
STUDENTS S
JOIN CRS_REGD CR ON ( S.ROLLNO = CR.CRS_ROLLNO )
JOIN CRS_OFFRD CO ON ( CR.CRS_CD = CO.CRS_CODE )
WHERE
CO.CRS_NAME IN (
'CS103',
'CS106'
)
)
-- ACTUAL LOGIC START FROM HERE
SELECT
S.NAME,
CO.CRS_NAME,
CR.MARKS
FROM
CTE C3
WHERE
C3.ROLLNO IN (
SELECT
C2.ROLLNO
FROM
CTE C1
JOIN CTE C2 ON ( C1.ROLLNO = 92005102
AND C1.ROLLNO <> C2.ROLLNO
AND C1.CRS_CODE = C2.CRS_CODE )
GROUP BY
C2.ROLLNO
HAVING ( MAX(CASE
WHEN C1.CRS_NAME = 'CS103'
AND C2.MARKS >= C1.MARKS THEN 1
END) = 1
AND MAX(CASE
WHEN C1.CRS_NAME = 'CS106'
AND C2.MARKS >= C1.MARKS THEN 1
END) = 1 )
);
Cheers!!

How to extract data from two tables using SQL object relational statement in oracle 11g

I am trying to solve the following query using object relational approach but don't know what is the proper way.
Find the number of saving accounts at each branch, displaying the
number and branch’s address.
I created two tables and insert some data like this:
--THIS IS BRANCH TABLE:
create type Branch_Address as object(
street varchar2(20),
city varchar2(20),
p_code varchar2(10))
not final
/
create type Branch_Phone as object(
phone varchar2(20))
not final;
/
create type branch_type as object(
bID varchar2(10),
bAddress Branch_Address,
bPhone Branch_Phone)
/
create table branch of branch_type(
primary key (bID))
/
insert into branch values(
'901',Branch_Address('Nic Street','Jordan','ABH887A'),Branch_Phone('0335454888'));
/
insert into branch values(
'906',Branch_Address('East End Garden','California','L181QP'),Branch_Phone('07455668711'));
/
insert into branch values(
'912',Branch_Address('Fredrick Street','London','LA112AS'),Branch_Phone('02841124478'));
/
insert into branch values(
'924',Branch_Address('West Street','Cambridge','CA8L871'),Branch_Phone('04511477885'));
--This is account table
create type account_type as object(
accNum int,
accType varchar2(15),
balance number,
bID ref branch_type,
inRate number,
limitOfFreeOD number,
openDate DATE)
/
create table account_table of account_type(
primary key (accNum))
/
insert into account_table
select account_type('1001','current','820.50',ref(b),'0.005','800','01-May-11')
from branch b
where b.bID = '901';
/
insert into account_table
select account_type('1010','saving','2155',ref(b),'0.02','0','08-Mar-10')
from branch b
where b.BID = '906';
/
insert into account_table
select account_type('1002','current','2600',ref(b),'0.005','1000','10-Apr-13')
from branch b
where b.BID = '912';
/
insert into account_table
select account_type('1112','saving','24000',ref(b),'0','1700','16-Jun-16')
from branch b
where b.BID = '924';
/
Branch(bID, street, city, p_code, bPhone)
Account(accNum, accType, balance, bID, inRate, limitOfFreeOD, openDate)
Bold is primary key
Italic is foreign key (In object relational we don't use Join if I am right).
Any help? Thanks.
Here's how you can join those:
select b.bid,
(select count(1) from account_table a
where a.bid.bid = b.bid
and a.acctype='saving'
) as num_accounts,
b.baddress.street, b.baddress.city, b.baddress.p_code
from branch b;
This is more confusing because branch_type.bID is a varchar2(10) and account_type.bID is a ref branch_type. I would rename bID on account_type to something like accBranch to be more clear that it does not store the ID, but is a reference to the entire object.
I haven't tested this query, but you can also write this as a join (below) instead of a subquery (above). They each have their uses.
select b.bid, count(1) as num_accounts,
b.baddress.street, b.baddress.city, b.baddress.p_code
from branch b
join account_table a
on a.bid.bid = b.bid
and a.acctype='saving'
group by b.bid, b.baddress.street, b.baddress.city, b.baddress.p_code;