How to write case expression while pivoting the data - sql

CREATE TABLE details_1 (
e_id NUMBER(10),
e_name VARCHAR2(30),
CONSTRAINT pk_details_1_e_id PRIMARY KEY ( e_id )
);
insert into details_1 values(11,'A');
CREATE TABLE ques_ans (
ques_ans_id NUMBER(10),
ref_ques_id NUMBER(10),
ref_ans_id NUMBER(10),
ref_ans_value VARCHAR2(100),
e_id NUMBER(10),
CONSTRAINT pk_ques_ans PRIMARY KEY ( ques_ans_id ),
CONSTRAINT fk_ques_ans FOREIGN KEY ( e_id )
REFERENCES details_1 ( e_id ),
constraint fk_ques_and_ques_id foreign key(ref_ques_id)
references ques_ref (ques_id)
);
insert into ques_ans values(1,3,1,11,null);
insert into ques_ans values(2,2,2,11,null);
insert into ques_ans values(3,4,1,11,null);
insert into ques_ans values(4,23,1,11,11);
CREATE TABLE ques_ref (
ques_id NUMBER(10),
code VARCHAR2(50),
code_label VARCHAR2(100),
constraint pk_ques_ref primary key(ques_id)
);
insert into ques_ref values(3,'changes_exist','Any known changes');
insert into ques_ref values(2,'E_Clubbed','E_id clubbed with other');
insert into ques_ref values(4,'E_impacted','E impacted by other');
insert into ques_ref values(23,'E_Clubbed_with_other','E clubbed with other E');
CREATE TABLE ans_ref (
ref_ans_id NUMBER(10),
code VARCHAR2(10),
code_value VARCHAR2(30)
);
insert into ans_ref values(1,'R_Yes','Yes');
insert into ans_ref values(2,'R_No','No');
commit;
My Attempt :
select d.e_id,
max(case qa.ref_ques_id when 3 then ar.code_value end) changes_exist,
max(case qa.ref_ques_id when 2 then ar.code_value end) E_Clubbed,
max(case qa.ref_ques_id when 4 then ar.code_value end) E_impacted,
--need to write case expression here
from details_1 d
join
ques_ans qa
on d.e_id = qa.e_id
join ans_ref ar
on ar.ref_ans_id = qa.ref_ans_id
group by d.e_id
I got stuck in the below requirement:
I need to check if ref_ques_id of ques_ans table is 23 then it should display ref_ans_value from the same table i.e ques_ans
For example:
In the table ques_ans for ques_ans_id 4 ref_ques_id is 23 then in this case it will display ref_ans_value i.e 11 in the column ref_ans_value
How can I write case expressions while pivoting the data? I am wondering if we can do it using case expression or is there any other way to achieve this?
Expected Output:
+------+---------------+-----------+------------+------------------+
| E_ID | CHANGES_EXIST | E_CLUBBED | E_IMPACTED | E_CLUBBED_WITH_E |
+------+---------------+-----------+------------+------------------+
| 11 | Yes | No | Yes | 11 |
+------+---------------+-----------+------------+------------------+

You can achieve that with further aggregations.
CASE
WHEN MAX(ques_ans.ref_ques_id) = '23' THEN MAX(COALESCE(ques_ans.ref_ans_value, 0))
ELSE -4
END
There are two problems you will need to sort out:
1
You artificially aggregated things in the SELECT clause to prevent technical problems, but you might need to change this, depending on how your actual data looks alike (not the one shown in the question, but in reality). If you have answers with different references, then you might want to change your database structure to better reflect reality.
2
You did not tell us what should happen when the value of ques_id is NOT 23, so I added an ad-hoc value in the ELSE. You will need to change it to the one you actually need.

Related

How to join multiple tables and printing the result for same id which are populating more than one in a single row

CREATE TABLE details_1 (
e_id NUMBER(10),
e_name VARCHAR2(30),
CONSTRAINT pk_details_1_e_id PRIMARY KEY ( e_id )
);
insert into details_1 values(11,'A');
CREATE TABLE ques_ans (
ques_ans_id NUMBER(10),
ref_ques_id NUMBER(10),
ref_ans_id NUMBER(10),
e_id NUMBER(10),
CONSTRAINT pk_ques_ans PRIMARY KEY ( ques_ans_id ),
CONSTRAINT fk_ques_ans FOREIGN KEY ( e_id )
REFERENCES details_1 ( e_id ),
constraint fk_ques_and_ques_id foreign key(ref_ques_id)
references ques_ref (ques_id)
);
insert into ques_ans values(1,3,1,11);
insert into ques_ans values(2,2,2,11);
insert into ques_ans values(3,4,1,11);
CREATE TABLE ques_ref (
ques_id NUMBER(10),
code VARCHAR2(50),
code_label VARCHAR2(100),
constraint pk_ques_ref primary key(ques_id)
);
insert into ques_ref values(3,'changes_exist','Any known changes');
insert into ques_ref values(2,'E_Clubbed','E_id clubbed with other');
insert into ques_ref values(4,'E_impacted','E impacted by other');
CREATE TABLE ans_ref (
ref_ans_id NUMBER(10),
code VARCHAR2(10),
code_value VARCHAR2(30)
);
insert into ans_ref values(1,'R_Yes','Yes');
insert into ans_ref values(2,'R_No','No');
The problem facing in joining the tables :
Table ques_ans has ref_ques_id column that is being populated from ques_ref table.
So, If ref_ques_id = 3 and ref_ans_id = 1 then it should display 'Yes' i.e populating from ans_ref table. Likewise for ref_ques_id = 2 then it should display 'No' and same for ref_ques_id = 4.
My Attempt :
select d.e_id, qa.ref_ques_id,
ar.code_value
from details_1 d
join ques_ans qa on(d.e_id = qa.e_id)
join ans_ref ar on(ar.ref_ans_id = qa.ref_ans_id) ;
In my attempt, I am getting 3 rows but ideally expected output should be like attached in the screenshot.
Columne e_id : Coming from details_1 table
Column Changes_exist: Validation in the ques_ans table ref_ques_id column and based on the ref_ans_id printing Yes or no.
Column E_clubbed: Validation in the ques_ans table ref_ques_id column and based on the ref_ans_id printing Yes or no.
Column E_Impacted: Validation in the ques_ans table ref_ques_id column and based on the ref_ans_id printing Yes or no.
The output should be like the attached screenshot but I got stuck that how this can be printed in a single row
Tool: SQL Developer
Version: 20.4
Code you wrote almost does it; there's another join missing (with ques_ref) so that you'd extract code name, but that still doesn't "fix" it because - as far as I can tell - there's no way to do it "dynamically" within SQL. What we usually do is to pivot data. One option is to use an aggregate function (max in this example) along with case expression, and to literally name every column (using code name you've previously extracted, but just to be of some help while writing column's alias).
SQL> with temp as
2 (select d.e_id, qa.ref_ques_id, qr.code, ar.code_value
3 from details_1 d
4 join ques_ans qa on d.e_id = qa.e_id
5 join ans_ref ar on ar.ref_ans_id = qa.ref_ans_id
6 join ques_ref qr on qr.ques_id = qa.ref_ques_id
7 )
8 select e_id,
9 max(case when ref_ques_id = 3 then code_value end) changes_exist,
10 max(case when ref_ques_id = 2 then code_value end) e_clubbed,
11 max(case when ref_ques_id = 4 then code_value end) e_impacted
12 from temp
13 group by e_id;
E_ID CHANGES_EXIST E_CLUBBED E_IMPACTED
---------- --------------- --------------- ---------------
11 Yes No Yes
SQL>

How to write case expression inside merge statement to print the desired result

CREATE TABLE DELIGATE_DETAILS_MAIN
( E_ID NUMBER(10,0),
COMPLETED_DATE TIMESTAMP (6),
CONSTRAINT PK_DELIGATE_DETAILS_MAIN PRIMARY KEY (E_ID));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (1,to_timestamp('13-12-21 6:05:23.991000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (2,to_timestamp('13-12-21 6:05:24.019000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (3,to_timestamp('13-12-21 6:05:24.029000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (4,to_timestamp('13-12-21 10:46:00.015000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
CREATE TABLE CONTROL_MAIN
( E_ID NUMBER(10,0),
E_SPEC VARCHAR2(30 BYTE),
CONSTRAINT PK_CONTROL_MAIN PRIMARY KEY (E_ID));
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (1,'SAP1');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (2,'FSAP');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (3,'SAP2');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (4,'SAP1-480');
CREATE TABLE QUESTION
( E_ID NUMBER(10,0),
QUEST VARCHAR2(30 BYTE),
CONSTRAINT PK_QUESTION PRIMARY KEY (E_ID));
Insert into QUESTION (E_ID,QUEST) values (1,'Yes');
Insert into QUESTION (E_ID,QUEST) values (2,'No');
Insert into QUESTION (E_ID,QUEST) values (3,'Yes');
Insert into QUESTION (E_ID,QUEST) values (4,'Yes');
CREATE TABLE DELIGATE_DETAILS_TRANS
( D_ID NUMBER(10,0),
E_ID NUMBER(10,0),
COMPLETED_DATE_TRANS DATE,
OWNER_DETAIL VARCHAR2(30 BYTE),
CONSTRAINT PK_DELIGATE_DETAILS_TRANS PRIMARY KEY (D_ID),
CONSTRAINT FK_E_ID FOREIGN KEY (E_ID)
REFERENCES TAM.DELIGATE_DETAILS_MAIN (E_ID));
Attempt:
MERGE INTO deligate_details_trans t USING ( SELECT
ddm.e_id,
ddm.completed_date
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE %'SAP'% )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
on (t.e_id = s.e_id)
when not matched then
insert (d_id,e_id, completed_date_trans, owner_detail)
values (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
--Here need to insert owner detail from control main
--If it is SAP1 or SAP2 then it will insert SAP1 or SAP2
--If it is SAP1-480 then it should insert SAP3);
Expected output:
+------+---------+-----------------------+--------------+
| D_ID | E_ID | COMPLETED_DATE_TRANS | OWNER_DETAIL |
+------+---------+-----------------------+--------------+
| 1 | 1 | 13-12-21 | SAP1 |
| 2 | 3 | 13-12-21 | SAP2 |
| 3 | 4 | 13-12-21 | SAP3 |
+------+---------+-----------------------+--------------+
For e_id 1: Based on the join condition from control_main and question table. Data should get inserted into the deligate_details_trans table and owner detail should be SAP1.
For e_id 2: Based on the join condition from control_main and question table. Data is NOT matching so it should not get inserted into the trans table.
For e_id 3: Based on the join condition from control_main and question table. Data should get inserted into the deligate_details_trans table and owner detail should be SAP2.
For e_id 4 it should check in the control main table and if it is SAP1-480 then it should insert SAP3 and for others, corresponding owner details should be inserted from the control_main table
If you want the case expression in the insert's values clause then you need to expose the control table value in the using clause:
MERGE INTO deligate_details_trans t
USING (
SELECT
ddm.e_id,
ddm.completed_date,
cm.e_spec
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE '%SAP%' )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
ON (t.e_id = s.e_id)
WHEN NOT MATCHED THEN INSERT (
d_id,e_id, completed_date_trans, owner_detail
)
VALUES (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
CASE s.e_spec
WHEN 'SAP1' THEN 'SAP1'
WHEN 'SAP2' THEN 'SAP2'
WHEN 'SAP1-480' THEN 'SAP3'
END
);
Alternatively move the case expression into the using clause, give it an alias, and refer to that alias in the values clause:
MERGE INTO deligate_details_trans t
USING (
SELECT
ddm.e_id,
ddm.completed_date,
CASE cm.e_spec
WHEN 'SAP1' THEN 'SAP1'
WHEN 'SAP2' THEN 'SAP2'
WHEN 'SAP1-480' THEN 'SAP3'
END AS owner_detail
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE '%SAP%' )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
ON (t.e_id = s.e_id)
WHEN NOT MATCHED THEN INSERT (
d_id,e_id, completed_date_trans, owner_detail
)
VALUES (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
s.owner_detail
);
db<>fiddle showing both (with %'SAP'% changed to '%SAP%', and sequence creation added).
I'm not sure why you have a timestamp in one table and a date in the other, and you don't need an explicit cast (though it doesn't hurt). But if you're doing that because you're only interested in the date part you should be aware that an Oracle date still has a time component, even if it isn't being shown in your DD-MM-YY format or db<>fiddle's default DD-MON-YY format. If you want to 'lose' the time part you can truncate the value at the day (DD) component, shown in this db<>fiddle which changes the display format so you can see the difference. But you might want to keep the time - in which case, ignore this part...

Create combination sql table

I'm trying to create a sql table in data base in VS that has room and userid column, but the sql will only accept your input if the userid exists in users table and room exists in rooms tables
Allows:
Users table:
Userid
1
2
3
RoomUsers table:
Room ----- User
1 1
2. 1
1. 2
1. 3
2. 3
Won't allow:
Users table:
Userid
1
2
RoomUsers table:
Room ----- User
1 4
Normal foreign key wont work because it only allows one of each index and not multiple, how can I allow what I need to occur,to happen?
(This would be a mess in comments)
Probably we are having an XY problem here. The thing you describe is simply solved with a foreign key. ie:
CREATE TABLE users (id INT IDENTITY NOT NULL PRIMARY KEY, ad VARCHAR(100));
CREATE TABLE rooms (id INT IDENTITY NOT NULL PRIMARY KEY, ad VARCHAR(100));
CREATE TABLE room_user
(
RoomId INT NOT NULL
, UserId INT NOT NULL
, CONSTRAINT PK_roomuser
PRIMARY KEY(RoomId, UserId)
, CONSTRAINT fk_room
FOREIGN KEY(RoomId)
REFERENCES dbo.rooms(id)
, CONSTRAINT fk_user
FOREIGN KEY(UserId)
REFERENCES dbo.users(id)
);
INSERT INTO dbo.users(ad)
OUTPUT
Inserted.id, Inserted.ad
VALUES('RayBoy')
, ('John')
, ('Frank');
INSERT INTO dbo.rooms(ad)
OUTPUT
Inserted.id, Inserted.ad
VALUES('Room1')
, ('Room2')
, ('Room3');
INSERT INTO dbo.room_user(RoomId, UserId)VALUES(1, 1), (1, 2), (2, 3);
-- won't allow
INSERT INTO dbo.room_user(RoomId, UserId)VALUES(999, 888);

SQL Server constraint to check other columns

Product: SQL Server
Is it possible to write a constraint, that checks the values of other columns? In my specific case I will give you an example:
Let's imagine I have a table with 5 Columns:
Name | Hobby1 | Hobby2 | Hobby3 | Hobby4
Lets say there are the following values in it:
John Doe| fishing | reading| swimming| jogging
What I try to reach is the following:
If someone trys to Insert : John Doe, fishing,reading
It should be blocked, cause I don't want the same combination in the first 3 Columns.
Can I realise that with a constraint or do I need a Primary key combination for the first 3 columns?
Thanks for your replies.
Add unique constraint to your table for first three columns.
ALTER TABLE YourTableName
ADD CONSTRAINT UK_Name_Hobby1_Hobby2 UNIQUE (Name, Hobby1,Hobby2);
Well, you could do as #jarlh says (in comments) and ensure the hobby columns are ordered and this will satisfy your requirements:
CREATE TABLE Hobbies
(
Name VARCHAR(35) NOT NULL,
Hobby1 VARCHAR(35) NOT NULL,
Hobby2 VARCHAR(35) NOT NULL,
Hobby3 VARCHAR(35),
Hobby4 VARCHAR(35),
CHECK ( Hobby1 < Hobby2 ),
CHECK ( Hobby2 < Hobby3 ),
CHECK ( Hobby3 < Hobby4 ),
UNIQUE ( Name, Hobby1, Hobby2 ),
);
...However, this would allow the following which feels like it should be invalid data:
INSERT INTO Hobbies VALUES ( 'John Doe', 'fishing', 'jogging', 'reading', 'swimming' );
INSERT INTO Hobbies VALUES ( 'John Doe', 'jogging', 'reading', 'swimming', NULL );

Integrity constraint for tables not immediately related

I am a SQL beginner and I can't figure out how to properly create an integrity constraint for situations like this:
The schema describes a delivery system - each Restaurant offers some items, which can be delivered to customers (outside the visible schema).
The problem comes with the in_delivery table - items from menu are registered with the delivery through this table. With the current state of things, it is possible to add a menu_item to a delivery which is done by a restaurant, but that restaurant may not offer the menu_item!
When inserting into in_delivery, I need to somehow check, if the Menu_Item_MenuItem_ID is present in offers, that has Restaurant_RestaurantID equal to RestaurantID in Delivery associated with the table.
I don't know if I can use a foreign key here, because the tables are not "adjacent"..
What comes into mind is to have a RestaurantID in in_delivery, that would be a foreign key both to Restaurant and Delivery. Then I could find that in offers. Is there a better way?
Thanks for your help
You could enforce your constraint with the following changes:
add the restaurant_id column in the in_delivery table
add a unique constraint on delivery (delivery_id, restaurant_id) (needed for 3.)
change the foreign key from in_delivery -> delivery to point to (delivery_id, restaurant_id)
change the foreign key from in_delivery -> menu_item to in_delivery -> offers
Alternatively you can use a trigger to check the constraint:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Restaurants (
RestaurantID NUMBER(2) PRIMARY KEY,
Name VARCHAR2(30) NOT NULL
)
/
INSERT INTO Restaurants
SELECT 1, 'Soylent Green Express' FROM DUAL
UNION ALL SELECT 2, 'Helga''s House of Ribs' FROM DUAL
/
CREATE TABLE Menu_Items (
Menu_Item_ID NUMBER(2) PRIMARY KEY,
Name VARCHAR2(20) NOT NULL
)
/
INSERT INTO Menu_Items
SELECT 1, 'Soylent Green' FROM DUAL
UNION ALL SELECT 2, 'Ribs' FROM DUAL
/
CREATE TABLE Offers (
RestaurantID NUMBER(2),
Menu_Item_ID NUMBER(2),
PRIMARY KEY ( RestaurantID, Menu_Item_ID ),
FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID ),
FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
)
/
INSERT INTO Offers
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
/
CREATE TABLE Deliveries (
RestaurantID NUMBER(2) NOT NULL,
Delivery_ID NUMBER(2) PRIMARY KEY,
FOREIGN KEY ( RestaurantID ) REFERENCES Restaurants ( RestaurantID )
)
/
INSERT INTO Deliveries
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
/
CREATE TABLE in_delivery (
Delivery_ID NUMBER(2),
Menu_Item_ID NUMBER(2),
PRIMARY KEY ( Delivery_ID, Menu_Item_ID ),
FOREIGN KEY ( Delivery_ID ) REFERENCES Deliveries ( Delivery_ID ),
FOREIGN KEY ( Menu_Item_ID ) REFERENCES Menu_Items ( Menu_Item_ID )
)
/
Just for ease of reading I've created two useful functions (you would probably want some exception handling in them):
CREATE OR REPLACE FUNCTION get_Delivery_RestaurantID (
p_Delivery_ID Deliveries.Delivery_ID%TYPE
) RETURN Restaurants.RestaurantID%TYPE
AS
v_RestaurantID Restaurants.RestaurantID%TYPE;
BEGIN
SELECT RestaurantID
INTO v_RestaurantID
FROM Deliveries
WHERE Delivery_ID = p_Delivery_ID;
RETURN v_RestaurantID;
END get_Delivery_RestaurantID;
/
CREATE OR REPLACE FUNCTION does_Restaurant_Offer_Item (
p_RestaurantID Restaurants.RestaurantID%TYPE,
p_Menu_Item_ID Menu_Items.Menu_Item_ID%TYPE
) RETURN NUMBER
AS
v_exists NUMBER(1);
BEGIN
SELECT CASE WHEN EXISTS ( SELECT 1
FROM Offers
WHERE RestaurantID = p_RestaurantID
AND Menu_Item_ID = p_Menu_Item_ID
)
THEN 1
ELSE 0
END
INTO v_exists
FROM DUAL;
RETURN v_exists;
END does_Restaurant_Offer_Item;
/
Then just add a trigger to the table to check that the Restaurant offers the item and, if not, raise an exception.
CREATE TRIGGER check_Valid_Delivery_Item
BEFORE INSERT OR UPDATE OF Delivery_ID, Menu_Item_ID
ON in_delivery
FOR EACH ROW
BEGIN
IF does_restaurant_Offer_Item( get_Delivery_RestaurantID( :new.Delivery_ID ), :new.Menu_Item_ID ) = 0
THEN
RAISE_APPLICATION_ERROR (-20100, 'Invalid Delivery Item');
END IF;
END check_Valid_Delivery_Item;
/
INSERT INTO in_delivery VALUES( 1, 1 )
/
INSERT INTO in_delivery VALUES( 2, 2 )
/
Query 1:
SELECT * FROM in_delivery
Results:
| DELIVERY_ID | MENU_ITEM_ID |
|-------------|--------------|
| 1 | 1 |
| 2 | 2 |
If you try to do:
INSERT INTO in_delivery VALUES( 1, 2 );
Then you get:
ORA-20100: Invalid Delivery Item ORA-06512: at "USER_4_F9593.CHECK_VALID_DELIVERY_ITEM", line 4 ORA-04088: error during execution of trigger 'USER_4_F9593.CHECK_VALID_DELIVERY_ITEM' : INSERT INTO in_delivery VALUES( 1, 2 )