Отправляет email-рассылки с помощью сервиса Sendsay

Открыто о СУБД Oracle на русском

  Все выпуски  

Открыто о СУБД Oracle на русском : Изменение соединения


Информационный Канал Subscribe.Ru

Выпуск 37

Изменение соединения

Этот выпуск посвящен "хитрым" операторам UPDATE, изменяющим данные в одной таблице на основе данных из другой. Попутно обсуждаются причины возникновения ошибки ORA-01779 при выполнении таких действий путем изменения соединения. По мотивам очередного ответа Тома Кайта.

Оператор UPDATE и NULL-значения

Том,

При изменении столбца с помощью оператора update, значение некоторых записей (которые не надо менять) изменяется на NULL. Я использую следующий оператор:

 
update    table    name B 
  set        columnname    =  
    (    select    value 
          from     lookup    O 
          where    B.keyname = O.keyname 
            and    O.Othercolumn = Other_value); 

В результате выполняются все необходимые изменения, но изменяются и записи, которые не надо менять: они получают значения Null. Можно ли этого избежать, поскольку нам надо часто изменять записи, но не все сразу.

Нет ли способа изменить только те записи, которые нужно, и не сбросить значения в других записях в Null?

Ответ Тома Кайта

Есть как минимум 2 способа правильно выполнить такого рода коррелированное изменение. Я покажу свой любимый метод (изменение соединения) и другой метод, который будет работать если нельзя задать ограничение уникальности по LOOKUP(keyname) (что необходимо для успешного изменения соединения).

Рассмотрим следующие тестовые таблицы:

 
scott@ORA734.WORLD> create table name 
  2  ( keyname int, 
  3    columnName varchar2(25) 
  4  ) 
  5  / 
Table created. 
 
scott@ORA734.WORLD> create table lookup 
  2  ( keyname int PRIMARY KEY, 
  3    value varchar2(25), 
  4    otherColumn int 
  5  ) 
  6  / 
Table created. 
 
scott@ORA734.WORLD> insert into name values ( 100, 'Original Data' ); 
1 row created. 
 
scott@ORA734.WORLD> insert into name values ( 200, 'Original Data' ); 
1 row created. 
 
scott@ORA734.WORLD> insert into lookup values ( 100, 'New Data', 1 ); 
1 row created. 
 
scott@ORA734.WORLD> commit; 
Commit complete. 

А вот параметр "other_value", который вы используете в своем операторе update...

 
scott@ORA734.WORLD> variable other_value number 
scott@ORA734.WORLD> exec :other_value := 1 
PL/SQL procedure successfully completed. 
 
scott@ORA734.WORLD> select * from name; 
 
   KEYNAME COLUMNNAME 
---------- ------------------------- 
       100 Original Data 
       200 Original Data 

Далее мы изменяем соединение. Можно изменять значение столбцов только в одной из таблиц, а другие таблицы, которые мы не изменяем, должны быть "защищены ключом" - мы должны иметь гарантию, что при соединении NAME со второй таблицей будет возвращено не более одной записи. Для этого столбец keyname в таблице LOOKUP должен быть либо первичным ключом, либо быть связанным ограничением уникальности...

 
scott@ORA734.WORLD> update 
  2    ( select columnName, value 
  3        from name, lookup 
  4       where name.keyname = lookup.keyname 
  5         and lookup.otherColumn = :other_value ) 
  6     set columnName = value 
  7  / 
 
1 row updated. 
 
scott@ORA734.WORLD> select * from name; 
 
   KEYNAME COLUMNNAME 
---------- ------------------------- 
       100 New Data 
       200 Original Data 

Обратите внимание, изменилась только интересующая нас строка - остальные данные не затронуты...

 
scott@ORA734.WORLD> rollback; 
Rollback complete. 
 
scott@ORA734.WORLD> select * from name; 
 
   KEYNAME COLUMNNAME 
---------- ------------------------- 
       100 Original Data 
       200 Original Data 

Следующий способ сработает безо всяких ограничений - не нужно первичного ключа млм ограничения уникальности для таблицы lookup (но лучше точно знать, что подзапрос вернет 0 или 1 запись!).

Оператор очень похож на ваш update, но имеет конструкцию where, чтобы изменялись только строки, для которых есть соответствия...

 
scott@ORA734.WORLD> update name 
  2     set columnName = ( select value 
  3                          from lookup 
  4                         where lookup.keyname = name.keyname 
  5                           and otherColumn = :other_value ) 
  6   where exists ( select value 
  7                    from lookup 
  8                   where lookup.keyname = name.keyname 
  9                     and otherColumn = :other_value ) 
 10  / 
 
1 row updated. 
 
scott@ORA734.WORLD> select * from name; 
 
   KEYNAME COLUMNNAME 
---------- ------------------------- 
       100 New Data 
       200 Original Data 

А можно ли добиться того же для нескольких столбцов?

Том,

Мне не удалось сделать следующее. Я создал две таблицы t и t1 с помощью select * from user_objects. В таблицах t и t1 - по 117 записей. Затем я взял один object_name(PRIMARY_SALESFORE) в таблице t и изменил три записи - object_type, object_id и created.

 
ADHOC@VOYAGER> select t.object_id,t1.object_id,t.object_type,t1.object_type, 
  2  t.created,t1.created 
  3  from t,t1 
  4  where t1.object_name=t.object_name 
  5  and t.object_name='PRIMARY_SALESFORCE'; 
 
OBJECT_ID OBJECT_ID OBJECT_TYPE   OBJECT_TYPE   CREATED   CREATED 
--------- --------- ------------- ------------- --------- --------- 
     2222     17927 ORATAB        TABLE         02-APR-02 01-APR-02 

Теперь при попытке изменить эти три поля в таблице t на основе t1 я получаю следующее сообщение об ошибке. Что я делаю не так?

 
1  update 
2  (select t.object_id,t1.object_id,t.object_type,t1.object_type, 
3  t.created,t1.created 
4  from t,t1 
5  where t1.object_name=t.object_name 
6  and t.object_name='PRIMARY_SALESFORCE') 
7  set t.object_id=t1.object_id, 
8  t.object_type=t1.object_type, 
9* t.created=t1.created 
ADHOC@VOYAGER> / 
update 
* 
ERROR at line 1: 
ORA-00904: invalid column name 

При попытке изменить всего один столбец в T я тоже получаю сообщение об ошибке:

 
ADHOC@VOYAGER> update  
  2  (select t.object_id toid,t1.object_id t1oid 
  3  from t,t1 
  4  where t.object_name=t1.object_name 
  5  and upper(t.object_name)='PRIMARY_SALESFORCE') 
  6  set toid=t1oid;              
update 
* 
ERROR at line 1: 
ORA-01779: cannot modify a column which maps to a non key-preserved table  

Ответ Тома Кайта

Таблицы T и T1 "невидимы" за пределами подставляемого представления (inline view). Вы это исправили, добавив псевдонимы toid, t1oid.

Что касается второй ошибки, пожалуйста, перечитайте представленный выше ответ. НАДО, чтобы столбец object_id таблицы T1 был связан ограничением первичного ключа/уникальности, чтобы каждая строка таблицы T соединялась НЕ БОЛЕЕ, чем с одной строкой таблицы T1.

Пусть в таблице T имеется:

 
OBJECT_NAME          OBJECT_ID 
--------------       ---------------- 
ABC                  xxxx 

А в таблице T1:

 
OBJECT_NAME          OBJECT_ID 
----------------     ------------------ 
ABC                  123 
ABC                  456 

что должно быть в результате:

 
ADHOC@VOYAGER> update  
  2  (select t.object_id toid,t1.object_id t1oid 
  3  from t,t1 
  4  where t.object_name=t1.object_name 
  5  and upper(t.object_name)='PRIMARY_SALESFORCE') 
  6  set toid=t1oid;   

надо ли при соединении брать 123 или 456, и в каких случаях? Поскольку результат этого изменения на 100% НЕОДНОЗНАЧНЫ, мы его не допускаем. Необходимо гарантировать защиту ключом с помощью ограничения первичного ключа/уникальности.

Соответствующий индекс в любом случае нужен из соображений производительности.

Комментарий читателя от 2 апреля 2002 года

Спасибо, Том. Я знал, что если еще раз (внимательно) перечитаю твой ответ, то смогу найти ошибку в SQL-операторе. Но, в любом случае, не мог бы ты помочь мне добиться желанной цели - изменить три столбца из другой таблицы, имеющий один совпадающий столбец (object_name), уникальный, но не являющийся первичным ключом.

Ответ Тома Кайта

 
update t 
  set ( f1, f2, f3 ) =  
        ( select a, b, c from t2 where t2.some_field = t.some_field ) 
 where exists ( select a, b, c from t2 where t2.some_field = t.some_field ) 
/ 

Этот запрос позволит решить вашу проблему. Если нельзя задать ограничение уникальности (которое нужно ТЕОРЕТИЧЕСКИ, даже если не потребуется практически - если значения в столбце some_field таблицы t2 окажутся НЕ уникальными, представленный запрос закончится сообщением об ошибке "подзапрос вернул более одной строки", потому что оператор update будет НЕОДНОЗНАЧНЫМ)

Хороший прием. Комментарий читателя от 9 апреля 2002 года

Спасибо за прекрасный сайт. Я пытаюсь изменить таблицу на основе определенной информации в той же таблице. Запрос выглядит следующим образом:

 
DECLARE 
CURSOR c1 IS SELECT ROWID FROM 
siebel.s_synd_data 
    where 
        WHERE 
     ROW_ID LIKE 'DDM%' 
     AND DATA_SRC_CD = 'DECPRF' 
     and POSITION_ID IS NULL; 
begin 
for cur in c1 loop 
     UPDATE SIEBEL.S_SYND_DATA A 
     SET A.POSITION_ID = (SELECT B.ROW_ID FROM SIEBEL.S_POSTN B,  
SIEBEL.S_ASGN_GRP C 
WHERE A.TERR_ID = C.ROW_ID 
AND SUBSTR(C.NAME, 1, 5) || '00D' = B.NAME) 
   where rowid=cur.rowid; 
  if mod(c1%rowcount,50000)=0 then 
           commit; 
  end if; 
end loop; 
commit; 
end ; 

Предполагается, что условию WHERE курсора C1 будет удовлетворять 9 миллионов строк. Мой АБД говорит, что транзакция будет зафиксирована только после изменения 9 миллионов строк, и мне надо фиксировать ее после каждых 50000 - правильно ли это?

Что можно сделать, чтобы это изменение выполнялось быстрее - я знаю, что ты ненавидишь такие вопросы, если за ними не стоят определенные критерии, но что, если я создам индекс (по функции)?

Ответ Тома Кайта

Я лично делал бы так:

 
update s_synd_data a   -- извините, я не могу заставить себя набрать sie.... 
   set a.position_id = ( select b.row_id  
                           from s_postn b, s_asng_grp c 
                          where c.row_id = a.terr_id 
                            and b.name = substr(c.name,1,5) || '00D' ) 
 where row_id like 'DDM%' 
   and data_src_cd = 'DECPRF' 
   and position_id is null; 

Если бы по столбцу position_id был индекс, я бы его удалил, выполнил изменение и пересоздал индекс без возможности восстановления и с распараллеливанием.

Прежде чем вы скажете: "но там же 9 миллионов строк - надо фиксировать по частям", я отвечу - ни в коем случае. Проверьте только, что выделено достаточно сегментов отката (иначе, при использовании вашего подхода, РАНО ИЛИ ПОЗДНО, вы получите сообщение об ошибке ORA-01555 - поищите на сайте обсуждения по слову 01555 или 1555 и поймете, что я имею ввиду).

Потребуется не так уж много места в сегментах отката, как может показаться. Процедурный код, вроде вашего:

  • будет работать медленне простого оператора update
  • сгенерирует больше данных отмены и повторного выполнения, чем один оператор update
  • будет источником ошибок (ora-01555, другие логические ошибки)
  • у вас уже есть логическая ошибка - c1%rowcount не содержит "суммарное" значение; это колчиество строк, измененных последним оператором update. Если вы измените фактически менее 50000 строк одним оператором, транзакция не будет зафиксирована (в вашем коде - ошибка)
  • вообще - плохая идея.

В своей системе я только что изменил 1099008 строк:

 
ops$tkyte@ORA817DEV.US.ORACLE.COM> update big_table set owner = lower(owner); 
Elapsed: 00:08:38.66 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select used_ublk from v$transaction; 
 
 USED_UBLK 
---------- 
     13002 
Elapsed: 00:00:00.01 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select 13002*8/1024 from dual; 
 
13002*8/1024 
------------ 
  101.578125 
Elapsed: 00:00:00.01 
ops$tkyte@ORA817DEV.US.ORACLE.COM> select count(*) from big_table; 
 
  COUNT(*) 
---------- 
   1099008 
Elapsed: 00:00:02.41 

для этого потребовалось около 100 Мбайт в сегменте отката. Вам следует ожидать порядка гигабайта. Я бы просто убедился (с помощью АБД), что столько места есть, использовал бы оператор set transaction use rollback segment при необходимости (можно просто создать временный сегмент для этого оператора update в каталоге /tmp, например, и потом его удалить). Выполните update. Этого достаточно.

Меньше думать, меньше делать, меньше вероятность ошибки...

Комментарий читателя от 30 апреля 2002 года

Уважаемый Том...

В какой версии Oracle появилась возможность выполнять "изменение соединения"? Я всегда использовал для таких запросов второй вариант, с конструкцией EXISTS. Всегда казалось смешным, что приходится один и тот же подзапрос вводить дважды.

Тебе надо написать книжку о хитрых приемах в Oracle SQL.

Ответ Тома Кайта

Уверен, что эта возможность появилась в версии 7.3

 
ops$tkyte@ORA734.WORLD> create table t1 ( x int primary key, y int ); 
 
Table created. 
 
ops$tkyte@ORA734.WORLD> create table t2 ( x int, y int ); 
 
Table created. 
 
ops$tkyte@ORA734.WORLD> insert into t1 values ( 1, 1 ); 
 
1 row created. 
 
ops$tkyte@ORA734.WORLD> insert into t1 values ( 2, 2 ); 
 
1 row created. 
 
ops$tkyte@ORA734.WORLD> insert into t2 values ( 2, null ); 
 
1 row created. 
 
ops$tkyte@ORA734.WORLD> insert into t2 values ( 3, null ); 
 
1 row created. 
 
ops$tkyte@ORA734.WORLD> update ( select t1.y t1_y, t2.y t2_y 
  2                     from t1, t2 
  3                    where t1.x = t2.x ) 
  4     set t2_y = t1_y 
  5  / 
 
1 row updated. 
 
ops$tkyte@ORA734.WORLD> select * from t2; 
 
         X          Y 
---------- ---------- 
         2          2 
         3 
 

Параллельные операторы ЯМД и изменение представления с соединением

 
ADHOC@VOYAGER> update  
  2  (select t.object_id toid,t1.object_id t1oid 
  3  from t,t1 
  4  where t.object_name=t1.object_name 
  5  and upper(t.object_name)='PRIMARY_SALESFORCE') 
  6  set toid=t1oid;   

В представленном выше запросе, который демонстрирует изменение представления с соединением, меня интересует, выполняется ли это изменение параллельно, если таблица t фрагментирована и выполнен оператор alter session enable parallel dml. Я использую аналогичный оператор update и выяснил, что лучшим планом выполнения оператора select будет полный просмотр обеих таблиц и соедитнение хешированием.

Ответ Тома Кайта

Если удовлетворены все остальные критерии (включено распараллеливание, таблицы допускают "распараллеливание") - должно выполняться параллельно.

Изменение будет выполняться по фрагментам таблицы T, с полным просмотром каждого фрагмента T и соединением его с таблицей T1 после фильтрации по условию upper(t.object_name).

Один из способов убедиться в этом - выполнить изменение, а затем - оператор select * from t where rownum = 1; Если этот оператор сработает, изменение не распараллеливалось, а если нет - было выполнено параллельно. Выполните Commit и сможете выбирать данные.

Можно ли это сделать, если задействовано более двух таблиц?

Я делал это, когда в соединении задействовано две таблицы.

Можно ли это сделать, если необходимо изменить столбец таблицы A в соответствии со значениями столбца в таблице C. Таблицы связаны так: A -> B -> C, где A является главной для всех, а B - главной для C?

Ответ Тома Кайта

Ай-я-яй, такой простой пример - вы же все уже указали - и не попытаться сделать... Ладно.

Ответ - да, если соблюдается "защита ключом", другими словами, каждая строка таблицы A появится в результате соединения НЕ БОЛЕЕ ОДНОГО РАЗА. В противном случае - нет.

Рассмотрим простой пример:

 
ops$tkyte@ORA920.LOCALHOSTgt; create table a ( x int primary key, y int ); 
 
Table created. 
 
ops$tkyte@ORA920.LOCALHOST> create table b ( x references a primary key ); 
 
Table created. 
 
ops$tkyte@ORA920.LOCALHOST> create table c ( x references b primary key, y int ); 
 
Table created. 
 
ops$tkyte@ORA920.LOCALHOST> insert into a values ( 1, null ); 
 
1 row created. 
 
ops$tkyte@ORA920.LOCALHOST> insert into a values ( 2, null ); 
 
1 row created. 
 
ops$tkyte@ORA920.LOCALHOST> insert into b values ( 1 ); 
 
1 row created. 
 
ops$tkyte@ORA920.LOCALHOST> insert into c values ( 1, 100 ); 
 
1 row created. 
 
ops$tkyte@ORA920.LOCALHOST> select * from a; 
 
         X          Y 
---------- ---------- 
         1 
         2 
 
ops$tkyte@ORA920.LOCALHOST> update ( select a.y a_y, c.y c_y 
  2          from a, b, c 
  3             where a.x = b.x and b.x = c.x ) 
  4      set a_y = c_y; 
 
1 row updated. 
 
ops$tkyte@ORA920.LOCALHOST> select * from a; 
 
         X          Y 
---------- ---------- 
         1        100 
         2 
 

Блокировки. Комментарий читателя от 2 октября 2002 года

Том,

В случае:

 
update 
      ( select columnName, value 
          from name, lookup 
         where name.keyname = lookup.keyname 
           and lookup.otherColumn = :other_value ) 
       set columnName = value 
    / 

будут ли обе таблицы, name и lookup, заблокированы по ходу изменения?

Ответ Тома Кайта

 
ops$tkyte@ORA920.LOCALHOST> alter table emp add dname varchar2(30); 
 
Table altered. 
 
ops$tkyte@ORA920.LOCALHOST> update ( select emp.dname edname, dept.dname ddname 
  2          from emp, dept 
  3                where emp.deptno = dept.deptno ) 
  4   set edname = ddname; 
 
14 rows updated. 
 
ops$tkyte@ORA920.LOCALHOST> select type, id1, (select object_name from  
user_objects where object_id = id1) oname 
  2    from v$lock 
  3   where sid = (select sid from v$mystat where rownum=1) 
  4      and type = 'TM' 
  5  / 
 
TY        ID1 ONAME 
-- ---------- -------------------- 
TM      31715 EMP 

Блокируются только строки таблицы, которую вы изменяете.

Комментарий читателя от 29 января 2003 года

 
scott@ORA734.WORLD> update 
  2    ( select columnName, value 
  3        from name, lookup 
  4       where name.keyname = lookup.keyname 
  5         and lookup.otherColumn = :other_value ) 
  6     set columnName = value 
  7  / 
 
1 row updated. 
 
scott@ORA734.WORLD> update name 
  2     set columnName = ( select value 
  3                        from lookup 
  4                       where lookup.keyname = name.keyname 
  5                           and otherColumn = :other_value ) 
  6   where exists ( select value 
  7                    from lookup 
  8                   where lookup.keyname = name.keyname 
  9                     and otherColumn = :other_value ) 
 10  / 
 
1 row updated. 

Том,

какой из этих двух запросов лучше с точки зрения производительности? Я предполагаю, что первый, но сомневаюсь. Если я ошибаюсь, то почему?

Ответ Тома Кайта

Оба плохи...

Нет, первый лучше

Хотя, постойте, второй лучше

Оба могут работать быстро, я думаю...

Их производительность будет зависеть от используемого оптимизатора, актуальности статистической информации, и т.п.

В общем случае, если "NAME" - очень маленькая таблица, а "LOOKUP" - очень большая, (но проиндексированная по keyname/othercolumn) - оператор с where exists вполне можно использовать.

Но обычно лучше изменять соединение.

Изменение главной таблицы на основе подчиненной (подчиненная - с составным ключом)

Не мог бы ты помочь мне сделать то, что я пытаюсь - изменить 2 или более столбца главной таблицы на основе данных в подчиненной, у которой первичный ключ - составной.

Пример:

 
Таблица: Food 
(food_id    number primary key 
 food_name  varchar2(100) 
 calorie    number  
 fibre      number 
 total_fat  number 
) 
 
Пример данных в таблице Food: 
Food_id  food_name  calorie    fibre    total_fat 
1        apple      null       null    null 
2        beer       null       null    null 
 
Таблица: Food_Portion 
(food_id     number 
 portion_id  number  
 qty         number 
 weight      number 
 calorie     number  
 fibre       number 
 total_fat   number 
 )  
  
food_id + portion_id - первичный ключ 
 
Пример данных в таблице Food_Portion: 
Food_id    Portion_ID qty    weight calorie    fibre  total_fat 
1          1          1      10     10         3      3     
1          2          2      15     20         5      2 
1          3          100    100    60         10     9 
2          1          1      10     5          0      0 
2          2          .5     6      4          0      0 
2          3          100    100    40         0      10 

В результате изменения надо установить значения столбцов calorie, fibre и total_fat в таблице food_table равными значениям в таблице food_portion, где food_portion.qty = 100 и food_portion.weight = 100

Гарантируется, что для каждого food_id в таблице food_portion будет только одна строка с qty = 100 и weight = 100 (т.е. оператор select food_id from food_portion where qty = 100 and weight = 100 вернет ровно одну строку для каждого значения food_id)

Я выполнял следующий оператор update:

 
update ( select a1, b1 from a, b where a.id = b.id ) 
set a1 = b1 (this one I haven't tried) 

Но получил сообщение об ошибке 'ORA-01779: cannot modify a column which maps to a non key-preserved table'

Потом я попробовал выполнить:

 
update t 
  set ( f1, f2, f3 ) =  
        ( select a, b, c from t2 where t2.some_field = t.some_field ) 
 where exists ( select a, b, c from t2 where t2.some_field = t.some_field ) 
  
update a 
set a1 = ( select b1 from b where b.id = a.id ) 
   where a.id in ( select id from b ) 

И оба оператора вернули сообщение об ошибке ORA-01427: single-row subquery returns more than one row

Меня интересует, можно ли выполнить такое изменение, не используя PL/SQL?

Ответ Тома Кайта

Если вы указываете:

 
where t2.some_field = t.some_field ) 

И получаете:

ORA-01427: single-row subquery returns more than one row

Значит, предположение о единственности строки для каждого food_id не оправдалось.

Но, в любом случае, если запрос:

 
select food_id 
  from food_portion 
 where qty = 100 and weight = 100 
 group by food_id 
 having count(*) > 1; 

возвращает НОЛЬ строк (нет дублирующихся значений!), то:

 
ops$tkyte@ORA817DEV> update food 
  2     set (calorie,fibre,total_fat) = 
  3             (select calorie,fibre,total_fat 
  4                    from food_portion fp 
  5                   where fp.food_id = food.food_id 
  6                     and fp.qty = 100 
  7                     and fp.weight = 100 ) 
  8   where exists 
  9             (select calorie,fibre,total_fat 
 10                    from food_portion fp 
 11                   where fp.food_id = food.food_id 
 12                     and fp.qty = 100 
 13                     and fp.weight = 100 ) 
 14  / 

Выполняет соответствующее изменение.

Очень полезный пример

После того, как увидел способ изменения соединения, обнаружил, что он хорошо подходит для изменения одного источника данных с просмотром предварительных и окончательных значений:

 
scott@MSORA920>create table dept_demo as select * from dept; 
 
Table created. 
 
scott@MSORA920>SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc)  
Iloc FROM dept_demo; 
 
    DEPTNO DNAME          INAME          LOC           ILOC 
---------- -------------- -------------- ------------- ------------- 
        10 ACCOUNTING     Accounting     NEW YORK      New York 
        20 RESEARCH       Research       DALLAS        Dallas 
        30 SALES          Sales          CHICAGO       Chicago 
        40 OPERATIONS     Operations     BOSTON        Boston 
 
scott@MSORA920>edit 
Wrote file afiedt.buf 
 
  1  UPDATE( 
  2  SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc) Iloc FROM  
dept_demo 
  3* ) SET dname = Iname, loc = Iloc 
scott@MSORA920>/ 
 
4 rows updated. 
 
scott@MSORA920>edit 
Wrote file afiedt.buf 
 
  1* SELECT deptno, dname, INITCAP(dname) Iname, loc, INITCAP(loc) Iloc FROM  
dept_demo 
scott@MSORA920>/ 
 
    DEPTNO DNAME          INAME          LOC           ILOC 
---------- -------------- -------------- ------------- ------------- 
        10 Accounting     Accounting     New York      New York 
        20 Research       Research       Dallas        Dallas 
        30 Sales          Sales          Chicago       Chicago 
        40 Operations     Operations     Boston        Boston 
 
scott@MSORA920> 

Спасибо :)

Комментарий читателя от 1 мая 2003 года

Том,

Если в твоем исходном примере вместо:

 
Update... 
SET    columnName = Value; 

Написать:

 
Update... 
SET    Value = columnName; 

Я получаю сообщение об ошибке "ORA-01779: cannot modify a column which maps to a non key-preserved table" (см. ниже).

Разме в запросе columnName не возвращает тоже всего одну запись? Почему так не срабатывает?

 
SQL> CREATE TABLE name ( 
  2  keyname    INT, 
  3  columnName VARCHAR2(25) 
  4  ); 
 
Table created. 
 
SQL> CREATE TABLE lookup ( 
  2  keyname     INT PRIMARY KEY, 
  3  value       VARCHAR2(25), 
  4  otherColumn INT 
  5  ); 
 
Table created. 
 
SQL> insert into name values ( 100, 'Original Data' ); 
 
1 row created. 
 
SQL> insert into name values ( 200, 'Original Data' ); 
 
1 row created. 
 
SQL> insert into lookup values ( 100, 'New Data', 1 ); 
 
1 row created. 
 
SQL> commit; 
 
Commit complete. 
 
SQL> UPDATE ( 
  2     SELECT a.columnName, b.value 
  3     FROM   name a, lookup b 
  4     WHERE  a.keyname = b.keyname AND 
  5            b.otherColumn = 1) 
  6  SET columnName = value; 
 
1 row updated. 
 
SQL> rollback; 
 
Rollback complete. 
 
SQL> UPDATE ( 
  2     SELECT a.columnName, b.value 
  3     FROM   name a, lookup b 
  4     WHERE  a.keyname = b.keyname AND 
  5            b.otherColumn = 1) 
  6  SET value = columnName; 
SET value = columnName 
    * 
ERROR at line 6: 
ORA-01779: cannot modify a column which maps to a non key-preserved table 

Ответ Тома Кайта

 
SQL> CREATE TABLE name ( 
  2  keyname    INT, 
  3  columnName VARCHAR2(25) 
  4  ); 
 
Table created. 
 
SQL> CREATE TABLE lookup ( 
  2  keyname     INT PRIMARY KEY, 
  3  value       VARCHAR2(25), 
  4  otherColumn INT 
  5  ); 
 
insert into name values ( 1, 'x' ); 
insert into name values ( 1, 'y' ); 
 
insert into lookup values ( 1, null, null ); 

А теперь, какой результат должен получиться после изменения:

 
SQL> UPDATE ( 
  2     SELECT a.columnName, b.value 
  3     FROM   name a, lookup b 
  4     WHERE  a.keyname = b.keyname AND 
  5            b.otherColumn = 1) 
  6  SET value = columnName; 

Столбец value будет иметь значение x или y?

Чтобы избавиться от двусмысленности, необходим первичный ключ по столбцам таблиц "lookup", по которым выполняется соединение - у вас его нет.


Оригинал обсуждения этого вопроса можно найти здесь.


Copyright © 2003 Oracle Corporation


В следующем выпуске

Пока не знаю. Следите за новостями на сайте проекта Open Oracle.

С наилучшими пожеланиями,

  В.К.



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное