I am inserting into TABLE_A as given below.
INSERT
INTO Table_A (house_id,
house_key_nbr,
mnty_code,
split)
SELECT wcp.id,
ld.ld_ln_id,
ld.ld_mnty,
ROUND((ld.ld_ln_bal/wla.LOAN_AMT) * 100,2) split
FROM table_B ld,
table_C cc,
TABLE_D wcp,
TABLE_E wla
WHERE cc.conv_id = I_conv_id
AND cc.ev_id = wcp.ev_id
AND cc.client_plan_nbr = ld.plan_id
AND wcp.ssn = ld.ssn
AND wla.house_id = wcp.id
AND wla.house_key_nbr = ld.ld_ln_id
AND ld.status_code in ('V','W');
Once i have loaded into the table_A then i created a cursor to find out the records having the sum of split not equal to 100. For those cases I will find the diff and then update the record as given below.
CURSOR max_percent IS
SELECT house_id,
house_key_nbr,
sum(split) percent_sum
FROM TABLE_A s1,
TABLE_D p1,
table_C c1
WHERE s1.house_id = p1.id
AND p1.ev_id = c1.ev_id
AND c1.conv_id = I_conv_id
GROUP BY house_id, house_key_nbr
HAVING SUM(split) != 100;
OPEN max_percent;
l_debug_msg:='Cursor Opened';
FETCH max_percent BULK COLLECT INTO mnty_rec;
l_debug_msg:='Fetching the values from cursor';
FOR i IN 1..mnty_rec.COUNT
LOOP
v_diff := 100.00 - mnty_rec(i).percent_sum;
l_debug_msg:='The difference is '||v_diff||' for the house_id : '||mnty_rec(i).house_id;
UPDATE work_conv_part_loan_mnty_splt wcplms
SET split = split + v_diff
WHERE wcplms.house_id = mnty_rec(i).house_id
AND wcplms.house_key_nbr = mnty_rec(i).house_key_nbr
AND rownum = 1;
l_debug_msg:='Updated the percentage value for the house_id'||mnty_rec(i).house_id ;
END LOOP;
CLOSE max_percent;
The question here is, I achieved this simple process using a cursor. Is there any way I can achieve it during the insertion time itself instead of writing the cursor?
I'm simplifying a bit your setup with two tables: table_a accumulation the data and table_b containing new data.
-- TABLE_A: Primary Key HOUSE_ID, HOUSE_KEY_NBR
create table table_a as
select 1 house_id, 1 house_key_nbr, 90 split from dual union all
select 1 house_id, 2 house_key_nbr, 30 split from dual union all
select 1 house_id, 3 house_key_nbr, 100 split from dual;
-- TABLE_B: new data
create table table_b as
select 1 house_id, 1 house_key_nbr, 5 split from dual union all
select 1 house_id, 1 house_key_nbr, 5 split from dual union all
select 1 house_id, 4 house_key_nbr, 50 split from dual union all
select 1 house_id, 4 house_key_nbr, 40 split from dual union all
select 1 house_id, 5 house_key_nbr, 100 split from dual;
The important point is that the table_a has the primary key defined, so you need to update only one row for the correction of the SPLIT
The first step is simple to MERGE the new data
MERGE INTO table_a a
USING (select HOUSE_ID, HOUSE_KEY_NBR, sum(SPLIT) SPLIT
from table_b
group by HOUSE_ID, HOUSE_KEY_NBR) b
ON (a.HOUSE_ID = b.HOUSE_ID and a.HOUSE_KEY_NBR = b.HOUSE_KEY_NBR)
WHEN MATCHED THEN
update SET a.SPLIT = a.SPLIT + b.SPLIT
WHEN NOT MATCHED THEN
insert (HOUSE_ID, HOUSE_KEY_NBR, SPLIT)
values (b.HOUSE_ID, b.HOUSE_KEY_NBR, b.SPLIT)
So basically you first aggregates the new data to the level of the PK and than using the MERGE either insert or update the table_a
In the second step perform the correction using the same approach with MERGE only use a different source table containing only the defference of the SPLIT to 100.
MERGE INTO table_a a
USING (select HOUSE_ID, HOUSE_KEY_NBR, 100 - sum(SPLIT) SPLIT
from table_a
group by HOUSE_ID, HOUSE_KEY_NBR
having sum(SPLIT) != 100) b
ON (a.HOUSE_ID = b.HOUSE_ID and a.HOUSE_KEY_NBR = b.HOUSE_KEY_NBR)
WHEN MATCHED THEN
update SET a.SPLIT = a.SPLIT + b.SPLIT
WHEN NOT MATCHED THEN
insert (HOUSE_ID, HOUSE_KEY_NBR, SPLIT)
values (b.HOUSE_ID, b.HOUSE_KEY_NBR, b.SPLIT)
After this step all SPLIT are equal 100
select HOUSE_ID, HOUSE_KEY_NBR, sum(SPLIT)
from table_a
group by HOUSE_ID, HOUSE_KEY_NBR
order by 1,2;
HOUSE_ID HOUSE_KEY_NBR SUM(SPLIT)
---------- ------------- ----------
1 1 100
1 2 100
1 3 100
1 4 100
1 5 100
If you do not want to MERGE in table_a and you use INSERT only, I'd challange this desing, because it is not clear which of the many records with the same key you want to update.
I'll recomend not to UPDATE but to INSERT additional rows with the calculated differece SPLIT.
If mnty_code is unique for each house_id, house_key_nbr pair, then you can use window functions in your insert. Try using this for inserting into the split column:
CASE WHEN 1 = ROWNUMBER() OVER ( PARTITION BY wcp.id, ld.ld_ln_id ORDER BY mnty_code DESC ) THEN
-- This is the last row for the given house_id / house_key_nbr, so do special split calculation
100 - SUM(ROUND((ld.ld_ln_bal/wla.LOAN_AMT) * 100,2)) OVER ( PARTITION BY wcp.id, ld.ld_ln_id ORDER BY mnty_code ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ) ELSE
-- Normal split calculation
ROUND((ld.ld_ln_bal/wla.LOAN_AMT) * 100,2)
END split
The idea is that, if you are inserting the last row for a given house_id, house_key_nbr, then set the split value to 100 minus the sum of all the previous values.
If mnty_code is not unique within each house_id, house_key_nbr pair, it gets problematic, because there is no way to identify the "last" row in each group.
Related
I have two tables in Oracle, in first table I have 100 users and in second table I have 100000 records. I want to distribute equal amount of records between them.....
Instead of writing updating and using rownum <= 1000 to distribute data....I want to write merge statement that can divide equal number of records between 100 users.
Table 1
column A Column B column c
1 Pre 90008765
2 Pre 90008766 and so on like this
Table 2
column a Column B column C Column d
1 null null null
2 null null null
And so on will have 100000 records
and between these two tables column a will be common in which we can apply join condition..... please guide me with merge query
If I understand correctly these words "write merge statement that can divide equal number of records between 100 users", you want this:
merge into table2 tgt
using (
select tb.rwd, ta.a
from (select rownum rn, a, b, c, count(1) over () cnt from table1) ta
join (select rowid rwd, rownum rn, a, b, c, d from table2) tb
on mod(ta.rn, cnt) = mod(tb.rn, cnt)) src
on (tgt.rowid = src.rwd)
when matched then update set a = src.a
dbfiddle
This statement assigns rows from T1 to rows in T2 in sequence 1-2-3-...-1-2-3-..., using function mod(). Of course you can update other columns if you need, not only A.
This question already has answers here:
LISTAGG in Oracle to return distinct values
(24 answers)
Closed 2 years ago.
I am trying to retrieve multiple concatenated distinct varchars (named CODE in query) from multiple rows on multiple columns using LISTAGG in oracle 12C, LISTAGG(distinct...) solves the problem on 19c but I must work with 12c.
Unexpected result
I get the above result using this query:
SELECT
T.c1 A,
T.c2 B,
LISTAGG( TI.CODE , ';' ) WITHIN GROUP (ORDER BY TI.CODE) AS COLUMNX1,
LISTAGG( TE.CODE, ' ;') WITHIN GROUP (ORDER BY TE.CODE ) AS COLUMNX2,
LISTAGG(TR.CODE, '; ') WITHIN GROUP (ORDER BY TR.CODE ) AS COLUMNX3
FROM TABLE1 T
INNER join TABLE_I TI on TI.fk_c2 = T.c2
INNER join TABLE_E TE on TE.fk_c2 = T.c2
INNER join TABLE_R TR on TR.fk_c2 = T.c2
WHERE T.d = *parameter*
GROUP BY
T.c1,
T.c2;
I want to retrieve this :
Expected result
The yellow marked strings should not be retrieved.
In evey line of the query result, the columns COLUMNX1, COLUMNX2, COLUMNX3 have the same number of concatenated strings, that's why I have the duplication problem.
furthermore, TABLE_I, TABLE_E and TABLE_R all have a foreign key fk_c2 that references TABLE1.c2
EDIT:
I added a with Clause to retrieve distinct values first then I joined it to my select statement
Expected result is retrieved with this query
WITH TEMP AS (
SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X1
FROM (
SELECT DISTINCT *
FROM TABLE_I
GROUP BY fk_c2 ) COLUMNX1
INNER JOIN
(SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X2
FROM (
SELECT DISTINCT *
FROM TABLE_E)
GROUP BY fk_c2 ) COLUMNX2
ON COLUMNX1.fk_c2 = COLUMNX2.fk_c2
INNER JOIN
(SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X3
FROM(
SELECT DISTINCT *
FROM TABLE_R)
GROUP BY fk_c2 ) COLUMNX3
ON COLUMNX1.fk_c2 = COLUMNX3.fk_c2
)
SELECT
T.c1 A,
T.c2 B,
tmp.X1,
tmp.X2,
tmp.X3
FROM TABLE1 T
INNER join temp tmp on tmp.fk_c2 = T.c2
WHERE T.d = *parameter*
GROUP BY
T.c1,
T.c2
tmp.X1,
tmp.X2,
tmp.X3;
You'll need additional step: first find distinct values, then aggregate them. For example:
SQL> with test (id, col) as
2 (select 1, 'x' from dual union all
3 select 1, 'x' from dual union all
4 --
5 select 2, 'w' from dual union all
6 select 2, 't' from dual union all
7 select 2, 'w' from dual union all
8 --
9 select 3, 'i' from dual
10 ),
11 -- first find distinct values ...
12 temp as
13 (select distinct id, col from test)
14 -- ... then aggregate them
15 select id,
16 listagg(col, ';') within group (order by col) result
17 from temp
18 group by id;
ID RESULT
---------- ----------
1 x
2 t;w
3 i
SQL>
I have an Oracle table I've compiled using an Informatica workflow. It's failing an integrity check because the following queries return a different number of rows:
SELECT DISTINCT * FROM table // 4,000 rows
SELECT * FROM table // 4,006 rows
The table consists of 17 fields, none of which are unique keys (obviously). How can I find the 6 duplicate rows?
For returning duplicate rows.
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY column1,column2...column2
ORDER BY column_names)
seq_no
FROM table cd)
where seq_no>1;
For example i have create one sample_table below for your better understanding.
create table sample_table
(
id1 number,
id2 number
)
i have inserted below data into table
ID1 ID2
1 2
1 2
1 2
2 3
2 3
2 3
In above data set we have 6 rows but only two rows are distinct.
By using below queries we can get distinct rows and non-distinct rows.
SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd
after partition the table with the help of id1 we will get the below results
ID1 ID2 SEQ_NO
1 2 1
1 2 2
1 2 3
2 3 1
2 3 2
2 3 3
Then if you want to see the distinct rows use below query
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd)
where seq_no=1;
if you want to see duplicate set use below query
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd)
where seq_no>1;
A posibiliy is to use a analytical function to count the rows in the same group and I don't see how you can write the query without writing all the columns in some clause:
select *
from (
Select a.*, count(*) over (partition by column1, column2, ..., column17) as cnt
from your_table a
)
where cnt>1
This should get 12 rows, because 6 are duplicated.
A basic sql query would be:
select col1, col2, ..., col17
from table
group by col1, col2, ..., col17
having count(*) > 1;
I have to fetch the first and last row of the table in Toad.
I have used the following query
select * from grade_master where rownum=(select max(rownum) from grade_master)
select * from grade_master where rownum=1
The second query works to fetch the first row. but the first not working. Anyone please help me.
Thanks in advance
Such request makes sense if you specify sort order of the results - there are no such things in database as "first" and "last" rows if sort order is not specified.
SQL> with t as (
2 select 'X' a, 1 b from dual union all
3 select 'C' , 2 from dual union all
4 select 'A' a, 3 b from dual
5 )
6 select a, b, decode(rn, 1, 'First','Last')
7 from (
8 select a, b, row_number() over(order by a) rn,
9 count(*) over() cn
10 from t
11 )
12 where rn in (1, cn)
13 order by rn
14 /
A B DECOD
- ---------- -----
A 3 First
X 1 Last
In oracle the data is not ordered until you specify the order in you sql statement.
So when you do:
select * from grade_master
oracle will give the rows in anyway it want wants.
OTOH if you do
select * from grade_master order by id desc
Then oracle will give the rows back ordered by id descending.
So to get the last row you could do this:
select *
from (select * from grade_master order by id desc)
where rownum = 1
The rownum is determined BEFORE the "order by" clause is assessed, so what this query is doing is ordering the rows descending (the inside query) and then giving this ordered set to the outer query. The outer gets the first row of the set then returns it.
I'm using oracle to output line items in from a shopping app. Each item has a quantity field that may be greater than 1 and if it is, I'd like to return that row N times.
Here's what I'm talking about for a table
product_id, quanity
1, 3,
2, 5
And I'm looking a query that would return
1,3
1,3
1,3
2,5
2,5
2,5
2,5
2,5
Is this possible? I saw this answer for SQL Server 2005 and I'm looking for almost the exact thing in oracle. Building a dedicated numbers table is unfortunately not an option.
I've used 15 as a maximum for the example, but you should set it to 9999 or whatever the maximum quantity you will support.
create table t (product_id number, quantity number);
insert into t values (1,3);
insert into t values (2,5);
select t.*
from t
join (select rownum rn from dual connect by level < 15) a
on a.rn <= t.quantity
order by 1;
First create sample data:
create table my_table (product_id number , quantity number);
insert into my_table(product_id, quantity) values(1,3);
insert into my_table(product_id, quantity) values(2,5);
And now run this SQL:
SELECT product_id, quantity
FROM my_table tproducts
,( SELECT LEVEL AS lvl
FROM dual
CONNECT BY LEVEL <= (SELECT MAX(quantity) FROM my_table)) tbl_sub
WHERE tbl_sub.lvl BETWEEN 1 AND tproducts.quantity
ORDER BY product_id, lvl;
PRODUCT_ID QUANTITY
---------- ----------
1 3
1 3
1 3
2 5
2 5
2 5
2 5
2 5
This question is propably same as this: how to calc ranges in oracle
Update solution, for Oracle 9i:
You can use pipelined_function() like this:
CREATE TYPE SampleType AS OBJECT
(
product_id number,
quantity varchar2(2000)
)
/
CREATE TYPE SampleTypeSet AS TABLE OF SampleType
/
CREATE OR REPLACE FUNCTION GET_DATA RETURN SampleTypeSet
PIPELINED
IS
l_one_row SampleType := SampleType(NULL, NULL);
BEGIN
FOR cur_data IN (SELECT product_id, quantity FROM my_table ORDER BY product_id) LOOP
FOR i IN 1..cur_data.quantity LOOP
l_one_row.product_id := cur_data.product_id;
l_one_row.quantity := cur_data.quantity;
PIPE ROW(l_one_row);
END LOOP;
END LOOP;
RETURN;
END GET_DATA;
/
Now you can do this:
SELECT * FROM TABLE(GET_DATA());
Or this:
CREATE OR REPLACE VIEW VIEW_ALL_DATA AS SELECT * FROM TABLE(GET_DATA());
SELECT * FROM VIEW_ALL_DATA;
Both with same results.
(Based on my article pipelined function)