Query doesn't update all rows - sql

There's quite big table, more than 10 000 000 rows. It has columns OBJ_ID, DATE_OF_CHANGE, USER. And I added a new column, RECORD_ID, it is empty for now.
I need to update it so RECORD_ID should have numeric values ascending for OBJ_ID and DATE_OF_CHANGE.
I came up with this:
CREATE SEQUENCE REC_ID_SEQ
START WITH 1
INCREMENT BY 1
CACHE 100;
/
CREATE OR REPLACE TRIGGER TRG_REC_ID_SEQ
BEFORE INSERT ON T_HISTORY
FOR EACH ROW
BEGIN
:NEW.RECORD_ID := REC_ID_SEQ.NEXTVAL;
END;
/
DECLARE
O_ID NUMBER := 0;
S_DATE DATE := SYSDATE;
HIST_NUM NUMBER := 0;
LOOP_COUNT NUMBER := 0;
BEGIN
FOR O IN (SELECT ROWID ROW_ID, D.* FROM T_HISTORY D ORDER BY D.OBJ_ID, D.DATE_OF_CHANGE)
LOOP
LOOP_COUNT := LOOP_COUNT + 1;
IF O.OBJ_ID != O_ID OR O.DATE_OF_CHANGE!= S_DATE
THEN
HIST_NUM := HIST_NUM + 1;
END IF;
UPDATE T_HISTORY T SET T.RECORD_ID = HIST_NUM WHERE T.ROWID = O.ROW_ID;
O_ID := O.OBJ_ID;
S_DATE := O.DATE_OF_CHANGE;
IF LOOP_COUNT > 100000 THEN
COMMIT; LOOP_COUNT := 0;
END IF;
END LOOP;
END;
/
But when the command stops working (no errors) I see that about half of rows were not updated. How do I do this the right way?

Use MERGE command and rowid pseudocolumn as a substitute of primary key:
merge into T_HISTORY t
using (
select rownum as xx, t.*
from (
select t.*, rowid as x_rowid
from T_HISTORY t
order by OBJ_ID, DATE_OF_CHANGE
) t
) xx
on (xx.x_rowid = t.rowid )
when matched then update
set t.RECORD_ID = xx;
Live demo: http://sqlfiddle.com/#!4/aad05/2

Similar to #krokodilko's solution, using analytical function:
MERGE INTO t_history t
USING (SELECT obj_id,
date_of_change,
ROW_NUMBER () OVER (ORDER BY obj_id, date_of_change) rn
FROM t_history) r
ON (t.obj_id = r.obj_id AND t.date_of_change = r.date_of_change)
WHEN MATCHED
THEN
UPDATE SET t.record_id = r.rn;

Related

How to assign the same value in certain block with PostgreSQL?

Here , I want to assign all the value of router_index in the same block with the first row of index value in this block.
E.g. The router_index value from Row 1 to 4 should be 5,383 and from 5 to 7 should be 2,703...
I wonder if I can do it with pure PostgreSQL ?
Thanks for your help!!!!
do $$
declare
cnt integer := 0;
head_index_id varchar := '0';
cur_index_id varchar := '0';
fixed_index varchar := '10';
cnt_limit integer;
begin
select max(per_id) from detailed_router into cnt_limit;
while cnt <= cnt_limit loop
select router_index from detailed_router where per_id = cnt into cur_index_id;
if head_index_id = '0' and cur_index_id != fixed_index then
head_index_id := cur_index_id;
else
if cur_index_id = fixed_index then
-- update
update detailed_router set router_index = head_index_id where per_id = cnt;
else
select router_index from detailed_router where per_id = cnt into head_index_id;
end if;
end if;
raise notice 'cnt %', cnt;
raise notice 'cur_index_id %', cur_index_id;
raise notice 'head_index_id %', head_index_id;
cnt := cnt + 1;
end loop;
end $$
If you want "10" values to be filled with the previous non-"10" value and you have a column that specifies the ordering, you can use window functions:
select t.*,
max(router_index) over (partition by driver_index_code, grp) as imputed_router_index
from (select t.*,
count(*) filter (where router_index > 10) over (partition by driver_index_code order by <ordering column>) as grp
from t
) t;

Oracle optimize select after update status performance

I have a store procedure that will
Update maximum 500 rows status from 0 to 1
Return those rows to program via cursor
Here is my store procedure code
PROCEDURE process_data_out (
o_rt_cursor OUT SYS_REFCURSOR
) IS
v_limit NUMBER;
l_data_ids VARCHAR2(32000);
BEGIN
v_limit := 500; -- limit 500
l_data_ids := '';
-- Create loop to get data
FOR i IN (
SELECT *
FROM
(
SELECT id FROM
TBL_DATA a
WHERE
a.created_at BETWEEN SYSDATE - 0.5 AND SYSDATE + 0.1
AND a.status = 0
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_1)
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_2 WHERE IS_DENY = 1)
ORDER BY
priority
)
WHERE
ROWNUM <= v_limit
) LOOP
BEGIN
-- Build string of ids like id1,id2,id3,
l_data_ids := l_data_ids
|| i.id
|| ',';
-- update row status to prevent future repeat
UPDATE TBL_DATA
SET
status = 1
WHERE
id = i.id;
END;
END LOOP;
COMMIT;
-- If string of ids length >0 open cursor to take data
IF ( length(l_data_ids) > 0 )
THEN
-- Cut last comma id1,id2,id3, --> id1,id2,id3
l_data_ids := substr(l_data_ids,1,length(l_data_ids) - 1);
-- open cursor
OPEN o_rt_cursor FOR
SELECT
id,
phone
FROM
TBL_DATA a
WHERE
a.id IN (
SELECT
to_number(column_value)
FROM
XMLTABLE ( l_data_ids )
);
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END process_data_out;
I want to optimize this performance and here is my question
Should I replace in by exists
Replace
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_1)
AND a.phone NOT IN (SELECT phone FROM TBL_BIG_TABLE_2 WHERE IS_DENY = 1)
by
AND NOT Exists (SELECT phone FROM TBL_BIG_TABLE_1 where TBL_BIG_TABLE_1.phone = a.phone)
AND NOT Exists (SELECT phone FROM TBL_BIG_TABLE_2 WHERE TBL_BIG_TABLE_2.phone = a.phone and IS_DENY = 1)
Is there a better way than
Save a string of ids like id1,id2,id3 after update row status
Open cursor by select from string of ids
I appreciate for any suggestion.
Thank for your concern
Row-by-row processing is always slower and you are also creating the string of ids, which again takes time so overall performance is going down.
You can use the collection DBMS_SQL.NUMBER_TABLE to store the updated ids from the UPDATE statement using the RETURNING clause and use it in the cursor query.
Also, I have changed your update statement so that it does not use NOT IN and uses the LEFT JOINS and ROW_NUMBER analytical function for increasing the performance as follows:
CREATE OR REPLACE PROCEDURE PROCESS_DATA_OUT (
O_RT_CURSOR OUT SYS_REFCURSOR
) IS
V_LIMIT NUMBER;
L_DATA_IDS DBMS_SQL.NUMBER_TABLE;
BEGIN
V_LIMIT := 500; -- limit 500
UPDATE TBL_DATA A
SET A.STATUS = 1
WHERE A.ID IN (
SELECT ID
FROM ( SELECT ID,
ROW_NUMBER() OVER(ORDER BY PRIORITY) AS RN
FROM TBL_DATA B
LEFT JOIN TBL_BIG_TABLE_1 T1 ON T1.PHONE = B.PHONE
LEFT JOIN TBL_BIG_TABLE_2 T2 ON T2.IS_DENY = 1 AND T2.PHONE = B.PHONE
WHERE B.CREATED_AT BETWEEN SYSDATE - 0.5 AND SYSDATE + 0.1
AND B.STATUS = 0
AND T1.PHONE IS NULL
AND T2.PHONE IS NULL)
WHERE RN <= V_LIMIT ) RETURNING ID BULK COLLECT INTO L_DATA_IDS;
OPEN O_RT_CURSOR FOR SELECT ID, PHONE
FROM TBL_DATA A
WHERE A.ID IN (SELECT COLUMN_VALUE FROM TABLE ( L_DATA_IDS ));
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END PROCESS_DATA_OUT;
/

using dynamic sql to create column for select statement

I'm writing a stored procedure for paginated results and this result can be ordered by certain values. I did have a switch case in a select statement but because it was trying to do an orderby on rownum it was very slow.
Now I am trying to use dyanmic sql to build the query outside the select but I don't know if what I am doing is possible.
Here is my SQL in Oracle SQL Developer:
create or replace PROCEDURE Sp_tsa_trainees_pagination (
schemeid IN INT,
searchval IN VARCHAR2,
pagesize IN INT DEFAULT 20,
currentpage IN INT DEFAULT 1,
--orderby IN VARCHAR2,
cursor_ OUT SYS_REFCURSOR)
AS
-- LOCAL VARIABLES
totalcount INT;
numberofpages INT;
startposition NUMBER;
endposition NUMBER;
orderby VARCHAR2(100) := 'surname asc' ;
dynamic_query VARCHAR(255) := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
BEGIN
-- Get total number of trainees in scheme
select COUNT(t.ORG_REGISTRATION_ID)
into totalcount FROM v_trainee t
where t.ORG_REGISTRATION_ID = schemeid
AND t.status = 'A' and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%';
-- calculate number of pages in the pagination by dividing total number of records by how many to display for each page
numberofpages := totalcount / pagesize;
-- get start position by multiplying number of records to display for each page by current page
startposition := pagesize *( currentpage-1);
-- add calculated start position by number of records to display to get end position
endposition := startposition + pagesize;
CASE orderby
WHEN 'surname desc' THEN dynamic_query := 'row_number() over (order by t.SURNAME DESC, t.FORENAMES DESC) AS rnum';
WHEN 'surname asc' THEN dynamic_query := 'row_number() over (order by t.SURNAME ASC, t.FORENAMES ASC) AS rnum';
END CASE;
OPEN cursor_ FOR
Select * from
(
SELECT
-- order by based on selection
dynamic_query rnum,
t.ORG_REGISTRATION_ID SearchId,
t.FORENAMES Forenames,
t.FORENAME Forename,
t.SURNAME Surname,
t.person_id PersonId,
t.trainee_name TraineeName,
t.STATUS Status,
t.IPD_ANNUAL_REVIEW_DATE AnnualReviewDate,
t.ANNUAL_REVIEW_STATUS AnnualReviewStatus,
t.payment_received PaymentRecieved,
t.TRAINEE_ID TraineeId,
t.IPD_SIGNUP_DATE IpdSignupDate,
t.START_DATE StartDate,
t.END_DATE EndDate,
t.LENGTH_ON_SCHEME LengthOnScheme,
t.EMPLOYEE_NUMBER EmploymentNumber,
t.SELECTED_LEVEL SelectedLevel,
t.SELECTED_LEVEL_DESCRIPTION SelectedLevelDescription,
t.ELIGIBLE_LEVEL EligibleLevel,
t.ELIGIBLE_LEVEL_DESCRIPTION EligibleLevelDescription,
sce.FORENAMES SceForenames,
sce.FORENAME SceForename,
sce.SURNAME SceSurname,
sce.mentor_name SceName,
sce.EMPLOYEE_NUMBER SceEmployeeNumber,
de.FORENAMES DeForenames,
de.FORENAME DeForename,
de.SURNAME DeSurname,
de.mentor_name DeName,
de.EMPLOYEE_NUMBER DeEmployeeNumber,
t.COMPLETED_ATTRIBUTE_LEVELS CompletedAttributeLevels,
t.ATTRIBUTE_LEVEL_COUNT AttributeLevelCount,
-- get percentage
CASE t.ATTRIBUTE_LEVEL_COUNT
WHEN 0 THEN 0
ELSE
COMPLETED_ATTRIBUTE_LEVELS / t.ATTRIBUTE_LEVEL_COUNT * 100
END percentage,
DECODE(F_ISTRAINEEGROUPMEMBER(t.ORG_REGISTRATION_ID, 'S', t.person_id),'Y','N','Y') WithoutTsaGroup,
orr.status SchemeStatus,
(select count(*) from TRAINING_GROUP_TRAINEE tgt where tgt.trainee_id = t.TRAINEE_ID) NUMBER_OF_GROUPS,
TotalCount
FROM v_trainee t
INNER JOIN org_registration orr ON t.ORG_REGISTRATION_ID = orr.id
LEFT OUTER JOIN v_mentor sce ON t.sce_id = sce.MENTOR_ID
LEFT OUTER JOIN v_mentor de ON t.de_id = de.MENTOR_ID
where t.ORG_REGISTRATION_ID = schemeid AND t.status = 'A'
and LOWER(t.trainee_name) like '%' || LOWER(searchval) || '%'
)
where rnum >= startposition and rnum <= endposition;
END;
I want to use this variable with the assigned sql:
dynamic_query rnum,
But when I execute the stored procedure I get this error:
ORA-01722: invalid number ORA-06512: at
"db.SP_TSA_TRAINEES_PAGINATION", line 46 ORA-06512: at line 13
So basically my question is can I assign a SQL to VARCHAR2 and then use it in a select statement dynamically.
You may need dynamic SQL for this. For example:
create or replace procedure testDyn(n in number, C OUT SYS_REFCURSOR) is
vDynamicPart varchar2(1000);
vSQl varchar2(1000);
begin
--
if (n = 1) then
vDynamicPart := 'count(1)';
else
vDynamicPart := 'count(null)';
end if;
--
vSQl := 'select ' || vDynamicPart || ' from dual';
open C for vSQl;
end;
If you call it
declare
n1 number;
n2 number;
C1 SYS_REFCURSOR;
C2 SYS_REFCURSOR;
begin
testDyn(1, C1);
testDyn(2, C2);
fetch C1 into n1;
fetch C2 into n2;
dbms_output.put_line('n1: ' || n1);
dbms_output.put_line('n2: ' || n2);
end;
you get:
n1: 1
n2: 0

SQL ORACLE error in trigger

I'm trying to create a trigger and I get the following error:
Error(24,5): PLS-00103: Found the symbol "BEGIN" when it was expected one of the following: * & - + / at loop mod remainder rem and or || multiset. I'm quite a newbie, thanks in advance!
CREATE OR replace TRIGGER ins_livro
instead OF INSERT ON viewLivros
FOR EACH ROW
DECLARE
cnt NUMBER := 10;
biggestID Number;
BEGIN
Select max(exemplar_id) into biggestID from exemplar;
INSERT INTO livro (
id_livro,
nome_livro,
id_editora,
ano,
Preco_Aluguer,
Preco_Compra,
Preco_Multa
)
VALUES (:new.id_livro,
:new.nome_livro,
:new.id_editora,
:new.ano,
:new.Preco_Aluguer,
:new.Preco_Compra,
:new.Preco_Multa
);
WHILE cnt > 0
BEGIN
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1
END;
END;
You are missing the loop and end loop clauses:
WHILE cnt > 0
LOOP
BEGIN
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1
END;
END LOOP;
You have a few mistakes in your syntax. Here it is corrected:
CREATE OR REPLACE TRIGGER ins_livro INSTEAD OF
INSERT ON viewLivros FOR EACH ROW DECLARE cnt NUMBER := 10;
biggestID NUMBER;
BEGIN
SELECT MAX(exemplar_id) INTO biggestID FROM exemplar;
INSERT
INTO livro
(
id_livro,
nome_livro,
id_editora,
ano,
Preco_Aluguer,
Preco_Compra,
Preco_Multa
)
VALUES
(
:new.id_livro,
:new.nome_livro,
:new.id_editora,
:new.ano,
:new.Preco_Aluguer,
:new.Preco_Compra,
:new.Preco_Multa
);
WHILE cnt > 0
LOOP
BEGIN
biggestID := biggestID + 1;
INSERT
INTO exemplar
(
id_exemplar,
id_livro
)
VALUES
(
biggestID,
:new.id_livro
);
cnt := cnt - 1;
END;
END LOOP;
END;
Issues:
You can't use this syntax: SET biggestID = biggestID + 1
You need to use: biggestID := biggestID + 1;
Notice the SET keyword has been removed, = has been changed to := and the line has been terminated with a semicolon.
Also, there's no such thing as :new.biggestID. That is a variable that you've defined in the trigger. It needed to be replaced by just biggestID.
Finally, the BEGIN and END around this block were replaced with LOOP and END LOOP:
SET biggestID = biggestID + 1
INSERT INTO exemplar (
id_exemplar,
id_livro
)
VALUES (
:new.biggestID,
:new.id_livro
);
SET cnt = cnt - 1

How to select a limited amount of rows for each foreign key?

I have this table:
id
feed_id
...
Let's say that I have 500 rows and I want to select 3 entries for each feed_id? And 50 as total limit.
How to write this SQL?
Use:
SELECT x.feedid
FROM (SELECT t.feedid,
CASE WHEN #feed != t.feedid THEN #rownum := 1 ELSE #rownum := #rownum + 1 END AS rank,
#feed := t.feedid
FROM TABLE t
JOIN (SELECT #rownum := NULL, #feed := 0) r
ORDER BY t.feedid) x
WHERE x.rank <= 3
ORDER BY x.feedid
LIMIT 50
What's not clear is the details of what you want returned - all the rows in your table, or just the feedid.
Have you tried using a subselect and limit?
Something like
SELECT *
FROM Table t
WHERE ID IN (SELECT ID FROM #Table WHERE FEED_ID = t.FEED_ID LIMIT 3)
LIMIT 500
You can do this with help of stored procedure.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_feed`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE a INT;
DECLARE cur1 CURSOR FOR SELECT id FROM test.id LIMIT 50;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
REPEAT
FETCH cur1 INTO a;
IF NOT done THEN
SELECT * FROM feed_id WHERE id=a LIMIT 3;
END IF;
UNTIL done END REPEAT;
CLOSE cur1;
END$$
DELIMITER;