Finding max value of multiple columns from multiple tables to update Sequence - sql

I had a problem where the DBAs needed to recreate my sequence (had to create with "NO CACHE". Unfortunately, he dropped the sequence before grabbing the current value! The problem is, from what I can tell, there are almost 25 tables that use this sequence. My plan was to try to find the max value of each of the Primary Key "ID" fields, then run a sequence loop to get the sequence back up.
What I'm hoping to do now, is clean up my "ugly" process for a more streamlined process that I can put in my documentation (in the event this occurs again!).
My original solution was do something like the following:
SELECT 'TABLE_1','TABLE_1_ID', MAX(TABLE_1_ID) from TABLE_1
UNION ALL
SELECT 'TABLE_2','TABLE_2_ID', MAX(TABLE_2_ID) from TABLE_2
UNION ALL
SELECT 'TABLE_3','TABLE_3_ID', MAX(TABLE_3_ID) from TABLE_3
UNION ALL
...... (continue select statements for other 20+ tables)
SELECT 'TABLE_25','TABLE_25_ID', MAX(TABLE_25_ID) from TABLE_25
ORDER BY 2 DESC;
This shows works, but putting the table with the highest "MAX" at the top; but to clean it up I'd like to:
1. Simplify the query (an eliminate the UNION ALL) if possible
2. I'd really like to just run the query that returns a single row..
This would be 'gravy', but I have a loop that will run through the next val of the sequence; that loop starts off with:
declare
COL_MaxVal pls_integer;
SEQ_Currval pls_integer default -1;
BEGIN
SELECT MAX(TABLE_X_ID) INTO COL_MaxVal
FROM TABLE_X
while SEQ_Currval < COL_MaxVal
loop
select My_Sequence_SEQ.nexval into SEQ_Currval
from dual;
end loop;
end
If possible, I'd really like to just run the loop script which would discover which table/column has the highest max value, then use that table in the loop to increment the sequence to that max value.
Appreciate any help on this.

Here is solution returning one row:
WITH all_data as
(
SELECT 'TABLE_1','TABLE_1_ID', MAX(TABLE_1_ID) as id from TABLE_1
UNION ALL
SELECT 'TABLE_2','TABLE_2_ID', MAX(TABLE_2_ID) from TABLE_2
UNION ALL
SELECT 'TABLE_3','TABLE_3_ID', MAX(TABLE_3_ID) from TABLE_3
UNION ALL
...... (continue select statements for other 20+ tables)
SELECT 'TABLE_25','TABLE_25_ID', MAX(TABLE_25_ID) from TABLE_25
),
max_id as
(
SELECT max(id) as id FROM all_data
)
SELECT
ad.*
FROM
all_data ad
JOIN max_id mi ON (ad.id = mi.id)
I can not see any simpler solution for this...

If it's not too late then dba might try flashback query against dictionary. E.g.
SELECT * FROM dba_sequences AS OF TIMESTAMP systimestamp - 1/24;
Your safe value should be last_number+cache size. See details in:
LAST_NUMBER on oracle sequence

Related

Store result of minus query ( list of varchars) in a variable in Oracle PL/SQL

I'm using below minus query to get the extra project_ids present in TABLE_ONE compared to TABLE_TWO
select project_id from TABLE_ONE minus select project_id from TABLE_TWO;
I want to store result of above query which is list of varchars in a variable since i need to perform below 2 steps :
If above query returns any project_ids, send an email which contains these project_ids in mail body
insert those extra project_ids in TABLE_TWO to make sure all project_ids present in TABLE_ONE are present in TABLE_TWO
For step 2 I tried below query and it worked.
insert into TABLE_TWO columns (project_id) values (select project_id from TABLE_ONE minus select project_id from TABLE_TWO);
However to perform above 2 steps i need to store the query result in a variable. Please let me know how to do it. I'm using Oracle 12c.
Unfortunately, neither of the two most natural ways to get the missing IDs into table_two (a multi-row INSERT or a MERGE) support the RETURNING.. BULK COLLECT INTO clause.
So, I think your best bet is to get the list of ids first and then use that list to maintain table_two.
Like this:
DECLARE
l_missing_id_list SYS.ODCINUMBERLIST;
BEGIN
SELECT project_id
BULK COLLECT INTO l_missing_id_list
FROM
(
SELECT t1.project_id FROM table_one t1
MINUS
SELECT t2.project_id FROM table_two t2 );
FORALL i IN l_missing_id_list.FIRST..l_missing_id_list.LAST
INSERT INTO table_two VALUES ( l_missing_id_list(i) );
COMMIT;
-- Values are now inserted and you have the list of IDs in l_missing_id_list to add to your email.
END;
That's the basic concept. Presumably you have more columns in TABLE_TWO than just the id, so you'll have to add those.
something like this. Use a cursor loop.
begin
for c_record in (select project_id from TABLE_ONE minus select project_id from TABLE_TWO)
loop
-- send your email however it is done using c_record.project_id
insert into TABLE_TWO columns (project_id) values (c_record.project_id);
end loop;
FYI, there is a disadvantage to doing it this way potentially. If you send the email and then the transaction is rolled back, the email still went out the door.
A more robust way would be to use Oracle Advances Queues, but that starts getting complicated pretty fast.
SELECT LISTAGG(project_id,',') WITHIN GROUP (ORDER BY project_id)
FROM (select project_id from TABLE_ONE minus select project_id from TABLE_TWO) x;

How to get the first id from the INSERT query

Let's imagine that we have a plpgsql (PostgreSQL 10.7) function where there is a query like
INSERT INTO "myTable"
SELECT * FROM "anotherTable"
INNER JOIN "otherTable"
...
So, this query will insert several rows into myTable. In the next query I want to collect the ids which were inserted with some condition. So, my idea was to do it the following:
INSERT INTO "resultTable" rt
SELECT FROM "myTable"
INNER JOIN ...
WHERE rt."id" >= firstInsertedId;
Now the question: how to find this firstInsertedId?
My solution:
select nextval(''"myTable.myTable_id_seq"'') into firstInsertedId;
if firstInsertedId > 1 then
perform setval(''"myTable.myTable_id_seq"'', (firstInsertedId - 1));
end if;
I don't really like the solution as I don't think that it is good for the performance to generate the id, then go back, then generate it again during the insertion.
Thoughts:
was thinking about inserting the ids into variable array and then find the minimum, but no luck.
was considering to use lastval() function, but it seems that it doesn'
t work for me even though in a very similar implementation in MySQL LAST_INSERT_ID() worked just fine.
Any suggestions?
You can do both things in a single statement using a data modifying common table expression. You don't really need PL/pgSQL for that.
with new_rows as (
INSERT INTO my_table
SELECT *
FROM anotherTable
JOIN "otherTable" ...
returning my_table.id
)
insert into resulttable (new_id)
select id
from new_rows;
Another option would be to store the generate IDs in an array.
declare
l_ids integer[];
begin
....
with new_rows as (
INSERT INTO my_table
SELECT *
FROM anotherTable
JOIN "otherTable" ...
returning my_table.id
)
select array_agg(id)
into l_ids
from new_rows;
....
end;

Evaluate a varchar2 string into a condition for a SQL statement

I'm trying to find which rows are missing from 1 database to another, I already have link to the both DBs and I already found out that I can't just join separate tables so what I'm trying right now is select the ID's from one table and paste them into the select statement for the other DB however I don't know how to parse a clob into a condition.
let me explain further:
I got this collection of varchar2's with all the ID's i need to check on the other DB, and I can iterate through that collection so I get a clob with form: 'id1','id2','id3'
I want to run this query on the other DB
SELECT * FROM atable#db2
WHERE id NOT IN (clob_with_ids)
but I don't know how to tell PL/SQL to evaluate that clob as part of the statement.
id field on atable#db2 is an integer and the varchar2 id's I got are from runnning a regex on a clob
edit:
I've been ask to add the example I was trying to run:
SELECT *
FROM myTable#db1
WHERE ( (creation_date BETWEEN to_date('14-JUN-2011 00:00:00','DD-MON-YYYY HH24:MI:SS') AND to_date('14-JUN-2011 23:59:59','DD-MON-YYYY HH24:MI:SS')) )
AND acertain_id NOT IN ( SELECT to_number(REGEXP_REPLACE(REGEXP_REPLACE(REGEXP_SUBSTR(payload,'<xmlTag>([[:alnum:]]+)-'),'<xmlTag>',''),'-','')) as sameIDasOtherTable
FROM anotherTable#db2
WHERE condition1 ='bla'
AND condition2 ='blabla'
AND ( (creation_date BETWEEN to_date('14-JUN-2011 00:00:00','DD-MON-YYYY HH24:MI:SS') AND to_date('14-JUN-2011 23:59:59','DD-MON-YYYY HH24:MI:SS')) ) )
ORDER BY TO_CHAR(creation_date, 'MM/DD/YYYY') ASC;
I get error ORA-22992
Any suggestiongs?
It seems to me you have invested a lot of time in developing the wrong solution. A way simpler solution would be to use a SET operator. This query retrieves all the IDs in the local instance of ATABLE which are missing in the remote instance of the same table:
select id from atable
minus
select id from atable#db2
If your heart is set on retrieving the whole local row, you could try an anti-join:
select loc.*
from atable loc
left join atable#db2 rem
on (loc.id = rem.id )
where rem.id is null
/
I don't believe you can do that, but I've been proved wrong on many occasions... Even if you could find a way to get it to treat the contents of the CLOB as individual values for the IN you'd probably hit the 1000-item limit (ORA-01795) fairly quickly.
I'm not sure what you mean by 'I already found out that I can't just join separate tables'. Why can't you do something like:
SELECT * FROM atable#db2 WHERE id NOT IN (SELECT id FROM atable#db1)
Or:
SELECT * from atable#db2 WHERE id IN (
SELECT id FROM atable#db2 MINUS SELECT id FROM atable#db1)
(Or use #APC's anti-join, which is probably more performant!)
There may be performance issues with joining large tables on remote databases, but it looks like you have to do that at some point, and if it's a one-off task then it might be bearable.
Edited after question updated with join error
The ORA-22992 is because you're trying to pull a CLOB from the the remote database, which doesn't seem to work. From this I assume your reference to not being able to join is because you're joining two remote tables.
The simple option is not to pull all the columns - specify which you need rather than doing a select *. If you do need the CLOB value, the only thing I can suggest trying is using a CTE (WITH tmp_ids AS (SELECT <regex> FROM anotherTable#db2) ...), but I really have no idea if that avoids the two-link restriction. Or pull the IDs into a local temporary table; or run the query on one of the remote databases.

Check whether a table contains rows or not sql server 2005

How to Check whether a table contains rows or not sql server 2005?
For what purpose?
Quickest for an IF would be IF EXISTS (SELECT * FROM Table)...
For a result set, SELECT TOP 1 1 FROM Table returns either zero or one rows
For exactly one row with a count (0 or non-zero), SELECT COUNT(*) FROM Table
Also, you can use exists
select case when exists (select 1 from table)
then 'contains rows'
else 'doesnt contain rows'
end
or to check if there are child rows for a particular record :
select * from Table t1
where exists(
select 1 from ChildTable t2
where t1.id = t2.parentid)
or in a procedure
if exists(select 1 from table)
begin
-- do stuff
end
Like Other said you can use something like that:
IF NOT EXISTS (SELECT 1 FROM Table)
BEGIN
--Do Something
END
ELSE
BEGIN
--Do Another Thing
END
FOR the best performance, use specific column name instead of * - for example:
SELECT TOP 1 <columnName>
FROM <tableName>
This is optimal because, instead of returning the whole list of columns, it is returning just one. That can save some time.
Also, returning just first row if there are any values, makes it even faster. Actually you got just one value as the result - if there are any rows, or no value if there is no rows.
If you use the table in distributed manner, which is most probably the case, than transporting just one value from the server to the client is much faster.
You also should choose wisely among all the columns to get data from a column which can take as less resource as possible.
Can't you just count the rows using select count(*) from table (or an indexed column instead of * if speed is important)?
If not then maybe this article can point you in the right direction.
Fast:
SELECT TOP (1) CASE
WHEN **NOT_NULL_COLUMN** IS NULL
THEN 'empty table'
ELSE 'not empty table'
END AS info
FROM **TABLE_NAME**

Best way to write a named SQL query that returns if row exists?

So I have this SQL query,
<named-query name="NQ::job_exists">
<query>
select 0 from dual where exists (select * from job_queue);
</query>
</named-query>
Which I plan to use like this:
Query q = em.createNamedQuery("NQ::job_exists");
List<Integer> results = q.getResultList();
boolean exists = !results.isEmpty();
return exists;
I am not very strong in SQL/JPA however, and was wondering whether there is a better way of doing it (or ways to improve it). Should I for example, write (select jq.id from job_queue jq) instead of using a star??
EDIT:This call is very performance critical in our app.
EDIT:Did some performance testing, and while the differences were almost negligible, I finally decided to go with:
select distinct null
from dual
where exists (
select null from job_queue
);
IF you are using EXISTS Oracle I recommend using null:
select null
from dual where exists (select null from job_queue);
The following will be the one with lower cost on Oracle:
select null
from job_queue
where rownum = 1;
Update: To include the case when there are no rows on table you can run the following query:
select count(*)
from (select null
from job_queue
where rownum = 1);
With this query you have a optimum plan and only two possible results: 1 if there are rows and 0 if there are no rows.
If you do an "exists" then it will stop looking as soon as it finds a match. This can stop it from doing a full table scan. Same with TOP 1 if you don't have an ORDER BY. If you do a TOP 1 ID and ID is in an index it might use the index and not even go to the table at all. Stopping the full table scan is where the biggest saving in performance is.
Another small savings is that if you do "SELECT 1" or "SELECT COUNT(1)" instead of "SELECT * " or "SELECT COUNT(*)" it saves the work of getting the table structure.
So I would go with:
SELECT TOP 1 1 AS Found
FROM job_queue
Then:
return !results.isEmpty();
This is the least amount of work that I can think of.
For Oracle it would be:
SELECT 1
FROM job_queue
WHERE rownum<2;
Or:
Set Rowcount 1
SELECT 1
FROM job_queue
Why not just do:
select count(*) as JobCount from job_queue
If JobCount = 0, then there's your answer!