Insert into .. select… na oraclu gubi dane.

Problem dotyczy serwra w wersji Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 – 64bi i pobrania danych z serwera zdalnego w wersji Oracle9i Enterprise Edition Release 9.2.0.8.0 – 64bit Production.

Na serwerze zdalnym mamy tabelę z danymi do zaczytania.

Dla ustalenia uwagi niech nazywa się tab_1 i będzie utworzona poleceniem:

CREATE TABLE tab_1
(ID NUMBER,
param_id NUMBER,
param_value VARCHAR2(4000));

Na systemie do którego chcę pobrać z niej dane tworzę perspektywę:

create or replace view v_tab1 as select * from tab_1 @db_link;

Po wykonaniu polecenia select * from v_tab1; widzimy wszystkie dane.

Tworzymy teraz w systemie docelowym tabelę tymczasową tmp_data:

CREATE GLOBAL temporary TABLE tmp_data
(ID NUMBER,
param_id NUMBER,
param_value VARCHAR2(4000))  ON COMMIT PRESERVE ROWS;

i próbujemy wykonać polecenie

insert into tmp_data select  id,param_id,  param_value from v_tab1 where id BETWEEN :1 and :2;

i od razu sprawdzamy wynik

select * from tmp_data ;

I niestety w ostatniej kolumnie zamiast spodziewanych danych są same nulle.

Cofam transakcję:

Rollback;

Zachowanie dziwne, ale jakoś trzeba sobie poradzić i te dane pobrać.

Robię prawie to samo, ale pobierając dane wykonuję konkatenację ostatniej kolumny z pustym stringiem (czyli nullem).

insert into tmp_data select id,param_id, '' || param_value as param_value  from v_tab1 where id BETWEEN :1 and :2;
select * from tmp_data ;
rollback;

Tym razem dane w tabelce tymczasowej są prawidłowe.

Podobny efekt zaobserwowałem z inną tablą, gdzie ostatnia kolumna była typu number

W rzeczywistym systemie na którym zaobserwowałem problem.

You May Also Like

OSGi Blueprint visualization

What is blueprint?Blueprint is a dependency injection framework for OSGi bundles. It could be written by hand or generated using Blueprint Maven Plugin. Blueprint file is only an XML describing beans, services and references. Each OSGi bundle could hav...

Simple trick to DRY your Grails controller

Grails controllers are not very DRY. It's easy to find duplicated code fragments in default generated controller. Take a look at code sample below. It is duplicated four times in show, edit, update and delete actions:

class BookController {
def show() {
def bookInstance = Book.get(params.id)
if (!bookInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
return
}
[bookInstance: bookInstance]
}
}

Why is it duplicated?

There is a reason for that duplication, though. If you move this snippet to a method, it can redirect to "list" action, but it can't prevent controller from further execution. After you call redirect, response status changes to 302, but after method exits, controller still runs subsequent code.

Solution

At TouK we've implemented a simple trick to resolve that situation:

  1. wrap everything with a simple withStoppingOnRender method,
  2. whenever you want to render or redirect AND stop controller execution - throw EndRenderingException.

We call it Big Return - return from a method and return from a controller at once. Here is how it works:

class BookController {
def show(Long id) {
withStoppingOnRender {
Book bookInstance = Book.get(id)
validateInstanceExists(bookInstance)
[bookInstance: bookInstance]
}
}

protected Object withStoppingOnRender(Closure closure) {
try {
return closure.call()
} catch (EndRenderingException e) {}
}

private void validateInstanceExists(Book instance) {
if (!instance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
throw new EndRenderingException()
}
}
}

class EndRenderingException extends RuntimeException {}

Example usage

For simple CRUD controllers, you can use this solution and create some BaseController class for your controllers. We use withStoppingOnRender in every controller so code doesn't look like a spaghetti, we follow DRY principle and code is self-documented. Win-win-win! Here is a more complex example:

class DealerController {
@Transactional
def update() {
withStoppingOnRender {
Dealer dealerInstance = Dealer.get(params.id)
validateInstanceExists(dealerInstance)
validateAccountInExternalService(dealerInstance)
checkIfInstanceWasConcurrentlyModified(dealerInstance, params.version)
dealerInstance.properties = params
saveUpdatedInstance(dealerInstance)
redirectToAfterUpdate(dealerInstance)
}
}
}