how to delete corresponding matching rows in Oracle SQL? - oracle

I'm trying to delete rows in a table only when there is a corresponding entry with a negative amount. The tricky part is there could be more positive than negative or more negative than positive.
value1 amt
12345 50
12345 50
12345 -50
12345 -50
abcde 40
abcde 40
abcde -40
11111 30
11111 -30
11111 -30
The result should be:
abcde 40
11111 -30

I have to apologize. I realized the posters data set was too simple. Here is a revised answer that I believe works.
Basically, you need to partition into pairs and then delete the pairs having sum() = 0.
create table t ( id varchar2(20), val number );
delete from t;
INSERT INTO t ( id, val ) values ( '12345', 50);
INSERT INTO t ( id, val ) values ( '12345', 50);
INSERT INTO t ( id, val ) values ( '12345', -50);
INSERT INTO t ( id, val ) values ( '12345', -50);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', 20);
INSERT INTO t ( id, val ) values ( 'abcde', 40);
INSERT INTO t ( id, val ) values ( 'abcde', -40);
INSERT INTO t ( id, val ) values ( '11111', 30);
INSERT INTO t ( id, val ) values ( '11111', -30);
INSERT INTO t ( id, val ) values ( '11111', -30);
INSERT INTO t ( id, val ) values ( 'aaaaa', 10);
INSERT INTO t ( id, val ) values ( 'aaaaa', -30);
COMMIT;
MERGE INTO t
USING (WITH value_partition AS
(SELECT t.*,
ROW_NUMBER () OVER (PARTITION BY t.id, t.val ORDER BY ROWID) rn_in_value
FROM t)
SELECT sp.ROWID row_id,
sp.*,
CASE WHEN SUM (sp.val) OVER (PARTITION BY sp.id, ABS (sp.val), rn_in_value) = 0 THEN 'N' ELSE 'Y' END
keep_row
FROM value_partition sp) u
ON (t.ROWID = u.row_id
AND u.keep_row = 'N')
WHEN MATCHED THEN
UPDATE SET t.val = u.val
DELETE
WHERE u.keep_row = 'N';
SELECT * FROM t;

Related

Oracle sql query to convert common rows to columns

I had a requirement to build a survey like application. Did this using Oracle APEX 18.2. I'm done designing the table structures and their relationships. I'm now on the part where I need to build a report (can be Interactive Report/Classic Report) where the columns will depend on what questions are available.
So basically, each question is supposed to have a column in the report. I can build the report and add these questions as column on design time. But I was wondering if there is a way to do this at runtime as well? More questions can be added in the future and I would like to report to be flexible enough to accommodate this. For now I would like to try via SQL statement.
Here's my table design for refence.
create table persons (
id number primary key
, name varchar2(100) not null
)
/
insert into persons values ( 1, 'Bruce' )
/
insert into persons values ( 2, 'Jennifer' )
/
create table questions (
id number primary key
, question varchar2(100) not null
)
/
insert into questions values ( 1, 'Gender' )
/
insert into questions values ( 2, 'Currently Employed' )
/
create table choices (
id number primary key
, choice varchar2(100) not null
)
/
insert into choices values ( 1, 'Male' )
/
insert into choices values ( 2, 'Female' )
/
insert into choices values ( 3, 'Yes' )
/
insert into choices values ( 4, 'No' )
/
create table question_choices (
id number primary key
, question_id number
, choice_id number
)
/
insert into question_choices values ( 1, 1, 1 )
/
insert into question_choices values ( 2, 1, 2 )
/
insert into question_choices values ( 3, 2, 3 )
/
insert into question_choices values ( 4, 2, 4 )
/
create table survey (
id number primary key
, name varchar2(100) not null
)
/
insert into survey values ( 1, 'Survey 1' )
/
create table survey_questions (
id number primary key
, survey_id number
, question_id number
)
/
insert into survey_questions values ( 1, 1, 1 )
/
insert into survey_questions values ( 2, 1, 2 )
/
create table survey_responses (
id number primary key
, survey_id number
, person_id number
, question_id number
, choice_id number
)
/
insert into survey_responses values ( 1, 1, 1, 1, 1 )
/
insert into survey_responses values ( 2, 1, 1, 2, 4 )
/
insert into survey_responses values ( 3, 1, 2, 1, 2 )
/
insert into survey_responses values ( 4, 1, 2, 2, 3 )
/
commit
/
I did a bit or research and found out about the pivot command. However, this approach also requires that the columns be defined at design time.
select *
from (
select s.name survey
, p.name respondent
, q.question
, c.choice
, row_number() over (partition by person_id, question order by choice ) rn
from persons p
, questions q
, choices c
, question_choices qc
, survey s
, survey_questions sq
, survey_responses sr
where p.id = sr.person_id
and q.id = sr.question_id
and c.id = qc.choice_id
and s.id = sq.survey_id
and sq.question_id = q.id
and sr.choice_id = c.id
)
pivot (max(question) question, max(choice) for question in ( 'Gender','Currently Employed' ))
Appreciate any comments/suggestions.

pl/sql statement Trigger

Suppose we have the following two tables:
OrderHeader(OrderID, Odate, CustID, Total)
Order_Item(OrderID,ItemID, Qty, Subtotal)
Write a statement-level trigger that updates the Total in the orderHeader table with
the total value of the order_item records whenever an insert, update or delete event
occurs on the order_item table. For any update error, raise an exception.
So far I have written this:
create or replace TRIGGER SECURE_ORDER
AFTER INSERT OR DELETE OR UPDATE ON Order_Item
declare
sum1 Number;
BEGIN
SELECT SUM(Qty*Price) Into sum1 FROM Order_Item Inner join Item Using(ItemID) WHERE OrderID=:OLD.OrderID;
UPDATE OrderHeader set Total=sum1 where OrderID=:OLD.OrderID;
EXCEPTION
WHEN too_many_rows then
dbms_output.put_line('Too many rows');
WHEN no_data_found then
dbms_output.put_line('No Data Found');
WHEN others then
dbms_output.put_line('other error');
END;
It is not correct however as the question statement says I have to write a statement level trigger which doesn't give me acces to NEW and OLD keyword. I don't know how to go about doing this in a statement level trigger. Any help would be appreciated.
You can just use a single MERGE statement:
CREATE TRIGGER SECURE_ORDER
AFTER INSERT OR DELETE OR UPDATE ON Order_Item
BEGIN
MERGE INTO OrderHeader dst
USING (
SELECT COALESCE( oh.OrderID, o.OrderID ) AS OrderID,
COALESCE( SUM( o.Qty*i.Price ), 0 ) AS total
FROM OrderHeader oh
FULL OUTER JOIN Order_Item o
ON oh.OrderID = o.OrderID
LEFT OUTER JOIN Item i
ON ( o.ItemID = i.ItemID )
GROUP BY COALESCE( oh.OrderID, o.OrderID )
) src
ON ( src.OrderID = dst.OrderID )
WHEN MATCHED THEN
UPDATE SET total = src.total
WHEN NOT MATCHED THEN
INSERT ( OrderID, Total ) VALUES ( src.OrderID, src.Total );
END;
/
Then, if you have the tables:
CREATE TABLE Item (
ItemID NUMBER(10,0)
GENERATED ALWAYS AS IDENTITY
CONSTRAINT Item__ItemID__PK PRIMARY KEY,
Name VARCHAR2(20)
NOT NULL,
Price NUMBER(12,2)
NOT NULL
CONSTRAINT Item__Price_GTE_0__CHK CHECK ( Price >= 0 )
);
CREATE TABLE Order_Item (
OrderID INT
-- CONSTRAINT Order_Item__OrderID__FK REFERENCES Orders ( OrderID )
NOT NULL,
Qty INT
NOT NULL
CONSTRAINT Order_Item__Qty_GT_0__CHK CHECK ( Qty > 0 ),
ItemID INT
NOT NULL
CONSTRAINT Order_Item__ItemID__FK REFERENCES Item ( ItemId ),
CONSTRAINT Order_Item__OID__IID__PK PRIMARY KEY ( OrderID, ItemID )
);
CREATE TABLE OrderHeader (
OrderID INT
NOT NULL,
total NUMBER(14,2)
NOT NULL
);
Then if you:
INSERT INTO Item ( Name, Price ) VALUES ( 'Item 001', 1.00 );
INSERT INTO Item ( Name, Price ) VALUES ( 'Item 002', 2.00 );
INSERT INTO Item ( Name, Price ) VALUES ( 'Item 003', 2.50 );
INSERT INTO Order_Item ( OrderID, ItemID, Qty ) VALUES ( 1, 1, 3 );
INSERT INTO Order_Item ( OrderID, ItemID, Qty ) VALUES ( 1, 2, 1 );
INSERT INTO Order_Item ( OrderID, ItemID, Qty ) VALUES ( 1, 3, 2 );
The OrderHeader table contains:
SELECT * FROM OrderHeader;
ORDERID | TOTAL
------: | ----:
1 | 10
Then if you do:
UPDATE Order_Item
SET Qty = 8
WHERE OrderID = 1
AND ItemID = 1;
The OrderHeader table contains:
SELECT * FROM OrderHeader;
ORDERID | TOTAL
------: | ----:
1 | 15
Then if you do:
DELETE FROM Order_Item
WHERE ( OrderID, ItemID ) IN ( (1,2), (1,3) );
The OrderHeader table contains:
SELECT * FROM OrderHeader;
ORDERID | TOTAL
------: | ----:
1 | 8
And, finally, if you do:
DELETE FROM Order_Item;
The OrderHeader table contains:
SELECT * FROM OrderHeader;
ORDERID | TOTAL
------: | ----:
1 | 0
db<>fiddle here

How to distinctly count customer IDs that visited a store in a given quarter only if the same customer ID visited in the previous quarter

I have a table
+-----------+----------+--------+-------+---------+
|Customer ID|Visit Date|Category|Product|Served by|
+-----------+----------+--------+-------+---------+
|1001 |03/17/2019|A |P11 |Jone Doe |
|1003 |03/17/2019|D |P12 |Jone Doe |
|1006 |03/15/2019|C |P13 |Jone Doe |
|1009 |03/10/2019|G |P14 |Jone Doe |
|1011 |12/12/2018|H |P15 |Foo Bar |
|1003 |11/11/2018|D |P16 |Foo Bar |
|1006 |09/10/2018|C |P17 |Foo Bar |
|1009 |10/10/2018|G |P18 |Foo Bar |
+-----------+----------+--------+-------+---------+
there are 4 customers but only 2 (1003 and 1009) visited in the previous quarter.
I used DATESINPERIOD but it counts all distinctly between the preceding quarters (I have a designated date table).
1st approach
customers_count =
CALCULATE (
DISTINCTCOUNT[Customer ID],
DATESINPERIOD (
'Calendar'[Date],
ENDOFQUARTER ( 'Calendar'[Date] ),
-2,
QUARTER
)
)
2nd approach
customers_count 2Q =
VAR customers_count_1 =
DISTINCT ( FILTER ( VALUES ( Orders[Customer ID] ) ) )
VAR customers_count_2 =
CALCULATETABLE (
DISTINCT ( FILTER ( VALUES ( Orders[Customer ID] ) ) ),
DATEADD ( 'Calendar'[Date], -1, QUARTER )
)
RETURN
COUNTROWS ( INTERSECT ( customers_count_1, customers_count_2 ) )
The expected count is 2 for the last quarter.
Your second approach looks reasonable. Try it without DISTINCT and FILTER.
customers_count 2Q =
VAR customers_count_1 =
VALUES ( Orders[Customer ID] )
VAR customers_count_2 =
CALCULATETABLE (
VALUES ( Orders[Customer ID] ),
DATEADD ( 'Calendar'[Date], -1, QUARTER )
)
RETURN
COUNTROWS ( INTERSECT ( customers_count_1, customers_count_2 ) )
The VALUES function returns a list of distinct values of its column argument that are within its filter context.

LINQ Left Join on Max Date

Ok so here are my tables in the database:
CREATE DATABASE Temp
GO --------------------------
USE Temp
GO --------------------------
CREATE TABLE Table1
(
Table1Id INT IDENTITY(1, 1) ,
Name VARCHAR(20) ,
CONSTRAINT pk_Table1 PRIMARY KEY ( Table1Id )
)
GO --------------------------
CREATE TABLE Table2
(
Table2Id INT IDENTITY(1, 1) ,
Table1Id INT ,
NAME VARCHAR(20) ,
TheDate SMALLDATETIME ,
CONSTRAINT pk_Table2 PRIMARY KEY ( Table2Id ) ,
CONSTRAINT fk_Table2_Table1 FOREIGN KEY ( Table1Id ) REFERENCES Table1 ( Table1Id )
)
GO --------------------------
INSERT INTO Table1
( Name )
VALUES ( 'Stack Overflow' )
GO --------------------------
INSERT INTO Table1
( Name )
VALUES ( 'Expert Sex Change' )
GO --------------------------
INSERT INTO Table1
( Name )
VALUES ( 'Code Project' )
GO --------------------------
INSERT INTO dbo.Table2
( Table1Id ,
NAME ,
TheDate
)
VALUES ( 1 ,
'S1' ,
'11-01-2012'
)
GO --------------------------
INSERT INTO dbo.Table2
( Table1Id ,
NAME ,
TheDate
)
VALUES ( 1 ,
'S2' ,
'11-01-2013'
)
GO --------------------------
INSERT INTO dbo.Table2
( Table1Id ,
NAME ,
TheDate
)
VALUES ( 2 ,
'E1' ,
'10-01-2013'
)
And here's my LINQ:
from t1 in Table1s
join t2 in Table2s.OrderByDescending(x => x.TheDate)
on t1.Table1Id equals t2.Table1Id into tt
from t2 in tt.DefaultIfEmpty()
select new
{
t1.Table1Id,
t1.Name,
t2.NAME,
t2.TheDate
}
This one returns:
Table1Id - Name - NAME - TheDate
1 - Stack Overflow - S2 - 11/1/2013
2 - Expert Sex Change - E1 - 10/1/2013
1 - Stack Overflow - S1 - 11/1/2012
3 - Code Project - null - null
I want the LINQ query not to return the third line, as is from an older Date Value.
I think I got it, the answer is:
from t1 in Table1s
join t2 in Table2s
on t1.Table1Id equals t2.Table1Id
into tt
from x in tt.DefaultIfEmpty()
// where ... t1 && x ..
orderby t1.Table1Id
group x by new {t1.Table1Id,t1.Name} into g
select new {
Table1Id = g.Key.Table1Id,
Name = g.Key.Name,
TheDate = g.Max(c => c.TheDate)
}

How do i turn this tables into an object-oriented table (object-relation) in Oracle

I have this table below in the picture that i want to create as an object-oriented table. I dont want the usual create table with relationships.... I just want to learn how to turn this table into an object-oriented table. Below is a picture of my tables and how are they connected:
CREATE TYPE A_TYPE AS OBJECT(
id INT,
col1 INT
);
/
CREATE TYPE A_REF_TABLE_TYPE AS TABLE OF REF A_TYPE;
/
CREATE TYPE B_TYPE AS OBJECT(
id INT,
col1 INT
);
/
CREATE TYPE B_REF_TABLE_TYPE AS TABLE OF REF B_TYPE;
/
CREATE TYPE C_TYPE AS OBJECT(
id INT,
a_list A_REF_TABLE_TYPE,
b_list B_REF_TABLE_TYPE,
col1 INT
);
/
CREATE TABLE A_TAB OF A_TYPE(
ID PRIMARY KEY
);
CREATE TABLE B_TAB OF B_TYPE(
ID PRIMARY KEY
);
CREATE TABLE C_TAB OF C_TYPE(
ID PRIMARY KEY
)
NESTED TABLE a_list STORE AS c_a_lists
NESTED TABLE b_list STORE AS c_b_lists;
INSERT INTO A_TAB VALUES( A_TYPE( 1, 3 ) );
INSERT INTO A_TAB VALUES( 2, 4 );
INSERT INTO B_TAB VALUES ( B_TYPE( 1, 7 ) );
INSERT INTO B_TAB VALUES ( 2, 2 );
INSERT INTO B_TAB VALUES ( 3, 10 );
INSERT INTO C_TAB VALUES (
1,
A_REF_TABLE_TYPE(
( SELECT REF(a) FROM A_TAB a WHERE ID = 2 ) -- Single value
),
( -- Multiple values
SELECT CAST( COLLECT( REF(b) ) AS B_REF_TABLE_TYPE )
FROM TAB_B b
WHERE ID IN ( 1, 3 )
),
42
);
INSERT INTO C_TAB VALUES (
2,
NULL, -- Unknown
B_REF_TABLE_TYPE(), -- No values
54
);
Output:
SELECT * FROM C_TAB;
ID A_LIST B_LIST COL1
-- ------------------------------- --------------------------------------------- ----
1 A_REF_TABLE_TYPE( A_TYPE(2,4) ) B_REF_TABLE_TYPE( B_TYPE(1,7), B_TYPE(3,10) ) 42
2 (null) B_REF_TABLE_TYPE() 54

Resources