Показаны сообщения с ярлыком Mysql. Показать все сообщения
Показаны сообщения с ярлыком Mysql. Показать все сообщения

понедельник, августа 30, 2010

Mysql ERROR 1005 (HY000): Can't create table (errno: 150)

Уже который раз спотыкаюсь об одни и те же грабли, решил записать.

т.е. после создания таблиц:

CREATE TABLE sf_guard_user
(
id BIGINT AUTO_INCREMENT,
first_name VARCHAR(255),
last_name VARCHAR(255),
email_address VARCHAR(255) NOT NULL UNIQUE,
username VARCHAR(128) NOT NULL UNIQUE,
algorithm VARCHAR(128) DEFAULT 'sha1' NOT NULL,
salt VARCHAR(128), password VARCHAR(128),
is_active TINYINT(1) DEFAULT '1',
is_super_admin TINYINT(1) DEFAULT '0',
last_login DATETIME, created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
INDEX is_active_idx_idx (is_active),
PRIMARY KEY(id)
)ENGINE = INNODB;

CREATE TABLE sf_guard_user_profile
(
id BIGINT AUTO_INCREMENT,
user_id BIGINT, first_name VARCHAR(20),
last_name VARCHAR(20),
email VARCHAR(255),
email_hash VARCHAR(255),
INDEX user_id_idx (user_id),
PRIMARY KEY(id)
) ENGINE = INNODB;


я пытаюсь создать ключи:


ALTER TABLE sf_guard_user_profile
ADD CONSTRAINT sf_guard_user_profile_user_id_sf_guard_user_id
FOREIGN KEY (user_id)
REFERENCES sf_guard_user(id)
ON DELETE CASCADE;

и получаю ошибку ERROR 1005 (HY000): Can't create table (errno: 150)
mysql говорит, что не может найти поле на которое ссылаемся

mysql> SHOW ENGINE INNODB STATUS;
...
100830 20:59:41 Error in foreign key constraint of table s1/#sql-477_152:
FOREIGN KEY (user_id) REFERENCES sf_guard_user(id) ON DELETE CASCADE:
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
...


mysql это не нравится, отсюда и ошибка, хотя пишет что не может найти поле, а оно есть.
Дело оказывается в том, что в symfony при генерации схемы нет проверки на размерность типов для внешних ключей, т.е. если указать разные размерности для ключевых полей, то будет ошибка, т.е. вместо:

sfGuardUserProfile:
tableName: sf_guard_user_profile
columns:
user_id: integer(4)
first_name: varchar(20)
last_name: varchar(20)
email: varchar(255)
email_hash: varchar(255)
relations:
Users:
class: sfGuardUser
refClass: sfGuardUserGroup
local: group_id
foreign: user_id
foreignAlias: Groups
sfGuardUser:
type: one
foreignType: one
class: sfGuardUser
local: user_id
foreign: id
onDelete: cascade
foreignAlias: Profile

надо :

sfGuardUserProfile:
tableName: sf_guard_user_profile
columns:
user_id: integer
first_name: varchar(20)
last_name: varchar(20)
email: varchar(255)
email_hash: varchar(255)
relations:
Users:
class: sfGuardUser
refClass: sfGuardUserGroup
local: group_id
foreign: user_id
foreignAlias: Groups
sfGuardUser:
type: one
foreignType: one
class: sfGuardUser
local: user_id
foreign: id
onDelete: cascade
foreignAlias: Profile


а именно поле user_id должно быть таким же как в у sfGuardUser

вторник, июня 16, 2009

Как заставить mysql5 использовать нужный вам default-character и collation

Для начала посмотрите что у вас есть


SHOW VARIABLES LIKE 'character_set%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)


По умолчанию Value=latin1

Теперь вы хотите чтобы все клиенты mysql сразу использовали нужную ва кодировку:
utf8,cp1251 или koi8r

Нужно добавить в файл my.cnf
/etc/mysql/my.cnf

Слудущие переменные:

[client]
default-character-set=utf8
[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8


После изменений перезагружайте сервер.
/etc/init.d/mysql restart

Если не работает еще раз проверьте что у вас происходит:

SHOW VARIABLES LIKE 'character_set%';


проверьте кодировку базы данных:

mysql> show create database yourdatabase;
+-------------+----------------------------------------------------------------------+
| Database | Create Database |
+-------------+----------------------------------------------------------------------+
| yourdatabase | CREATE DATABASE `yourdatabase` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+-------------+----------------------------------------------------------------------+
1 row in set (0.00 sec)


Кодировка по умолчанию для создания таблиц наследуется.

Можно еще проще для ubuntu (10.04):
создать файл, например /etc/mysql/conf.d/mysqld_charset.cnf
с текстом:

[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci

[mysql]
default-character-set=utf8


и перезапустить mysql

понедельник, марта 23, 2009

Интересный момент в mysql 5 для foreign key

CREATE TABLE `key` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`slug` varchar(128) NOT NULL,
`group_id` int(11) NOT NULL,
`parent_id` int(11) default '0',
`comp_id` int(11) default NULL,
PRIMARY KEY (`id`),
KEY `key_FI_1` (`group_id`),
KEY `key_FI_2` (`parent_id`),
CONSTRAINT `key_FK_1` FOREIGN KEY (`group_id`) REFERENCES `key_group` (`id`) ON DELETE CASCADE,
CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8


С первого взгляда все ок.
Теперь попробуем вставить запись:
insert into key (name,group_id) values ('ключ1',1)

выходит ошибка
mysql>ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`database/key`, CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE)


Дело оказывается в `parent_id` int(11) default '0' , т.к. нет записи с id=0.
Правильнее сделать `parent_id` int(11) default NULL.

Мне кажется в propel при генерации из схемы можно сделать проверку на defaultValue, т.к. в купе с foreign key это уже ошибочно в большинстве случаев.

суббота, июня 16, 2007

Разработка ПО на php под Oracle 10.X и MySQL

Даты, время

Используйте CURRENT_TIMESTAMP для совместимости с MySQL вместо NOW(), SYSDATE().


Oracle> select CURRENT_TIMESTAMP from dual;
CURRENT_TIMESTAMP
------------------------------------------
14-JUN-07 02.47.52.987970 PM +04:00



mysql> select CURRENT_TIMESTAMP;
+---------------------+
| CURRENT_TIMESTAMP |
+---------------------+
| 2007-06-14 14:58:23 |
+---------------------+
1 row in set (0.00 sec)


Установите стандартное время для MySQL в формате 'YYYY-MM-DD HH24:MI:SS'
Oracle> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
Session altered.

Старайтесь выкручиваться разными приёмами:
mysql> select hour(timediff(now(), t_timestamp)) from table1;
Oracle> select CEIL(SYSDATE - t_timestamp) from table1;


если неудаётся, то ...
Создайте недостающие функции:

FIND_IN_SET
CREATE OR REPLACE FUNCTION
FIND_IN_SET(TWHAT VARCHAR2, TWHERE VARCHAR2) RETURN INTEGER
IS
BEGIN
DECLARE
CDATA varchar2(4000);
CURSOR TDATA IS
SELECT REGEXP_REPLACE(REGEXP_SUBSTR(TWHERE, ','||TWHAT||',|^'||TWHAT||',|^'||TWHAT||'$|,'||TWHAT||'$'),',','') FROM DUAL;
FROM DUAL;
BEGIN
open TDATA;
FETCH TDATA into CDATA;
close TDATA;
IF (TO_CHAR(CDATA) = TWHAT) THEN
return 1;
ELSE
return 0;
END IF;
END;
END FIND_IN_SET;
/


Тесты:

Oracle> SELECT table1.field1 from table1
WHERE find_in_set('5',table1.field1) >= 1;

table1.field1
-------------
',5,1,2,3,4,'

mysql> SELECT table1.field1 from table1
WHERE find_in_set('5',table1.field1) >= 1;

+---------------+
| table1.field1 |
+---------------+
| ,5,1,2,3,4, |
+---------------+
1 row in set (0.00 sec)


CONCAT_WS

Oracle> CREATE OR REPLACE FUNCTION
CONCAT_WS(PATTERN VARCHAR2,
STR1 VARCHAR2 DEFAULT '',
STR2 VARCHAR2 DEFAULT '',
STR3 VARCHAR2 DEFAULT '',
STR4 VARCHAR2 DEFAULT '',
STR5 VARCHAR2 DEFAULT '',
STR6 VARCHAR2 DEFAULT '',
STR7 VARCHAR2 DEFAULT '',
STR8 VARCHAR2 DEFAULT '',
STR9 VARCHAR2 DEFAULT '',
STR10 VARCHAR2 DEFAULT '',
STR11 VARCHAR2 DEFAULT '',
STR12 VARCHAR2 DEFAULT ''
) RETURN VARCHAR2
IS
BEGIN
DECLARE
CDATA varchar2(4000):='';
BEGIN
IF (LENGTH(STR1) > 0) THEN
CDATA := STR1;
END IF;
IF (LENGTH(STR2) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR2);
END IF;
IF (LENGTH(STR3) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR3);
END IF;
IF (LENGTH(STR4) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR4);
END IF;
IF (LENGTH(STR5) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR5);
END IF;
IF (LENGTH(STR6) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR6);
END IF;
IF (LENGTH(STR7) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR7);
END IF;
IF (LENGTH(STR8) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR8);
END IF;
IF (LENGTH(STR9) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR9);
END IF;
IF (LENGTH(STR10) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR10);
END IF;
IF (LENGTH(STR11) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR11);
END IF;
IF (LENGTH(STR12) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR12);
END IF;
return CDATA;
END;
END;
/

Function created.

Тесты:

Oracle> select concat_ws('::','1','2') from dual;
CONCAT_WS('::','1','2')
------------------------------------------------
1::2

Oracle> select concat_ws('::','1') from dual;
CONCAT_WS('::','1')
------------------------------------------------
1

mysql> select concat_ws('::','1','2');
+-------------------------+
| concat_ws('::','1','2') |
+-------------------------+
| 1::2 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select concat_ws('::','1');
+---------------------+
| concat_ws('::','1') |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)


Но не всегда получается сделать универсальный код или создать недостающие функции, слишком проблематично или сложно, учитывая временные рамки. Чтобы выкрутится, переложите часть забот с sql на плечи вашего скриптового языка.

PHP
Если множество решений для разработки приложений поддерживающих множество СУБД. Можно пойти:

1) путем обработки запроса регулярными выражениями подаваемого в php-класс под каждую СУБД(пример в исходных кодах phpbb.com, 3-я версия поддерживает mysql, oracle, postgresql).
Преимущества:

  • можно добавить поддержку СУБД на любом этапе разработки;

Недостатки

  • сложность в составлении регулярных выражениях, есть вероятность ошибки, т.е. в равно приходится возвращаться к старому коду;

2) путем обработки запроса перед тем как подать его в php-класс.
Преимущества:

  • надежность кода, вы всегда знаете как будет выполняться тот или иной запрос;

Недостатки

  • для добавления поддержки новой СУБД придется изменять все запросы во всем коде;

Я выбрал 2 способ:


if($config['driver'] == "mysql"){
$db_limit = " LIMIT 1";
} else {
if($['driver'] == "oracle"){
$db_limit = " AND rownum = 1";
}
}
$sql = "
SELECT field1 f1,field2 f2
FROM table1
WHERE f1 = f2 ".$db_limit;

Если запрос сложнее, то необходимо обрабатывать его начиная с блока WHERE:
(заметьте, что в блоке GROUP BY для Oracle приходится перечислять все поля)


if($config['driver'] == "mysql"){
$db_stuff = "
WHERE f1 = f2
GROUP BY field1
ORDER BY field1 DESC
LIMIT 1";
} else {
if($['driver'] == "oracle"){
$db_stuff = "
WHERE f1 = f2 AND rownum = 1
GROUP BY field1,field2
ORDER BY field1 DESC";
}
}
$sql = "
SELECT field1 f1,field2 f2
FROM table1 ".
$db_stuff;

Еще пример:

if($config['driver'] == "mysql"){
$db_hour = "hour(timediff(now(), t_timestamp))";
} else {
if($config['driver'] == "oracle"){
$db_hour = "CEIL(SYSDATE - t_timestamp)";
}
}
$sql = "
SELECT field1 f1,field2 f2, ".
$db_hour." t_hour"
."FROM table1"


ORA ERRORS

ORA-00923: ключевое слово FROM не найдено там, где оно ожидалось.
возникает, если использовать ключевые слова: number, char, date , ...
в конструкциях:

sql> select somename number, somedate date from sometable;

ORA-00933: неверное завершение SQL-предложения

Возникает, если вы использовали посторонние символы.
Как правило это 'AS', Oracle не понимает эту конструкцию:

Oracle> SELECT table1.field1 AS f FROM table1;

Другие статьи:

  1. Установка и настройка Oracle
  2. Разработка ПО на php под Oracle 10.X и MySQL
  3. Перенос данных с Mysql на Oracle

пятница, июня 15, 2007

Перенос данных с Mysql на Oracle


Есть самый простой вариант - это MySQL Migration Toolkit(флэш-туториал), доступен под все платформы. Очень удобно и просто.

Но если вам нужна миграция под ваши потребности и вы хотите понять как все это работает, то предлагаю сделать все самими своими руками.

Для начала создайте копию структуры базыданных с MySQL в Oracle:

mysqldump --no-data -uUSER -pPASSWORD -hHOST DATABASE

понятно, что вместо USER, PASSWORD, HOST, DATABASE вписать свои значения.

Измените вручную описание таблиц и помните, что длина имен таблиц и полей не должна превышать 30 символов:

mysql> CREATE TABLE some_table (
table_id INT(11) NOT NULL AUTO_INCREMENT,
table_field_name1 INT(11) NOT NULL,
table_field_name2 VARCHAR(255) NULL DEFAULT 'some',
table_field_name3 TEXT NOT NULL,
PRIMARY KEY (table_id),INDEX(table_field_name1)
);
Query OK, 0 rows affected (0.05 sec)



oracle> CREATE TABLE some_table (
table_id NUMBER(11) NOT NULL,
table_field_name1 NUMBER(11) NOT NULL,
table_field_name2 VARCHAR2(255) NOT NULL,
table_field_name3 VARCHAR2(4000) NOT NULL
);
Table created.


Пропало описание AUTO_INCREMENT - в Oracle для этого существует sequences(последовательности), PRIMARY KEY и INDEX - ключи создаются иначе, DEFAULT 'some' - в Oracle не совместимо понятие NULL и DEFAULT, т.е. надо выбрать либо DEFAULT 'some', либо NULL.

Изменилось описание INT на NUMBER, VARCHAR - VARCHAR2, TEXT - VARCHAR2(4000). Таблицу соответствия можно посмотреть здесь

Далее сделайте дамп данных с MySQL:
span class="code">
mysqldump --add-drop-table --default-character-set=latin1 --no-create-info --single-transaction=TRUE --add-locks=FALSE --extended-insert=FALSE -uUSER -pPASSWORD -hHOST DATABASE > data.sql

Важным параметром является single-transaction=TRUE - создаст записи по одной строчке, что позволит по строчно обработать данные с помощью sed.


  1. cat './tables/table1.table' \
  2. | sed -e 's/[`]//g' \
  3. | sed -e 's/^\/\*/\-\-/' \
  4. | sed -e 's/^LOCK TABLES/\-\-/' \
  5. | sed -e 's/^UNLOCK TABLES/\-\-/' \
  6. | sed -e \"s/'\(\-\)\{0,1\}\([0-9]\{1,\}\)\,\([0-9]\{0,2\}\)'/'\1\2.\3'/g\" \
  7. | sed -e \"s/'0000-00-00 00:00:00'/NULL/\" > 'tables/table1.sql'



Пояснение:

  1. построчное чтение
  2. замена всех символов `
  3. замена коментариев начинающихся с /* на --
  4. комментирование строк начинающихся с LOCK TABLES
  5. комментирование строк начинающихся с UNLOCK TABLES
  6. замена строкового значения числа вида '99,99' на 99.99 (об этом ниже)
  7. замена '0000-00-00 00:00:00' на NULL


В базе данных MySQL были строковые поля к которых были числа в формате 99,99 и 99.99, было решено при переходе на Oracle привести эти поля в тип NUMBER и вставка '99,99' привела бы к ошибке.

После того как данные подготовлены, внести их можно из sqlplus:

sqlplus> @./tables/table1.sql

Теперь когда целостность данных сохранена, можно создать sequences(последовательности), которые заменяют AUTO_INCREMENT в MySQL.
Создадим одну последовательность на все таблицы, и чтобы небыло дублирования id-номеров, последовательность нужно начать с максимального значения id-поля среди всех таблиц.

<?php
$oracle = array(
'name'=>'DB_NAME',
'user'=>'USER_NAME',
'password'=>'PASSWORD',
'host'=>'XXX.XXX.XXX.XXX',
'port'=>'PORT');
$mode = OCI_COMMIT_ON_SUCCESS;

$config = "
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS =
(COMMUNITY = PHP)
(PROTOCOL = TCP)
(Host = {$oracle['host']})
(Port = {$oracle['port']})
)
)
(CONNECT_DATA =
(SERVICE_NAME = {$oracle['name']})
)
)";

$link = oci_connect($oracle['user'],
$oracle['password'],
$config)
or die("Couldn't connect");

#Читаю список таблиц
$tables = file("tables.dat");
array_shift($tables);
$max_row_id = 1;
foreach($tables as $table){
$table = preg_replace("/[\r\n]/","",$table);
$sql = "SELECT * FROM $table WHERE rownum=1 ORDER BY 1 DESC \n";
$result = oci_parse($link, $sql);
oci_execute($result,$mode);
$row = oci_fetch_row($result);
if($row[0] > $max_row_id){
$max_row_id = $row[0];
}
$column_name = oci_field_name($result, 1);
#Создаем триггер для последовательности
$sql_trigger[] = "
CREATE OR REPLACE trigger \"TG_".strtoupper($table)."_BI\"
before insert on \"".strtoupper($table)."\"
for each row
begin
select \"MY_SEQ\".nextval into :NEW.".$column_name." from dual;
end TG_$table;
/";

}
print "Create sequence and start from $max_row_id+1\n";
#Удаляем последовательность
print $sql = "DROP sequence MY_SEQ ;/\n";
#Создаем последовательность
print $sql = "CREATE sequence MY_SEQ START WITH ".($max_row_id+1)." ;/\n";

#Создаем триггеры
foreach($sql_trigger as $sql){
print $sql."\n";
#$db->($sql);
}

oci_close($link);
?>


Все файлы для миграции можно скачать одним архивом здесь

Дополнительные ссылки:

  1. переход к другой кодировке в MySQL
  2. Мануал по sed


Другие статьи:

  1. Установка и настройка Oracle
  2. Разработка ПО на php под Oracle 10.X и MySQL
  3. Перенос данных с Mysql на Oracle

четверг, ноября 09, 2006

Не используйте условия IF в MySQL запросах

При изучении почти всех языков, начинающим разработчикам говорят: "не используйте абсолютные переходы goto", кроме языка Perl, что означает "старайтесь не использовать" или
"используйте, но крайне редко". Тоже я могу сказать и про IF в MySQL: "старайтесь не использовать конструкцию IF в MySQL запросах".
Разрабатывая внутреннюю программу для фирмы на php+mysql в силу обстоятельств мне пришлось прибегнуть к такой конструкции при запросе в MySQL:


SELECT
t1.*, t2.*, t3.*
FROM
t1, t2, t3
WHERE
IF (t1.field1 = 0,
t1.field2 = t3.field2,
t1.field3 = t2.field3
)
GROUP BY t1.field3
ORDER BY DESC



Что, на первый взгляд, спасало положение, но я проверял это на 5, максимумна 10 записях в таблице. При введение в эксплуотацию этого кода при количестве записей 200 и выше, сервер MySQL просто "вешался". Страница генерировалась от 60 до 250 секунд. Сначала я грешил что я использую слишком много запросов в внешней базе данных на другом хосте, но даже исключение этих запросов не помогло.
Как я понял дело заключается в механизме IF, т.е. код просит пройти все записи таблицы t1, где поле field1 = 0
и выбрать данные или из t2 или из t3. Получается очень долгий запрос.
Пришлось потратить больше времени и переделать структуру программы. Еще раз прихожу к выводу, что нельзя экономить на времени проектировании проекта, но это уже другая тема.

среда, ноября 08, 2006

Переход к другой кодировке на MySQL 5.X

Передо мной встала задача перейдти к другой кодировке в рабочей базе данных MySQL.
Первоначально данные хранились в MySQL 4.Х, но после перехода к MySQL 5 версии данные были внесены в кодировке UTF-8. Причем в MySQL 5 появилась параметр "сравнение", по умолчанию значение которого равно "latin1".

$ mysqldump database_name -uroot -p > base.sql
Установка konwert:
$ apt-get install konwert
Смотрим список доступных фильтров:
$ ls /usr/share/konwert/filters/
Конвертируем:
$ konwert UTF8-cp1251 base.sql -o base-1.sql
Конвертацию под windows можно сделать с помощью программы Штирлиц.
Заменяем текущую кодировку на нужную:
$ cat base-1.sql | awk '{gsub(/latin1/,"cp1251");print}' > base-1251.sql
где latin1 - текущая кодировка БД, cp1251 - новая кодировка.
Тоже самое можно сделать в любом редакторе простой заменой.
И результат заносим в нашу базу:
$ mysql -uroot -p -d database_name