28 февраля 2013

Задача о восьми ферзях -- одним оператором SQL

Вот, наткнулся на статью "Задача о восьми Ферзях на Oracle SQL"
http://habrahabr.ru/post/128137/
Решение может и верное, но выглядит кошмарно

Захотел решить её попроще.
В лучших традициях Оракла -- одним оператором SQL.
Получилось.. Не сразу, но все же получилось. Долго не был уверен, что её вообще можно решить _одним_оператором

Сама задача полностью описана на Википедии
http://ru.wikipedia.org/wiki/%C7%E0%E4%E0%F7%E0_%EE_%E2%EE%F1%FC%EC%E8_%F4%E5%F0%E7%FF%F5

Решено в общем случае -- для доски любого размера. Для доски 8*8 время выполнения 2 секунды, для 9*9 -- 40 секунд. Дальше -- растёт лавинообразно, но так и должно быть.
Я решал принципиальный вопрос -- можно ли это сделать компактно и в общем случае.

Детали реализации оператора SQL:
Клетки внутри оператора адресуются не по именам (a1, b2), а по номерам, начиная с 0.
Для доски 8 на 8 -- от 0 до 63.
Использован алгоритм поиска с возвратом
http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D1%81_%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%82%D0%BE%D0%BC

Но этот алгоритм был усовершенствован, я назвал такой алгоритм "Метод ограниченных ресурсов". Суть его такова:
На доске есть четыре типа ресурсов: вертикали, горизонтали, левые и правые диагонали.
Каждая поставленная фигура на доску "потребляет" одну горизонталь, одну вертикаль, и две диагонали.
Соответственно, следующего ферзя мы будем пробовать ставить на ту клетку, для которого есть оставшиеся незанятые горизонталь, вертикаль и диагонали.
Этим существенно сокращаем время перебора.

Оператор возвращает все возможные решения этой задачи. Для доски 8*8 это 92 решения.

with
  T2 (N, L, X, Y, D1, D2, NL)
  as (select level - 1, 0
           , power (2, trunc ((level - 1) / &&D)), power (2, mod ((level - 1), &D))
           , power (2, &D - 1 + trunc ((level - 1) / &D) - mod ((level - 1), &D))
           , power (2, trunc ((level - 1) / &D) + mod ((level - 1), &D))
           , chr (trunc ((level - 1) / &D) + 97) || to_char (mod ((level - 1), &D) + 1)
      from dual connect by level <= power (&D, 2))
, T3 (RN, RL, RX, RY, RD1, RD2, RNL)
  as (select * from T2 union all
      select N, RL + 1, RX + X, RY + Y, RD1 + D1, RD2 + D2, RNL || ' ' || NL
      from T2, T3
      where bitand (RX , X ) = 0 and bitand (RY ,  Y) = 0
        and bitand (RD1, D1) = 0 and bitand (RD2, D2) = 0 and N > RN
     )
select RNL from T3 where RL = &D - 1;


 

 

 
 

12 февраля 2013

Восстановление части БД (отдельных tablespace) на момент времени при недостатке места. Простая альтернатива TSPITR

Задача:

Восстановить удалённые данные в таблице, при том, что в UNDO её уже нет и воспользоваться flashback мы уже не можем.
Восстановить данные нужно из backup и archivelog, но нет сервера, где можно восстановить БД полностью (нет места). На доступном свободном сервере мы можем поднять только часть БД -- нужное нам табличное простанство.

Этот сценарий является ручной альтернативой процедуре "Tablespace Point-in-Time Recovery (TSPITR)", описанной в главе 21 книги Oracle® Database Backup and Recovery User's Guide
http://docs.oracle.com/cd/E11882_01/backup.112/e10642/rcmtspit.htm 
Отличия этого метода в его простоте и в том, что метод свободен от всех ограничений, присущих TSPITR с auxiliary DB.


Решение:

Клонируем экземпляр, восстанавливаем БД из BACKUP как обычно средствами RMAN.
RECOVERY выполняем частично (ненужные нам табличные простанства переводим в оффлайн) средствами SQL*PLUS

Шаги:
(Далее я предполагаю, что имя экземпляра ORCL на резервном сервере совпадает с именем экземпляра на боевом, иначе -- придется подкорректировать spfile и параметры запуска экземпляра)

Эти шаги выполняются на вспомогательном (не боевом) сервере

1. Подключитесь к БД в RMAN

RMAN> connect target SYS/пароль@ORCL_LOCALHOST

2. Восстановите SPFILE
Обратите внимание, что имя восстановленного файла отличается на символ "1". RMAN не будет востанавливать spfile в тот путь, который был использован при старте экземпляра

RMAN> restore spfile to 'C:\ORACLE\product\11.2.0\dbhome_1\database\SPFILEORCL.ORA1' from '<путь ко FRA>\ORCL\<путь>\<имя файла бэкапа с спфайлом>';

3. Подменим SPFILE -- восстановленный сделаем активным

RMAN> shutdown immediate;

RMAN> host 'copy C:\ORACLE\product\11.2.0\dbhome_1\database\SPFILEORCL.ORA1 C:\ORACLE\product\11.2.0\dbhome_1\database\SPFILEORCL.ORA';

4. Стартуем БД

RMAN> set dbid 3344205742 # укажите ваш DBID -- на боевом select DBID from V$DATABASE

RMAN> startup nomount;

RMAN> restore controlfile from '\\<путь ко FRA>\ORCL\BACKUPSET или AUTOBACKUP\<дата>\<имя файла бэкапа с контролфайлом>'; -- обычно из autobackup

RMAN> alter database mount;

5. Каталогизируем бэкапы и архивлоги, удалив прежние пути:

RMAN> crosscheck backup;

RMAN> delete noprompt expired backup;

RMAN> crosscheck archivelog all;

RMAN> delete noprompt expired archivelog all;

RMAN> catalog start with '<путь ко FRA>\ORCL\';

Проверим, что всё правильно закаталогировалось.
В TOAD, SQL Navigator, SQL Developer или пр., что может отобразить сетку

select * from V$ARCHIVED_LOG where DELETED = 'NO' order by RESETLOGS_CHANGE#, SEQUENCE#
select * from V$BACKUP_PIECE where DELETED = 'NO' order by START_TIME

6. Определяем список номеров датафайлов, которые нужно восстановить.
Это удобно сделать через запросы к V$DATAFILE и V$TABLESPACE
В моем случае -- восстанавливаем # 1, 2, 9, 71 (SYSTEM, SYSAUX, UNDO, нужный мне тейблспейс). Первые три файла (ТП) обязательны к восстановлению.

7. Восстанавливаем нужные датафайлы

RMAN> restore datafile 1,2,9,71;

Далее работаем в SQL*Plus

8. Формируем список отключаемых датафайлов через запрос

select 'alter database datafile '||FILE#||' offline;' from V$DATAFILE where file# not in (1, 2, 9, 71) order by FILE#

Получившийся результат копируем и выполняем в "плюсе":

SQL> alter database datafile 3 offline;
SQL> alter database datafile 4 offline;
...

SQL> alter database datafile 99 offline;
SQL> alter database datafile 100 offline;


9. Приступаем собственно к восстановлению:
Формат даты только такой YYYY-MM-DD:HH24:MI:SS. Также можно сделать until change (в этом случае имеется ввиду номер SCN), until consistent, until cancel.
SQL> recover database until time '2013-02-11:08:00:00' using backup controlfile;

ORA-00279: change 3177249496 generated at 02/10/2013 18:13:54 needed for thread 1
ORA-00289: suggestion : \\<путь>\13_02_10\O1_MF_1_144_8KHKZG55_.ARC
ORA-00280: change 3177249496 for thread 1 is in sequence #144

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

В ответ на это сообщение набираем
auto
для того, чтобы сервер автоматические выбирал архивлоги для восстановления -- мы все их правильно закаталогизировали на шаге 5. Альтернатива -- набирать путь к каждому архивлогу.
Сообщение:
Log applied.
Media recovery complete.

говорит о том, что всё ОК.

10. Создаём redo-логи, если необходимо:

SQL> alter database clear logfile group 1;
SQL> alter database clear logfile group 2;
SQL> alter database clear logfile group 3;


11. Открываем БД с опцией resetlogs, обязательной для неполного восстановления:

SQL> alter database open resetlogs;

Сообщение:
Database altered.
говорит о том, что всё ОК.

Добавлю, что уже после открытия БД, если нужны другие ТП, можно их восстановить в RMAN:
RMAN> restore datafile 3;
RMAN> recover datafile 3;
RMAN> sql 'alter database datafile 3 online';