<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>C, PHP, VB, .NET &#187; Бази от Данни</title>
	<atom:link href="http://www.cphpvb.net/category/db/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.cphpvb.net</link>
	<description>дневникът на Филип Петров</description>
	<lastBuildDate>Mon, 30 Jan 2012 16:44:20 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Решение на вариант 1 от изпит редовна сесия 2011</title>
		<link>http://www.cphpvb.net/db/7013-solution-of-the-offroad-problem/</link>
		<comments>http://www.cphpvb.net/db/7013-solution-of-the-offroad-problem/#comments</comments>
		<pubDate>Thu, 19 May 2011 11:38:36 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=7013</guid>
		<description><![CDATA[Задача 1. Да се проектира база от данни за оффроуд състезание. В базата данни се пази информация за екипажите: стартовия номер, имената на пилота и щурмана, марката и модела на автомобила, и кръвните групи за всеки член на екипажа. Състезанието е разделено на различни етапи. В статистическата информация се пази времето на старта,     и времето [...]]]></description>
			<content:encoded><![CDATA[<p><strong>Задача 1</strong>. Да се проектира база от данни за оффроуд състезание. В базата данни се пази информация за екипажите: стартовия номер, имената на пилота и щурмана, марката и модела на автомобила, и кръвните групи за всеки член на екипажа. Състезанието е разделено на различни етапи. В статистическата информация се пази времето на старта,     и времето на пристигане за всеки етап, на всеки автомобил. За всеки етап се пази присъдената му категория сложност от 1 до 10. За всеки етап от състезанието се назначава специален автомобил наречен „евакуатор“, който оказва техническа и медицинска помощ на повредени и катастрофирали автомобили. Също така за всеки етап се записва съдия, който регулира състезанието.<span id="more-7013"></span></p>
<p>Проектирайте ER диаграма на описаната база от данни</p>
<p><em>Решение</em>: Състезанието е едно, т.е. няма смисъл да се пази отделна таблица с негово име, дата или други характеристики. Основните обекти са етапите, съдиите, евакуаторите, екипажите и автомобилите. В задачата не е указано, но ще приемем, че един автомобил може да се кара само от един екипаж (т.е. екипажите не си сменят автомобилите в различните етапи, т.е. както е в реалните състезания), следователно те ще бъдат обединени в една таблица. Статистиката със сигурност ще бъде атрибут на свързващ обект между етапите и екипажите с техните автомобилите. Едно примерно решение е следното:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2011/05/offroad.png"><img class="aligncenter size-medium wp-image-7014" title="ER диаграма на база данни за оффроуд състезание" src="http://www.cphpvb.net/wp-content/uploads/2011/05/offroad-211x300.png" alt="ER диаграма на база данни за оффроуд състезание" width="211" height="300" /></a></p>
<p><em>Забележка</em>: Естествено е възможно един съдия или евакуатор да участва в повече от един етап (даже това е напълно нормално). Тоест за да бъде правилно нормализирана нашата база от данни ние би следвало да ги отделим в отделни таблици с връзки 1:M. Понеже не се очаква едно състезание да има прекалено много етапи, в настоящото примерно решение това не е направено. Ако бъде реализирано не е грешка, даже напротив.</p>
<p><strong>Задача 2</strong>. Създайте базата данни чрез езика SQL.</p>
<p><em>Решение</em>: Използваме синтаксиса на MySQL:</p>
<pre>CREATE TABLE stages(
 number INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 level INT(2) UNSIGNED NOT NULL DEFAULT 5,
 evacuator VARCHAR(255) NOT NULL,
 judge VARCHAR(255) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE crews(
 start_number INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
 name_pilot VARCHAR(255) NOT NULL,
 name_assistant VARCHAR(255) NOT NULL,
 blood_pilot ENUM("A+","A-","B+","B-","0+","0-","AB+","AB-"),
 blood_assistant ENUM("A+","A-","B+","B-","0+","0-","AB+","AB-"),
 branch_car VARCHAR(255) NOT NULL,
 model_car VARCHAR(255) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE stats(
 crew_start_number INT UNSIGNED,
 stage_number INT UNSIGNED,
 PRIMARY KEY(crew_start_number,stage_number),
 start TIMESTAMP NULL DEFAULT NULL,
 finish TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB;

ALTER TABLE stats
ADD FOREIGN KEY (crew_start_number)
    REFERENCES crews(start_number) ON DELETE CASCADE ON UPDATE CASCADE,
ADD FOREIGN KEY (stage_number)
    REFERENCES stages(number) ON DELETE CASCADE ON UPDATE CASCADE;</pre>
<p>За решението на задачата НЕ е нужно, но за демонстрация ще вмъкнем и примерни данни:</p>
<pre>INSERT INTO stages(number, level, evacuator, judge)
VALUES (1,3,"Ivan Ivanov","Dimitar Dimitrov"),
       (2,5,"Stoian Stoianov","Dimitar Dimitrov"),
       (3,8,"Petar Petrov","Evgeni Evgeniev"),
       (4,10,"Ivan Ivanov","Dimitar Dimitrov");

INSERT INTO crews(start_number, name_pilot, name_assistant, blood_pilot,
                  blood_assistant, branch_car, model_car)
VALUES (1,"Ventcislav Ivanov","Stefan Kozarov","A+","0-","UAZ","469"),
       (2,"Krasimir Avramov","Petar Avramov","A-","A+","Nissan","Patrol"),
       (3,"Dimitar Dechev","Asen Bratanov","AB+","AB+","Lada","Niva 1.7i"),
       (4,"Iuri Petrov","Todor Alexiev","0+","B-","UAZ","Patriot"),
       (5,"Atanas Zhelev","Ivan Stoev","A+","B-","Lada","Niva 1.6"),
       (6,"Ivan Alexandrov","Philip Trifonov","A+","AB+","Toyota","Land Cruiser");

INSERT INTO stats(crew_start_number, stage_number, start, finish)
VALUES (1,1,20110518094400,20110518101539),
       (2,1,20110518101600,20110518105619),
       (3,1,20110518105800,20110518111030),
       (4,1,20110518111100,20110518115122),
       (5,1,20110518115300,20110518121913),
       (6,1,20110518122000,20110518125910),
       (1,2,20110518133000,20110518135012),
       (2,2,20110518135200,20110518141500),
       (3,2,20110518141600,NULL),
       (4,2,20110518143500,20110518145806),
       (5,2,20110518150000,20110518153219),
       (6,2,20110518153300,20110518155113),
       (1,3,20110518160000,NULL),
       (2,3,20110518163000,20110518165555),
       (4,3,20110518165700,20110518172133),
       (5,3,20110518172200,NULL),
       (6,3,20110518173000,20110518175320),
       (1,4,20110518180000,20110518181951),
       (2,4,20110518182200,20110518184932),
       (4,4,20110518185200,NULL),
       (6,4,20110518190000,20110518193359);</pre>
<p><strong>Задача 3</strong>. Изведете списък с генералното класиране в състезанието. Формулата, по която се изчислява е: сумата от (сложност на етап / време на завършване).</p>
<p><em>Решение</em>: Понеже сложността на етапите е число от 1 до 10, а разликите във времената са много голями, за да не се получават прекалено малки числа в следващата заявка съм умножил получения резултат по 10000:</p>
<pre>SELECT crews.start_number, crews.name_pilot, crews.name_assistant,
       crews.branch_car, crews.model_car,
       SUM(stages.level*10000/(stats.finish-stats.start)) AS points
FROM crews
JOIN stats ON crews.start_number = stats.crew_start_number
JOIN stages ON stages.number = stats.stage_number
WHERE stats.finish IS NOT NULL
GROUP BY crews.start_number
ORDER BY points DESC;</pre>
<p><em>Забележка</em>: В задачите на изпита погрешно беше отбелязано, че формулата е &#8222;сложността на етапа уможена по времето на завършване&#8220;. Това естествено обръща резултите в полза на по-слабите участници (те ще събират повече точки), т.е. там класирането трябваше да бъде в обратен на показания по-горе ред (като проблемни в този вид на решение на задачата идват незавършилите състезатели &#8211; за тях би трябвало да се предвижда някакво наказание извън тази задача). Логиката на конструиране на заявката обаче си остава абсолютно същата.</p>
<p><strong>Задача 4</strong>. Изведете списък с имената на пилотите, марката и моделите на автомобилите, които не са успели да завършат етап номер 3.</p>
<p><em>Решение</em>: Тук трябва да се досетим, че за незавършил автомобил се смята не само този, който е стартирал и се е провалил (т.е. в колона &#8222;finish&#8220; има стойност NULL), но също така и този, които въобще не е стартирал етапа (в нашите примерни данни има такава кола):</p>
<pre>SELECT crews.name_pilot, crews.branch_car, crews.model_car
FROM crews
WHERE crews.start_number IN(
          SELECT stats.crew_start_number
          FROM stats
          WHERE stats.finish IS NULL
                AND stats.stage_number = 3
      )
      OR crews.start_number NOT IN(
          SELECT stats.crew_start_number
          FROM stats
          WHERE stats.stage_number = 3
      );</pre>
<p><em>Забележка</em>: На студентите, които не са се досетили за варианта кола въобще да не е стартирала етап 3, оценките не са им намалявани значително.</p>
<p><strong>Задача 5</strong>. Изведете списък с класацията по марки автомобили, спечелили специалните етапи с категория на сложност от 7 нагоре.</p>
<p><em>Решение</em>: Това очевидно е най-трудната задача, защото изисква специално внимание към групирането на данните за агрегатната функция и вложените заявки във FROM.</p>
<pre>SELECT crews.branch_car, crews.model_car
FROM crews
WHERE crews.start_number IN(
          SELECT t1.crew_num
          FROM(
                SELECT stats.crew_start_number AS crew_num, stages.number AS stage_num,
                       MAX(stages.level*10000/(stats.finish-stats.start)) AS points
                FROM stats JOIN stages ON stats.stage_number = stages.number
                WHERE stages.level&gt;=7 AND stats.finish IS NOT NULL
                GROUP BY stage_num
          ) AS t1
);</pre>
<p><em>Забележка</em>: Реално никой не се справи отлично със задача 5, но на студентите, които бяха подходили правилно им се отчете за вярна.</p>
<p>П.П. Генерирането на картинката и написването на горната статия на Notepad ми отне петдесет и седем минути. В последствие корекциите по кода, за да бъде изпълним на компютър (премахване на синтактични и граматически грешки), ми отнеха още дванадесет минути. Да, задачата е измислена от самия мен, но на изпита имаше още 45 минути аванс :)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/7013-solution-of-the-offroad-problem/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>IN или EXISTS</title>
		<link>http://www.cphpvb.net/db/6947-in-vs-exists/</link>
		<comments>http://www.cphpvb.net/db/6947-in-vs-exists/#comments</comments>
		<pubDate>Mon, 18 Apr 2011 22:09:33 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=6947</guid>
		<description><![CDATA[Нека разгледаме следните примерни две таблици: CREATE TABLE users( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL ) ENGINE=InnoDB; CREATE TABLE orders( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, count INT UNSIGNED NOT NULL, user_id INT UNSIGNED, FOREIGN KEY(user_id) REFERENCES users(id) ) ENGINE=InnoDB; Вкарайте примерни данни разгледайте резултата от [...]]]></description>
			<content:encoded><![CDATA[<p>Нека разгледаме следните примерни две таблици:<span id="more-6947"></span></p>
<pre>CREATE TABLE users(
   id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
   username VARCHAR(255) NOT NULL UNIQUE,
   password VARCHAR(255) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE orders(
   id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
   count INT UNSIGNED NOT NULL,
   user_id INT UNSIGNED,
   FOREIGN KEY(user_id) REFERENCES users(id)
) ENGINE=InnoDB;</pre>
<p>Вкарайте примерни данни разгледайте резултата от следните заявки:</p>
<pre>SELECT orders.count
FROM orders
WHERE orders.user_id IN(
    SELECT users.id
    FROM users
    WHERE users.username = "petar"
);

SELECT orders.count
FROM orders
WHERE EXISTS(
    SELECT *
    FROM users
    WHERE users.username="petar"
      AND users.id = orders.user_id
);</pre>
<p>Очаквано двете заявки дават един и същи изход. Разликата в изпълнението им обаче е съществена. EXISTS търси просто за наличие на ред, докато IN проверява стойностите. Реално условието (users.id = orders.user_id) присъства и в двете заявки, но във втората е зададено явно като ограничаващо условие във вложената заявка, докато в първата е свързващо условие между двете таблици.</p>
<ul>
<li>При използването на оператор IN първо ще се вземе стойност от външната заявка и след това ще се провери спрямо върнатите стойности от вложената. Вложената ще върне като резултат колона с уникални (distinct) и индексирани стойности. Естествено този резултат ще бъде кеширан (т.е. ще бъде преизползван в бъдеще &#8211; нещо, с което в миналото е имало проблеми) и ще бъде преизползван за следващите проверки със стойности от основната заявка;</li>
<li>При използването на оператор EXISTS винаги ще се прави &#8222;full table scan&#8220; на таблица &#8222;orders&#8220;. От това пълно нейно прелистване ще се изведат само тези редове на които условието във вложената заявка е изпълнено.</li>
</ul>
<p>Кое от двете е по-добро? В общи линии можем да твърдим, че заявките написани с EXISTS в повечето случаи ще са по-бързи, а понякога значително по-бързи. При тях отпада нуждата от правене на &#8222;distinct&#8220; и индексиране на резултата от вложената заявка. Практически ние директно сме указали на MySQL, че не желаем всички редове от вложената заявка, а явно указваме ограничението, т.е. един вид &#8222;помагаме на оптимизатора&#8220;.</p>
<p>Техниката използвана с EXISTS ще бъде много по-добър избор тогава, когато таблица &#8222;orders&#8220; е с малко записи, а &#8222;users&#8220; е с много и особено когато е с повтарящи се записи по колоната за сравнение. Така въпросният &#8222;full table scan&#8220; няма да отнеме много време, а вложената заявка ще бъде много по-ефективна. Обратно &#8211; ако таблицата от вложената заявка е малка, а таблицата от главната заявка е голяма, то ефектът ще бъде в полза на оператор IN, но с малко. Така, че <span style="text-decoration: underline;">в общия случай заявката преработена с EXISTS ще бъде по-добрия вариант от IN</span>. Естествено проверката на това в реална ситуация е силно препоръчителна и няма да навреди с нищо. Използвайте тази техника, която ви даде по-добри реални резултати в конкретния случай.</p>
<p>Много важно е да отбележим, че правилното индексиране е от основно значение за бързината на изпълнение на заявките и в двата случая. В горния пример users.id е PK и има индекс по подразбиране, но за orders.user_id е задължително да бъде създаден такъв. При изпълняване на заявки, в които аналогът на users.id не е PK е задължително да се направи индекс и по нея.</p>
<p>Реални проблеми при избора между IN и EXISTS идват тогава, когато в една от колоните за сравнение (или и при двете) има възможност за стойности &#8222;NULL&#8220; и ние трябва да ги отчитаме. За повече информация прочетете <a title="IN vs EXISTS in MYSQL 5.6" href="http://dev.mysql.com/doc/refman/5.6/en/in-subquery-optimization.html" target="_blank">раздела в документацията на MySQL</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/6947-in-vs-exists/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Join или вложен Select?</title>
		<link>http://www.cphpvb.net/db/6909-join-%d0%b8%d0%bb%d0%b8-%d0%b2%d0%bb%d0%be%d0%b6%d0%b5%d0%bd-select/</link>
		<comments>http://www.cphpvb.net/db/6909-join-%d0%b8%d0%bb%d0%b8-%d0%b2%d0%bb%d0%be%d0%b6%d0%b5%d0%bd-select/#comments</comments>
		<pubDate>Sat, 02 Apr 2011 12:44:48 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=6909</guid>
		<description><![CDATA[Въпросът поставен в заглавието на статията е много често разискван и около него се водят спорове. По принцип има една тенденция програмистите категорично да избягват вложените select заявки, защото още от миналото има един мит, че те се изпълняват бавно. Този мит се дължи главно на грешки в СУБД, които не са използвали правилно индексите [...]]]></description>
			<content:encoded><![CDATA[<p>Въпросът поставен в заглавието на статията е много често разискван и около него се водят спорове. По принцип има една тенденция програмистите категорично да избягват вложените select заявки, защото още от миналото има един мит, че те се изпълняват бавно. Този мит се дължи главно на грешки в СУБД, които не са използвали правилно индексите при вложените заявки. Днес това отдавна вече (почти) не се среща, т.е. можем да очакваме вложените заявки да вървят достатъчно добре. Така въпросът &#8222;join или вложен select&#8220; отново стои на дневен ред.</p>
<p>Ще демонстрираме с един пример. Нека имаме следната база от данни:<span id="more-6909"></span></p>
<pre>CREATE TABLE clients(
   id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(255) NOT NULL) ENGINE=InnoDB;

CREATE TABLE products(
   id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
   name VARCHAR(255) NOT NULL) ENGINE=InnoDB;

CREATE TABLE orders(
   client_id INT UNSIGNED NOT NULL,
   product_id INT UNSIGNED NOT NULL,
   quantity INT UNSIGNED NOT NULL,
   PRIMARY KEY(client_id,product_id),
   FOREIGN KEY (client_id) REFERENCES clients(id),
   FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB;

INSERT INTO clients(id, name)
VALUES (NULL, "Ivan"),
       (NULL, "Petar"),
       (NULL, "Maria"),
       (NULL, "Philip");

INSERT INTO products(id, name)
VALUES (NULL, "GSM"),
       (NULL, "Bike"),
       (NULL, "Icecream");

INSERT INTO orders(client_id, product_id, quantity)
VALUES (1, 1, 2),
       (1, 3, 4),
       (2, 1, 1),
       (2, 2, 1),
       (2, 3, 2),
       (3, 2, 2),
       (4, 3, 1);</pre>
<p>Поставяме си да решим следната задача: <span style="text-decoration: underline;">изведете id-тата на хората, които са поръчали продукти 1 или 2</span>. Впечатление прави условието &#8222;или&#8220;, както също така, че връзката между clients и products е от тип M:M.</p>
<p>Първо ще дадем естественото решение с вложен select:</p>
<pre>SELECT clients.name
FROM clients
WHERE clients.id IN(
      SELECT orders.client_id
      FROM orders
      WHERE product_id IN (1,2)
);</pre>
<p>Очаквано отговорът на СУБД е:</p>
<pre>+-------+
| name  |
+-------+
| Ivan  |
| Petar |
| Maria |
+-------+</pre>
<p>При използването на решение с join положението не е толкова просто. Следният пример е грешен:</p>
<pre>SELECT clients.name
FROM clients JOIN orders ON clients.id=orders.client_id
WHERE orders.product_id IN (1,2);</pre>
<p>Ще видим, че в резултатът от него има повторения:</p>
<pre>+-------+
| name  |
+-------+
| Ivan  |
| Petar |
| Petar |
| Maria |
+-------+</pre>
<p>За да се избавим от повторенията ще е нужно да използваме ключова дума DISTINCT:</p>
<pre>SELECT <strong>DISTINCT</strong> clients.name
FROM clients JOIN orders ON clients.id=orders.client_id
WHERE orders.product_id IN (1,2);</pre>
<p>Така заявката вече ще върне правилен резултат.</p>
<p>Коя от двете заявки беше по-добра? Отговорът еднозначно е, че това е тази, която е с вложен select! При заявката с join първо се генерира таблицата с повторенията, а чак след това се премахнаха дублиращите се. При вложен select дублирания въобще нямаше. Ако направите по-сериозен тест с огромно количество данни с много повторения и създадени индекси по съответните колони, то ще видите, че заявката с вложен select ще се изпълни видимо по-бързо.</p>
<p>Когато използвате SELECT DISTINCT &#8230; WHERE&#8230; заявки (или GROUP BY), то СУБД първо ще създаде временна свързваща таблица и чак тогава ще ги агрегира. При вложен select това не се случва, съответно не се правят безсмислени операции.</p>
<p>Ето още един начин да направим същата заявка, но вместо IN ще използваме EXISTS:</p>
<pre>SELECT clients.name
FROM clients
WHERE EXISTS(
      SELECT * FROM orders
      WHERE orders.product_id IN (1,2) AND orders.client_id=clients.id
);</pre>
<p>Дали IN или EXISTS е по-добър подход ще разгледаме по-късно в отделна статия. При всички положения обаче вложения select би трябвало да дава по-добри резултати от използването на join като негова алтернатива.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/6909-join-%d0%b8%d0%bb%d0%b8-%d0%b2%d0%bb%d0%be%d0%b6%d0%b5%d0%bd-select/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Симулиране на CHECK с VIEW</title>
		<link>http://www.cphpvb.net/db/5671-%d1%81%d0%b8%d0%bc%d1%83%d0%bb%d0%b8%d1%80%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-check-%d1%81-view/</link>
		<comments>http://www.cphpvb.net/db/5671-%d1%81%d0%b8%d0%bc%d1%83%d0%bb%d0%b8%d1%80%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-check-%d1%81-view/#comments</comments>
		<pubDate>Thu, 01 Apr 2010 13:46:54 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=5671</guid>
		<description><![CDATA[В предишна статия свързана с ограниченията CHECK писахме за нещо изключително неприятно &#8211; не се поддържат от MySQL. В същия момент именно CHECK понякога е доста важно за интегритета на данните когато пишем в &#8222;несигурна среда&#8220;, т.е. работим с програми, на които не можем да вярваме. Първият и най-качествен вариант за справяне с проблема е [...]]]></description>
			<content:encoded><![CDATA[<p>В предишна статия свързана с <a href="http://www.cphpvb.net/db/5388-check-constraint/" target="_blank">ограниченията CHECK</a> писахме за нещо изключително неприятно &#8211; не се поддържат от MySQL. В същия момент именно CHECK понякога е доста важно за интегритета на данните когато пишем в &#8222;несигурна среда&#8220;, т.е. работим с програми, на които не можем да вярваме.<span id="more-5671"></span></p>
<p>Първият и най-качествен вариант за справяне с проблема е да не даваме директен достъп за въвеждане и промяна на данни в базата данни. Вместо това можем да направим набор от вградени процедури, които да правят проверка на входните параметри. За нещастие в повечето случаи писането на процедури за всяка ситуация е много трудоемка работа, а и кода става много по-трудно преносим.</p>
<p>Вторият доста популярен вариант е с използването на тригери (процедури, които се задействат при insert и update). По мое мнение обаче той трябва да се избягва за бази данни със сложен дизайн, защото тригерите не се активират при каскадно обновяване на данните. Може би ще разгледаме подобна функционалност когато стигнем до писане и на статия свързана с тригери.</p>
<p>Методът на който ще се спрем на пръв поглед е доста странен &#8211; задаване на функционалност CHECK чрез VIEW. Фундаменталната основа, на която ще стъпим е, че в MySQL върху VIEW може да се изпълняват INSERT, UPDATE и DELETE заявки. Именно това тяхно свойство ни позволява да заобиколим недостатъка от липсата на ограничение CHECK. Нека например вземем една съвсем проста таблица с продукти и техните цени:</p>
<pre>CREATE DATABASE viewcheckexample;

USE viewcheckexample;

CREATE TABLE products(
	`id` INT AUTO_INCREMENT,
	PRIMARY KEY(id),
	`name` VARCHAR(255) NOT NULL,
	`price` DOUBLE NOT NULL,
	UNIQUE(name, price),
	<strong>CHECK(price&gt;2)</strong>
) ENGINE=INNODB;</pre>
<p>Както знаем условието CHECK ще бъде прието, но просто ще бъде пропуснато от MySQL, т.е. спокойно можем да вкараме продукт с price=0. Нека обаче направим изглед (VIEW), от който генерираме същата таблица, но с въведеното условие от проверката:</p>
<pre>CREATE VIEW products_view
AS SELECT * FROM products
   WHERE price&gt;2
   WITH CASCADED CHECK OPTION;</pre>
<p>От тук нататък можем да проверим, че условието работи ако се обръщаме към въпросното VIEW:</p>
<pre>INSERT INTO products_view(name, price)
VALUES ('A', <strong>0</strong>);
<strong>ERROR 1369 (HY000): CHECK OPTION failed 'viewcheckexample.products_view'</strong>

INSERT INTO products_view(name, price)
VALUES ('A', 5);
Query OK, 1 row affected (0.00 sec)

UPDATE products_view
SET <strong>price=0</strong>
WHERE id=1;
<strong>ERROR 1369 (HY000): CHECK OPTION failed 'viewcheckexample.products_view'</strong>

UPDATE products_view
SET price=50
WHERE id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0</pre>
<p>Недостатък при използвания подход е, че винаги съобщението за грешка е едно и също, т.е. потребителят няма да бъде уведомен какво точно не е в ред. И все пак въпреки, че показаната функционалност работи, ще бъде по-добре ако от MySQL просто добавят нужната функционалност колкото се може по-бързо. Остава да чакаме&#8230;</p>
<p><strong>Задача</strong>: „Поправете“ всички досега разгледани примерни бази данни, като създадете необходимите VIEW при таблици с повече от един външен ключ и по този начин създадете алтернатива на CHECK условия за интегритет.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/5671-%d1%81%d0%b8%d0%bc%d1%83%d0%bb%d0%b8%d1%80%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-check-%d1%81-view/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>MySQL OFFSET</title>
		<link>http://www.cphpvb.net/db/5513-mysql-offset/</link>
		<comments>http://www.cphpvb.net/db/5513-mysql-offset/#comments</comments>
		<pubDate>Thu, 18 Mar 2010 02:28:15 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=5513</guid>
		<description><![CDATA[Още в началото, когато се разглеждаха заявки за еднотабличен оператор SELECT, набързо се разгледа оператор LIMIT. Да припомним &#8211; той приемаше за параметър целочислено число X, чрез което от резултатната таблица се връщат само първите X реда, а останалите &#8222;се отрязват&#8220;. Това естествено има редица приложения &#8211; разглеждане на най-новите записи от статистики, разглеждане на [...]]]></description>
			<content:encoded><![CDATA[<p>Още в началото, когато се разглеждаха <a href="http://www.cphpvb.net/db/1140-%D0%B7%D0%B0%D1%8F%D0%B2%D0%BA%D0%B8-%D0%B7%D0%B0-%D0%B5%D0%B4%D0%BD%D0%BE%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%87%D0%B5%D0%BD-%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80-select/" target="_blank">заявки за еднотабличен оператор SELECT</a>, набързо се разгледа оператор LIMIT. Да припомним &#8211; той приемаше за параметър целочислено число X, чрез което от резултатната таблица се връщат само първите X реда, а останалите &#8222;се отрязват&#8220;. Това естествено има редица приложения &#8211; разглеждане на най-новите записи от статистики, разглеждане на &#8222;най-добрите&#8220; резултати от състезание, извеждане на последните записи в таблица и т.н. Почти винаги, за такива случаи, оператор LIMIT е предхождан от ORDER BY.<span id="more-5513"></span></p>
<p>Съвсем логичен въпрос обаче идва по-късно: &#8222;а какво да правим ако искаме не първите, а вторите X реда?&#8220;. Ето ви стандартен пример &#8211; искате да направите сайт, в който да направите &#8222;страниране&#8220; на публикациите. На първо страница ще показвате първите 10 резултата, на втора резултатите от 11 до 20, на трета от 21 до 30, и т.н. Тук определено ще срещнете затруднение в писането на заявки за извеждането на по-задните страници.</p>
<p>Първият подход е да се използва т.нар. OFFSET. Нека например имаме таблица с много продукти. Ако желаем да изведем на екрана всички продукти от 101 до 110, то ще напишем следната заявка:</p>
<pre>   SELECT id, name
   FROM products
   ORDER BY id
   LIMIT 10 OFFSET 100;
</pre>
<p>OFFSET има смисъл на &#8222;отместване&#8220;. Тук все пак трябва да споменем, че колкото по-голямо отместване правим, толкова по-бавно се изпълнява заявката. Затова при доста големи бази данни тези заявки биха породили сериозен проблем откъм производителност.</p>
<p>Алтернативата е да се правили лимитиране в WHERE клаузата. Например ако във въпросната таблица products сме убедени, че в id на продуктите &#8222;няма дупки&#8220;, т.е. липсващи id, то можем да напишем горната заявка като:</p>
<pre>   SELECT id, name
   FROM products
   WHERE id BETWEEN 101 AND 110
   ORDER BY id;
</pre>
<p>Тази заявка ще се изпълни доста по-бързо от предишната, но за съжаление със съответната цена. Ясно е, че при изтриване на един продукт с id в интервала [101, 110] ще се получи въпросната &#8222;дупка&#8220; и върнатите резултати няма да са 10, а 9. Затова използването на този подход следва да се прави само ако стриктно контролираме id-тата на продуктите (в случая от примера). Това означава, че при изтриване на продукт с id X ние трябва да се погрижим да понижим с 1 id-тата на всички продукти с id по-голямо от X, или още по-добре &#8211; да променим id-то на последния въведен продукт в системата на X, като по този начин предотвратим тази &#8222;дупка&#8220;. Както се досещате това евентуално може да доведе до редица други проблеми, както и доста по-голяма загуба на производителност, особено ако често изпълняваме заявки DELETE върху таблицата.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/5513-mysql-offset/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>CHECK constraint</title>
		<link>http://www.cphpvb.net/db/5388-check-constraint/</link>
		<comments>http://www.cphpvb.net/db/5388-check-constraint/#comments</comments>
		<pubDate>Thu, 25 Feb 2010 13:00:02 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=5388</guid>
		<description><![CDATA[В статията за вложен SELECT представихме едно допълнение към ER диаграмата за база от данни на университет. Да припомним &#8211; проблемът беше, че в оригиналния ER модел връзката между студенти и факултети минаваше през записани учебни предмети. Така ние нямаше възможност да разберем от кой факултет е даден студент, ако той не е записал нито [...]]]></description>
			<content:encoded><![CDATA[<p>В статията за вложен SELECT представихме едно допълнение към ER диаграмата за база от данни на университет. Да припомним &#8211; проблемът беше, че в оригиналния ER модел връзката между студенти и факултети минаваше през записани учебни предмети. Така ние нямаше възможност да разберем от кой факултет е даден студент, ако той не е записал нито един учебен предмет. Предложеното решение беше да пазим допълнителен външен ключ от таблицата &#8222;студенти&#8220; към таблицата &#8222;факултети&#8220;:<span id="more-5388"></span></p>
<p><img class="aligncenter" title="ER диаграма" src="http://www.cphpvb.net/wp-content/uploads/2009/03/erd.png" alt="" width="350" height="546" /></p>
<p>Това обаче не решава друг основен проблем &#8211; какво гарантира, че дадения студент ще запише само и единствено предмети от този факултет? Отговорът е, че все още нищо не го гарантира! Нека демонстрираме това като построим същата диаграма в опростен вид (без таблици университети, преподаватели и задочници, а директна връзка факултети -&lt; предмети &gt;-&lt; студенти):</p>
<pre>CREATE TABLE faculties(
   `id` INT UNSIGNED AUTO_INCREMENT,
   PRIMARY KEY(id),
   `name` VARCHAR(255) NOT NULL UNIQUE
) ENGINE=INNODB;

CREATE TABLE subjects(
   `id` INT UNSIGNED AUTO_INCREMENT,
   PRIMARY KEY(id),
   `name` VARCHAR(255) NOT NULL,
   `faculty_id` INT UNSIGNED NOT NULL,
   FOREIGN KEY(faculty_id) REFERENCES faculties(id),
   UNIQUE(name, faculty_id)
) ENGINE=INNODB;

CREATE TABLE students(
   `id` INT UNSIGNED AUTO_INCREMENT,
   PRIMARY KEY(id),
   `name` VARCHAR(255) NOT NULL,
   `faculty_id` INT UNSIGNED NOT NULL,
   FOREIGN KEY(faculty_id) REFERENCES faculties(id),
   UNIQUE(name, faculty_id)
) ENGINE=INNODB;

CREATE TABLE subjects_students(
   `student_id` INT UNSIGNED NOT NULL,
   `subject_id` INT UNSIGNED NOT NULL,
   PRIMARY KEY(student_id, subject_id),
   FOREIGN KEY(student_id) REFERENCES students(id),
   FOREIGN KEY(subject_id) REFERENCES subjects(id)
) ENGINE=INNODB;</pre>
<p>Сега да вмъкнем информация в таблиците и да демонстрираме проблема:</p>
<pre>INSERT INTO faculties(`name`)
      VALUES ("FKSU"),("FET");

INSERT INTO subjects(`name`, `faculty_id`)
VALUES ("Bazi Danni", (SELECT id FROM faculties WHERE name="FKSU")),
       ("Sapromat", (SELECT id FROM faculties WHERE name="FET"));

INSERT INTO students(`name`, `faculty_id`)
VALUES ("Ivan Ivanov", (SELECT id FROM faculties WHERE name="FKSU"));</pre>
<p>Така имаме два факултета (FKSU и FET), които имат по един учебен предмет всеки:</p>
<pre>SELECT * FROM faculties;
+----+------+
| id | name |
+----+------+
|  2 | FET  |
|  1 | fksu |
+----+------+

SELECT * FROM subjects;
+----+------------+------------+
| id | name       | faculty_id |
+----+------------+------------+
|  1 | Bazi Danni |          1 |
|  2 | Sapromat   |          2 |
+----+------------+------------+

mysql&gt; SELECT * FROM students;
+----+-------------+------------+
| id | name        | faculty_id |
+----+-------------+------------+
|  1 | Ivan Ivanov |          1 |
+----+-------------+------------+</pre>
<p>Ние сме сигурни, но нека все пак се убедим, че проблема е налице &#8211; ще запишем Иван Иванов едновременно и за двата предмета, които са от различни факултети:</p>
<pre>INSERT INTO subjects_students(subject_id, student_id)
VALUES ((SELECT id FROM subjects WHERE name="Bazi Danni"),
              (SELECT id FROM students WHERE name="Ivan Ivanov")),
       ((SELECT id FROM subjects WHERE name="Sapromat"),
              (SELECT id FROM students WHERE name="Ivan Ivanov"));</pre>
<p>Проблемът вече е налице и имаме невалидна информация в базата от данни:</p>
<pre>SELECT students.name AS student, faculties.name AS faculty
FROM students JOIN faculties ON students.faculty_id = faculties.id
WHERE students.name="Ivan Ivanov";
+-------------+---------+
| student     | faculty |
+-------------+---------+
| Ivan Ivanov | FKSU    |
+-------------+---------+

SELECT students.name AS student, faculties.name AS faculty
FROM students
JOIN subjects_students ON students.id = subjects_students.student_id
JOIN subjects ON subjects.id = subjects_students.subject_id
JOIN faculties ON faculties.id = subjects.faculty_id
WHERE students.name = "Ivan Ivanov";
+-------------+---------+
| student     | faculty |
+-------------+---------+
| Ivan Ivanov | FKSU    |
| Ivan Ivanov | FET     |
+-------------+---------+</pre>
<p>Очевидно, за да се справим с този проблем ние трябва да НЕ позволяваме в таблицата subjects_students да се записват предмети, които са от &#8222;чужд&#8220; факултет. Това може да се постигне чрез ограничение CHECK. Ето как можем да поправим предишната CREATE TABLE заявка, за таблица &#8222;subjects_students&#8220;:</p>
<pre>CREATE TABLE subjects_students(
   `student_id` INT UNSIGNED NOT NULL,
   `subject_id` INT UNSIGNED NOT NULL,
   PRIMARY KEY(student_id, subject_id),
   FOREIGN KEY(student_id) REFERENCES students(id),
   FOREIGN KEY(subject_id) REFERENCES subjects(id),
<strong> CHECK (subject_id = (SELECT subjects.id FROM subjects WHERE subjects.faculty_id=( SELECT students.faculty_id FROM students WHERE students.id = student_id )) )</strong>
) ENGINE=INNODB;</pre>
<p>Така ние въвеждаме ограничение, чрез което забраняваме добавянето на предмет от различен факултет от този на студента. За съжаление тази функционалност все още не работи в СУБД MySQL. До сега (най-новата версия е MySQL 5.1.41) CHECK ограниченията се приемат от MySQL като валидни записи, но в последствие не се проверяват при изпълнение на заявки INSERT (тоест вие можете да продължите да въвеждате невалидни данни). Очаква се подобна функционалност да бъде въведена скоро, но за сега не е налична. Все пак е добре да я познавате, защото в други СУБД като Oracle например функционалността CHECK работи отлично. За момента, при използване на MySQL, е в ръцете на програмиста да се проверяват тези циклични зависимости през софтуера достъпващ базата от данни.</p>
<p>Затова при използване на MySQL засега бъдете внимателни &#8211; интегритета на данните може да бъде нарушен дори когато ограниченията са уж написани перфектно &#8211; различните СУБД имплементират функционалности по различен начин (а както виждате понякога въобще не се имплементират). Ако все пак желаете да реализирате споменатата по-горе функционалност и контрола все пак да бъде на ниво база от данни с MySQL, а не при приложението &#8211; тогава забранете заявки INSERT и въведете алтернатива чрез съхранени процедури.</p>
<p><strong>Задача</strong>: Начертайте опростената ER диаграма на примерната база от данни</p>
<p><strong>Задача</strong>: &#8222;Поправете&#8220; цикличната зависимост в базата от данни banks, която беше разгледана в предишни статии, чрез въвеждане на ограничение/я CHECK.</p>
<p><strong>Задача</strong>: Създайте съхранена процедура за &#8222;записване на студент към предмет&#8220;, при която не се позволява записването към предмет от &#8222;чужд&#8220; факултет.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/5388-check-constraint/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Още за ограниченията UNIQUE</title>
		<link>http://www.cphpvb.net/db/5384-mysql-unique-constraint/</link>
		<comments>http://www.cphpvb.net/db/5384-mysql-unique-constraint/#comments</comments>
		<pubDate>Thu, 25 Feb 2010 11:34:40 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=5384</guid>
		<description><![CDATA[В досега разглежданите примери при CREATE TABLE на няколко пъти показвахме параметър &#8222;UNIQUE&#8220;, който се добавяше след дадена променлива. Например: CREATE TABLE university( `id` INT UNSIGNED AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL UNIQUE, PRIMARY KEY(id) )ENGINE=INNODB; Казахме, че PRIMARY KEY винаги е едновременно NOT NULL и UNIQUE по подразбиране, както и че е възможно да направим [...]]]></description>
			<content:encoded><![CDATA[<p>В досега разглежданите примери при CREATE TABLE на няколко пъти показвахме параметър &#8222;UNIQUE&#8220;, който се добавяше след дадена променлива. Например:</p>
<pre>CREATE TABLE university(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(255) NOT NULL <strong>UNIQUE</strong>,
   PRIMARY KEY(id)
)ENGINE=INNODB;</pre>
<p>Казахме, че PRIMARY KEY винаги е едновременно NOT NULL и UNIQUE по подразбиране, както и че е възможно да направим комбинация от две или дори повече колони заедно PRIMARY KEY.<span id="more-5384"></span></p>
<p>В така зададените възможности не можем обаче да се справим с друг проблем &#8211; какво ще стане ако искаме да направим комбинация от две или повече колони уникални, но в същия момент НЕ желаем да ги правим PRIMARY KEY? Ето един пример &#8211; в горната таблица вместо име на университет ще пазим име и град:</p>
<pre>CREATE TABLE university(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(255) NOT NULL,
   `city` VARCHAR(255) NOT NULL,
   PRIMARY KEY(id)
)ENGINE=INNODB;</pre>
<p>Така ние можем да въведем &#8222;Технически Университет&#8220; от град &#8222;София&#8220;, но и &#8222;Технически Университет&#8220; от град &#8222;Варна&#8220;. Така името не може да бъде UNIQUE, но и града не може да бъде UNIQUE (имаме и &#8222;Софийски Университет&#8220; от &#8222;София&#8220;). Всичко щеше да бъде наред ако не се явяваше проблемът, че никой всъщност не ни спира да въведем &#8222;Технически Университет&#8220; от град &#8222;София&#8220; два или повече пъти. Те ще се различават само по своя id, което обаче си е променлива за вътрешно ползване. Така ние определено се сблъскваме с проблема &#8222;дублиране на информация&#8220;. В най-честия случай ние НЕ желаем това.</p>
<p>Подсказаното решение е да направим комбинацията от тези колони уникална. Това се постига когато &#8222;изнесем&#8220; UNIQUE KEY CONSTRAINT по абсолютно същия начин както го правихме досега с PRIMARY KEY:</p>
<pre>CREATE TABLE university(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(255) NOT NULL,
   `city` VARCHAR(255) NOT NULL,
   <strong>UNIQUE KEY (name, city)</strong>,
   PRIMARY KEY(id)
)ENGINE=INNODB;</pre>
<p>Всъщност спокойно можете да пропуснете думата &#8222;KEY&#8220; и да използвате само &#8222;UNIQUE&#8220;. MySQL ще разбере това съкращение коректно.</p>
<p><strong>Задача</strong>: Проверете в преди показаните примери от базата данни с университет и базата данни с банки дали този проблем с дублиране на записи е наличен в различните таблици и ако да &#8211; поправете ги!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/5384-mysql-unique-constraint/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Нормализация на бази от данни</title>
		<link>http://www.cphpvb.net/db/1876-%d0%bd%d0%be%d1%80%d0%bc%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f-%d0%bd%d0%b0-%d0%b1%d0%b0%d0%b7%d0%b8-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8/</link>
		<comments>http://www.cphpvb.net/db/1876-%d0%bd%d0%be%d1%80%d0%bc%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f-%d0%bd%d0%b0-%d0%b1%d0%b0%d0%b7%d0%b8-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8/#comments</comments>
		<pubDate>Wed, 20 May 2009 20:33:28 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1876</guid>
		<description><![CDATA[Нормализацията на бази от данни е една изключително важна тема, която обаче е по-теоретично насочена и поради тази причина не наблягахме досега на нея. Текстът по-долу е реферат на студента Никола Костадинов Симеонов, който смятам, че много добре описва темата &#8222;нормализация на бази от данни&#8220;&#8230; тема: До къде може да стигнем с номализацията? Никола Костадинов [...]]]></description>
			<content:encoded><![CDATA[<p><em>Нормализацията на бази от данни е една изключително важна тема, която обаче е по-теоретично насочена и поради тази причина не наблягахме досега на нея. Текстът по-долу е реферат на студента Никола Костадинов Симеонов, който смятам, че много добре описва темата &#8222;нормализация на бази от данни&#8220;&#8230;</em></p>
<p style="text-align: center;">тема:</p>
<p style="text-align: center;"><strong>До къде може да стигнем с номализацията?</strong></p>
<p style="text-align: center;">Никола Костадинов Симеонов, ТУ-София, ФКСУ, гр. 57</p>
<p><span id="more-1876"></span></p>
<p>Нормализацията е един от основновните процеси при проектиране на бази данни. Най общо казано нормализацията премахва повтаряемостта и минимизира излишъка от данни. Резултатът е по добра организация,по добра използваемост на паммета и премахване на аномалиите в базата данни. Нормализацията не винаги е най-доброто решение за дадена база данни, например при data Warehouse бази данни се прилага съвсем различен подход(използват се силно денормализирани бази данни). Или иначе казано сляпото прилагане на всички нормални форми може да доведе до катастрофални последици-изключително голям брой таблици,сложни заявки за сливане и най-важното много лоша производителност. Това в крайна сметка може да направи базата данни неизползваема. Така стигаме до въпроса как да намерим баланса?Да избегнем аномалиите и да и във същото време да запазим високата производителност и време за отговор на базата данни.</p>
<p>В този реферат ще се опитам да дам отгоровор на тези въпроси, също така ще разгледам какво точно се прави след 3-та нормална форма ,защо се прави и в кои случаи не е препоръчително да се прави. Най-накрая ще се опитам да обобщя личната ми позиция по тази много важна тема защото тя има важно място в цялостния процес на разробтка на софтуер. Отделянето на достатъчно време за доброто проектиране на базата данни още от началото ще спести много проблеми и усилия по нататък.</p>
<p>Първо нека накратко разгледаме нормалите форми и как се прилагат.</p>
<p><strong>1НФ</strong>: Една таблица е в първа нормална форма, тогава и само тогава, когато не съдържа повтарящи се атрибути или групи от атрибути.</p>
<p><strong>2НФ</strong>: Таблицата трябва да е в 1НФ и всички неключови стойности трябва да са напълно функционално зависими от първичния ключ. Не са позволени частични зависимости.</p>
<p><strong>3НФ</strong>: Премахват се непреките зависимости. Това са полетата които са непряко зависими от първични ключ. Например поле е фунцкционално зависимо на друго поле, а то от своя страна е зависимо от първичния ключ.</p>
<p><strong>Boyce-codd Normal Form (BCNF)</strong>: Всяка детерминанта трябва да бъде кандидат ключ. Детерминант е произволно поле от което са напълно функционално зависими други полета. Ако съществува един единствен кандидат ключ 3НФ и BCNF са едно и също.</p>
<p><strong>4НФ</strong>: Трябва да се премахнат многостойностните зависимости.</p>
<p><strong>5НФ</strong>: Трябва да се премахнат цикличните зависимости</p>
<p><strong>Domain key Normal Form (DKNF)</strong>: Тази форма е по скоро определение на това как трябва да излегжда нормализирана до съвършенство база данни.</p>
<ul>
<li>Не трябва да съществуват аномалии при записване, обновяване или триене на данни. Иначе казано всеки запис трябва да бъде директно достъпен по всякакъв начин така че да не възникват грешки;</li>
<li>Всеки запис във всяка таблица трябва да бъде уникално идентифициран и свързан с първичния ключ в своята таблица. Това означава че всяко поле трябва да е пряко определено от пърчвичния ключ;</li>
<li>Всички проверки за типа на данните се извършват в самата база данни(Като се има предвид производителноста това е крайно нежелателно в комерсиална работна среда. По-добър подход би бил  да се раздели функционалността между базата данни и приложението).</li>
</ul>
<p>Несъмнено прилагането на първите две форми, като създаване на разширяващи(master-detail връзки) в 1НФ и Отделяне на статичните данни в 2НФ е необходимо. Не само че помага за избягване на аномалите те са необходими за да съществува релационния модел. Прилагането на 3НФ също е доста срещано но с всяка следваща форма модела на базата става все по раздробен и броя на таблиците нараства. От там нарастват и SQL заявките за сливане и намалява производителноста.</p>
<p><strong>Защо да отиваме след 3-та нормална форма?</strong></p>
<p>Прилагането на първите 3 нормални форми може да се каже че елиминира 90% от аномалиите при изтриване , обновяване или въвеждане. Използването на останалите 3 се нормални форми се прилагат в някои по специфични случаи за да се „доизглади” модела.</p>
<p>Тук искам да дам няколко нагледни примера как се прилагат формите след 3-та и да покажа защо по мое мнение в много случаи това е излишно и е признак на следване на математическо съвършенство а не проектиране на работна база данни.</p>
<p><em>Премахване на едно към едно NULL полета</em></p>
<p>Това е често срещан подход за нормализация след 3-та НФ при който се премахват потенциално нулевите полета. Тук съм дал следния пример съществува база данни за библиотека като една от таблиците и се казва EDITION. Полетата rank и ingram_units са потенциално с нулеви стойности. Може да нормализираме модела като отделим тези полета в отделна таблица и по този начин спестим място. Създаваме новата таблица RANK с първичен ключ ISBN номера и двете потенциално нулеви полета. Връзката която се получава в този случай между RANK и EDITION таблиците е нула или едно към едно. Това означава че ако съществува RANK задължително трябва да съществува EDITION запис, обаче обратното не е задължително може да съществува издание което не е оценено и не е получило RANK.</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig11.png"><img class="aligncenter size-full wp-image-1877" title="fig11" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig11.png" alt="fig11" width="556" height="340" /></a></p>
<p>В случая с таблицата RANK при от фиг 1.1 полетата rank и ingram_units не са зависими едно от друго и са напълно независими. Много е възможно едното от полетата да бъде нула а другото не. Ако продължим с нормализацията до крайности може да разделим таблицата на две нови както е показано на фиг 1.2. Това ниво на нормализация е доста абсурдно и отчасти безмислено. В днешно време паметта е евтина а процесорното време скъпо така че се обезсмиля разделянето на таблицата RANK.</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig1-2.png"><img class="aligncenter size-full wp-image-1881" title="fig1-2" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig1-2.png" alt="fig1-2" width="593" height="499" /></a></p>
<p><strong>Boyce-Codd Normal Form (BCNF)</strong></p>
<p>Нека сега разгледаме прилагането на тази форма. При нея базата данни трябва да е вече в 3-та НФ и всяка таблица да има само по един кандидат-ключ. Кандидат ключ е поле което е възможно да се използва за първичен ключ.</p>
<p>От лявата страна на Фиг.2 е дадена таблица която съдържа естествени (основни) ключови полета като customer_name и address и изкуствен(допълнителн) customer_id. Допълнителния ключ customer_id е добавен защото е по ефиктивен при търсене и по лесно се обработва като цяло. От дясната страна на Фиг.2 е показана таблицата Customer разбита на отделни таблици според точното прилагане на Boyce-Codd Normal Form. Както се вижда раздробяването на таблицата customer на толкова много таблици е меко казано нецелесъобразно!</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig21.png"><img class="aligncenter size-full wp-image-1880" title="fig21" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig21.png" alt="fig21" width="553" height="487" /></a></p>
<p><strong>4-та Нормална Форма</strong></p>
<p>При нея таблицата трябва да е в 3-та нормална форма или 3-та нормална форма с BCNF. При прилагането на 4-та нормална форма трябва да се премахнат многостойностните зависимости. Това означава че единични стойности а не съставни трябва да са зависими от първичния ключ.</p>
<p>Многостойностно поле е такова поле което съдържа колекция масив от стойности от някакъв тип разделени със запетаи.<br />
Фигура 3 показва таблицата Employee с различните умения които притежават служителите и техните сертификати. Трябва да се отбележи че уменията и сертификатите не само че не са зависими едно от друго но са добавени в списък разделен със запетаи.</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig31.png"><img class="aligncenter size-full wp-image-1882" title="fig31" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig31.png" alt="fig31" width="535" height="215" /></a></p>
<p>За да приложим 4-та нормална форма премахваме многостойностните зависимости като разделяме таблицата Employee на три таблици Employee_Skill, Employee и Employee_Certification:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig41.png"><img class="aligncenter size-full wp-image-1883" title="fig41" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig41.png" alt="fig41" width="573" height="223" /></a></p>
<p>Във същността си 4-та нормална форма пренася колекцията от многостойностните елементи във различни таблици и записи и така прави всеки запис по лесен за директен достъп.</p>
<p><strong>5-та Нормална Форма</strong></p>
<p>За да приложим 5-та нормална форма таб таблиците трябва да са вече в 4-та. При 5-та нормална форма се премахват цикличните зависимости. Циклична зависимост се получава когато едно поле зависи от друго поле а то от своя страна зависи директно или индиректно от първото поле. 5-та нормално форма се нарича още „Проекционна нормална форма”. Думата проекционна идва от това че метода създава нови таблици съдържащи подмножества от данните на оригиналната таблица. Циклична зависимост се получава когато например в съставен първичен ключ с три полета всяко от полетата е зависимо от всички други освен от себе си. В конкретния пример името на проекта е зависимо от неговия мениджър и от служителите които работят по него,същото се отнася и за останалите две полета името на служителя е зависимо от мениджъра и от проекта по който работи,за третото поле се отнася същото името на мениджъра е зависимо от проекта който ръководи и от сложителите които управлявава. Таблицата от фигура 5 с съставния ключ може да се раздели на 3 таблици кадето полета са разделени по двойки и така вече не съществува циклична зависимост в отделните таблици.</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/fig51.png"><img class="aligncenter size-full wp-image-1884" title="fig51" src="http://www.cphpvb.net/wp-content/uploads/2009/05/fig51.png" alt="fig51" width="487" height="361" /></a></p>
<p>И така това са нормалните форми. В повечето случаи прилагането на формите води до определени подобрения в структурата и до избягване на аномалии но както вече няколко пъти споменах важно е да се има предвид фактора производителност и то още от самото начало на проектирането на базите данни.</p>
<p><strong>Нужно да се нормализира модела след 3-та нормална форма?</strong></p>
<p>В някои случаи може и да е но в голямата си част работните комерсиални бази данни не стигат дотам. Моето лично мнение е, че проектантите на бази данни не трябва да се страхуват да не използват висококо ниво на нормализация а по скоро обратното. От първостепенна важност е проектанта да може да „види” модела на базата през погледа на софтуерния разработчик и крайния потребител и точно да се определят изискванията.</p>
<p>За да определите нивото на нормализация е добре да си зададете следните въпроси:</p>
<p>1. Какъв тип е базата данни? Дали е OLTP, OLAP или warehouse?</p>
<p>2. Какви са по същество заявките към базата? Дали са повече за добавяне или са за извеждане?</p>
<p>При бази данни при които заявките са добавяне са повече от заявките за извеждане на информация е в общия случай е по добре да се използва по дълбоко ниво на нормализация и обратното ако заявките за извеждане са повече от тези да добавяне тогава може да си позволим по слаба нормализация.</p>
<p><strong>Фактори които влиаят на нивото на нормализация</strong>:</p>
<p>Тук съм подбрал основните фактори които трябва да вземем предвид при прилагането на нормализацията.</p>
<p>1. Коефицентът на добавени/изведени стойности.</p>
<p>2. За какво се използва базата за тразнакционно записване/обработване или е система за подпомагане на избора.</p>
<p>3. Какво е времето за отговор от което се нуждаем при добавяне изтриване и обновяване.</p>
<p>4. Колко е като обем пика на зареждане на данни в базата данни за определен период от време.</p>
<p>5. Дали базата данни е съставена като централизирана система или е разпростряна на няколко места.</p>
<p>6. Как ще контролираме транзакционните процеси дали ще използваме еднофазно или многофазно потрвърждаване.</p>
<p>7. Дали ще се използва временно кеш съхранение на даден тип данни.<br />
След отговора на тези въпроси ще имаме обща представа каква ще точно нашата система и много по лесно ще определим нивото на нормализация.</p>
<p><strong>Каде трябва да теглим чертата?</strong></p>
<p>В случая няма еднозначен отговор но в 90% от случаите това е 3НФ или 3НФ с BCNF. Един добър подход би бил да се остави до някаква степен приложението да проверява данните които се въвеждат и извежда с цел в крайна сметка да се покрият изискванията за скорост и производителност. В случаите когато ситуацията налага да се използват по дълбоки нива на нормализация тогава могат да се използват и 4-та и 5-та нормална форма само, че тогава проблема със нормализацията става много по-сложен и в повечето случаи не си заслужава да се стига до там.</p>
<p>Използвана литетература :</p>
<p>1. Beginning Database Design &#8211; Gavin Powel (Wrox, 2006),<br />
2. Database Design Know It All &#8211; Stephen Buxton, Thomas P. Nadeau (Morgan Kaufmann Publishers,2009).<br />
3. Beginning.Database.Design.From.Novice.to.Professional –Clare Churcher (Apress, 2007).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1876-%d0%bd%d0%be%d1%80%d0%bc%d0%b0%d0%bb%d0%b8%d0%b7%d0%b0%d1%86%d0%b8%d1%8f-%d0%bd%d0%b0-%d0%b1%d0%b0%d0%b7%d0%b8-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Разделяне на части (partitioning)</title>
		<link>http://www.cphpvb.net/db/1866-%d1%80%d0%b0%d0%b7%d0%b4%d0%b5%d0%bb%d1%8f%d0%bd%d0%b5-%d0%bd%d0%b0-%d1%87%d0%b0%d1%81%d1%82%d0%b8-partitioning/</link>
		<comments>http://www.cphpvb.net/db/1866-%d1%80%d0%b0%d0%b7%d0%b4%d0%b5%d0%bb%d1%8f%d0%bd%d0%b5-%d0%bd%d0%b0-%d1%87%d0%b0%d1%81%d1%82%d0%b8-partitioning/#comments</comments>
		<pubDate>Wed, 20 May 2009 20:02:31 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1866</guid>
		<description><![CDATA[Представената по-долу статия е написана във вид на реферат от студентката Павлина Темелакиева от Технически Университет &#8211; София. Тема: &#8222;Постигане на висока производителсност на базата данни чрез разделяне на части&#8220; Павлина Евгениева Темелакиева, ТУ – София , ФКСУ, гр. 63А Преди няколко години са писани много статтии за „Откритие за отлична производителност” (http://www.tdan.com/i016fe03.htm), където се [...]]]></description>
			<content:encoded><![CDATA[<p>Представената по-долу статия е написана във вид на реферат от студентката Павлина Темелакиева  от Технически Университет &#8211; София.</p>
<p style="text-align: center;">Тема:</p>
<p style="text-align: center;"><strong>&#8222;Постигане на висока производителсност<br />
на базата данни чрез разделяне на части&#8220;</strong></p>
<p style="text-align: center;">
<p style="text-align: center;">Павлина Евгениева Темелакиева, ТУ – София , ФКСУ, гр. 63А</p>
<p><span id="more-1866"></span></p>
<p style="text-align: left;">Преди няколко години са писани много статтии за „Откритие за отлична производителност” (<a href="http://www.tdan.com/i016fe03.htm" target="_blank">http://www.tdan.com/i016fe03.htm</a>), където се говори срещу разбирането, че кодът на SQL е основният отговорник за производителността в  база от данни. Вместо това, беше изтъкнат факта, че добрият физически дизайн  е сред водещите компоненти за голямо бързодействие на базата от данни. В допълнение е добавено проучване на Oracle, което илюстрира как лошият дизайн е основният виновник за забавянето на базата. В годините след това все още се застава зад мнението, че вески администратор на бази данни, който иска високо бързодействие на базата си трябва да инвестира в добре обмислен и лесен за разбиране физически дизайн, за да може да направи крайния потребител доволен, а не търпеливо изчакващ изпълнението на заявката си( което търпение в много случаи се превръща в изнервяне).<br />
В новите версии на MySQL( от 5.1)  има потенциално оръжие за високо бързодействие на много натоварени обемни бази от данни, наречено средство за разделяне.</p>
<p style="text-align: left;"><strong>Какво е разделяне на части?</strong><br />
Това е техника при физическия дизайн на базата от данни, с която много администратори са добре запознати. Въпреки че разделянето може да бъде използвано за реализацията на много обекти като основната цел е да се намали количеството на четенето на данни за някои операции в SQL, така че цялото време за отговор да бъде намалено.</p>
<p style="text-align: left;">
<p style="text-align: left;">Има две основни форми на разделяне на части:<strong></strong></p>
<p style="text-align: left;"><strong>Хоризонтално Разделяне</strong> – тази форма е разделяне на сегменти на редовете в таблицата. Така да се формират различни групи от физически разположени в редове данни, към които може да се обърнем иднивидуално( една част) или колективно( една към всички части). Всички колони, определени за някоя таблица могат да бъдат намерени във всяка поредица от части и така нито един атрибут на таблицата няма да се изгуби. Пример за хоризонтално разделяне може да бъде таблица, която съдържа 10-годишна история с данни за фактуи , които биват разделени на 10 различни части, всяка от която съдържа  количество данни за една година.</p>
<p style="text-align: left;"><strong>Вертикално Разделяне</strong> – тази схема за разделяне обикновено е използвана, за да намали широчината на  целевата таблица като някоя таблица се съедини верикално като по този начин само по-съществени колони от данни ще останат, като всяка част ще съдържа всички колони. Пример за вертикално разделяне може да бъде таблица, чиито колони съдържат голямо количество тескт или са от тип BLOB и не са адресирани често. Те биват разбити на две таблици, които съответно съдържат колоните с най-честа адресация в едната и рядко адресиран текст или BLOB данни в другата.</p>
<p style="text-align: left;">Преди производителите на бази данни да започнат да създават този метод ( най-вече хоризонтален), администраторите на бази данни и модераторите трябваше ръчно да създават отделни структури от таблици, за да съхраняват желаните части, които или съхраняват излишък от данни или са свързани заедно, за да сформират един логически родителски обект. Тази практика  е остаряла за по-голямата част от хоризонатлното разделяне, въпреки че все още е подходящо за вертикално разделяне.</p>
<p style="text-align: left;"><strong>Разделяне в MySQL</strong><br />
Едно от нововъведенията в MYSQL е поддръжката на хоризонтално разделяне. Добрите новини за MYSQL и новото попълнение са, че всички основни форми на разделяне се поддържат.</p>
<p><strong>Обхват</strong>: това състояние позволява на администраторите да определят специфичен обхват, в който данните са записани. Например даден администратор може да създаде разделена на части таблица, която е сегментирана на три отделни части, които съдържат данни за 1980-та, 1990-та и всичко след 2000 година.<br />
<strong>Хеш</strong>: това състояние позволява на администраторите да разделят данните, базирани на изчислен хеш ключ, който е определен от една или повече колони в таблицата като крайната цел е равновесно разпределение на данните в частите. Например, даден администратор може да създаде разделена таблица, която има 10 части, базирани на първичния ключ на таблицата.<br />
<strong>Ключ</strong>: специална форма на Хеша, където MySQL гарантира разпределение на данните през системно- генериран хеш ключ.<br />
Лист: този метод позволява да се сегментират данни, базирани на предефиниран лист от стойности, който самият администратор определя. Например даден администратор може да създаде разделена таблица, която създаде 3 части, формирани от данните за години 2004, 2005, 2006.<br />
<strong>Комбиниран(composite)</strong>: това финално състояние за разделяне позволява на даден администратор да изпълнява подразделяне, когато таблицата първоначално е разделяна. Например обхватното разделяне,след което всяка част бива сегментирана равномерно от друг метод(например хеш).<br />
<strong>Паралелно изпълнение на заявки с релционни оператори</strong>: освен разделянето на таблици има други – физически &#8211; методи за разделяне. В следните параграфи ще бъдат разгледани някои от тях.</p>
<p style="text-align: left;">Всъщност разделянето на данни е първата стъпка в разделянето на части на релационните графи. Основната идея е да се използват паралелни потоци от данни, вместо да се пишат отделни оператори за това. Този подход позволява използването на непроменяни, съществуващи практики, чрез които да се приложат релационните оператори паралелно. Всеки релационен оператор има серия от входни портове, на които всеки ред от таблицата „пристига” и изходящ порт, от който излиза целият поток от данни, пратени от даден оператор. Паралелното поддържане на поток от данни се реализира чрез разделяне и съединяване на потоци от данни в тези последователности от портове. Този метод позволява използването на съществуваща последователност от оператори, за да се поддържа паралелно управление.</p>
<p style="text-align: left;">Разглеждаме обхождане на релация А, която е разделена в рамките на три диска във фрагменти съответно A0, A1 и A2. Това обхождане може да бъде осъществено като обхождане на три оператора за сканиране, които изпращат отговрите си до общ оператор за изход. Изпълнителят на паралелната заявка създава троен сканиращ процес(фиг. 1) и и ги насочва да почерпят ресурс от три различни входни потока(A0, A1, A2). Той също ги насочва към общ възел. Всеки скан може да работи на отделен процесор или диск. И така първият основен паралелен оператор е merge, който може да комбинира няколко даннови потока в единствен последователен поток. Операторът за присъединяване (merge) се грижи да насочи данните в една точка.</p>
<p style="text-align: left;"><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/mergeoperator.png"><img class="aligncenter size-full wp-image-1867" title="merge operator" src="http://www.cphpvb.net/wp-content/uploads/2009/05/mergeoperator.png" alt="merge operator" width="489" height="217" /></a></p>
<p style="text-align: left;">Ако многоетаптна паралелна операция трябва да се изпълнява паралелно, то данните отново трябва да бъдат насочени(разделени) на няколко независими потока. Т.нар split-operator се използва, за да раздели или да клонира поток от редове в таблицата, произведени от релационните оператори. Операторът за разделяне превръща една или повече величини от резултантните редове в таблиците в поредица от процеси(фиг. 2).</p>
<p style="text-align: left;"><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/processexecuting.png"><img class="aligncenter size-full wp-image-1868" title="process executing" src="http://www.cphpvb.net/wp-content/uploads/2009/05/processexecuting.png" alt="process executing" width="541" height="184" /></a></p>
<p style="text-align: left;">Като пример, засягащ двата разделящи оператора е показана следната SQL заявка  и фигура 3:</p>
<p style="text-align: left;"><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/example.png"><img class="aligncenter size-full wp-image-1869" title="example" src="http://www.cphpvb.net/wp-content/uploads/2009/05/example.png" alt="example" width="598" height="227" /></a></p>
<p style="text-align: left;">Да предположим, че трите процеса са използвани, за да изпълнят join оператора, а 5 други процеса изпълняват двата сканиращи оператора – три сакниращи части за релация А, докато други 2 части се сканират в релаация В. Всяка от трите сканиращи части от релация А ще има същия обединяващ оператор, изпращащ всички редове в интервала „A-H” към порт 1 на join процес 0, всички „I-Q” към порт 1 на join процес 1 и всички „R-Z” към порт 1 на  join процес 2.</p>
<p style="text-align: left;"><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/relations.png"><img class="aligncenter size-full wp-image-1870" title="relations" src="http://www.cphpvb.net/wp-content/uploads/2009/05/relations.png" alt="relations" width="607" height="140" /></a></p>
<p style="text-align: left;">Подобно на това двата B сканиращи възли имат същия обединяващ оператор с изклчючение на това, че техните изходи са обединени от порт 1, а не от 0 за всеки join процес. Всеки join  процес вижда всяка поредица от входни потоци А(левите сканиращи възли) от порт 0 и съответно поредица от входни потоци Б(десните сканиращи възли) от порт 1. Изходите на всеки join са поред разделени на три потока, базирани на  някакъв разделителен критерий на релацията C.  За да се изясни този пример, разглеждаме първия join процес във фигура 3(процесор 5, портове 0 и 1, процес 3). Той ще получи всички релации А и редовете в интервала  „А-Н”, обединени в един поток на порт 0 и всички редове „А-Н” обединени в един поток от порт 1. Ще ги присъедини, използвайки хеш-join, sort-merge join или друг.</p>
<p style="text-align: left;">Ако всеки от тези процеси са на независим процесор с независимо записващо устройство, ще има малки смущения измежду тях. Операторът за разделяне тук е само даден като пример. Други оператори за разделяне биха могли да дублират входен поток информация или да го раздели чрез хеш.</p>
<p style="text-align: left;">Операторите за просъединяване и разделяне имат значителен контрол и буферна конструкция. Това предотвратява някой оператор да стигне твърде далеч при изчисляването. Когато буферът на оператора за разделяне се препълни, той забавя релационния оператор докато се освободи място на изхода за данните.</p>
<p style="text-align: left;"><a href="http://www.cphpvb.net/wp-content/uploads/2009/05/insert-join-scan.png"><img class="aligncenter size-full wp-image-1871" title="insert-join-scan" src="http://www.cphpvb.net/wp-content/uploads/2009/05/insert-join-scan.png" alt="insert-join-scan" width="579" height="278" /></a></p>
<p style="text-align: left;">Има много ползи от разделянето на части, но двете основни предимства са:</p>
<p style="text-align: left;"><strong>Растяща производителност</strong>: по време на претърсващите операции – оптимизаторът на MySQL знае коя част съдържа данните, които ще задоволят някоя специфична заявка и ще има достъп само до оне необходими части по време на изпълнението на самата заявка. Например една таблица с един милион редове може да бъде разбита на 10 различни части с обхватния метод, така че всяка част да съдържа 100,000 реда. Ако заявката разбере, че ? трябват данни от само една от частите и все пак е необходимо преглеждане на цялата таблица, то ще бъдат сканирани само 100,000 реда вместо 1 милион. Очевидно трябва да бъде по-лесно за MySQL да обходи 100,000 реда вместо 1 милион, следователно заявката ще се изпълни много по-скоро. Същата полза произлиза от необходимостта да бъдат индексирани и отделните шасти, на които бива разделена таблицата, т.е. създават се локални индекси за отделните таблици. В крайна сметка става възможно маркирането на разделените таблици  през различни физически пътища, специфизирайки  директорията на отделните части. Това позволява физическото I/O  съравнование да бъде намалено, когато множество части бъдат достигнати по едно и също време.</p>
<p style="text-align: left;"><strong>Опростено  ръководене на данните</strong> – разделянето позволява на администраторите да имат повече контрол над това как данните са управлявани във вътрешността на базата. Създавайки части, администраторъ опростява начинът на изпълнение на някои операции с данните. Например, той може да изтрие специфични части в една таблица, докато останалите части остават недокоснати. По-натам, частите се поддържат автоматично от MySQL, така че администраторът не трябва ръчно да разделя и поддържа хоризонтално разделяне на части за някоя таблица. Например администраторът на базата може да създаде таблица с история, която съхранява данни за клиенти, които са разделени според някакъв годишен критерий. Тези части са автоматично приложени и разделени от сървъра на базите данни с никаква намеса да администратора.</p>
<p style="text-align: left;"><strong>Разделянето на части в действие</strong>:<br />
От гледна точка на дизайна за подобряване на бързодействието на базата основно сме заинтересовани . Използвайки подходящо разделянето на части, драматично може да се подобри работата на базата. Следният пример ще илюстрира това:<br />
За да видим ползата от разделянето на части, ще създадем идентична MyISAM таблици, които съдържат информация, свързана с дати, но нека да отделим една, а другата да оставим като стандартна хийп таблица. За нашата отделена таблица ще направим разделяне, базирано на полето година:</p>
<pre>CREATE TABLE part_tab (
	c1 int default NULL ,
	c2 varchar(30) default NULL ,
	c3 date default NULL ,
) engine=myisam
PARTITION BY RANGE (year(c3)) (
	PARTITION p0 VALUES LESS THAN (1995),
	PARTITION p1 VALUES LESS THAN (1996) ,
	PARTITION p2 VALUES LESS THAN (1997) ,
	PARTITION p3 VALUES LESS THAN (1998) ,
	PARTITION p4 VALUES LESS THAN (1999) ,
	PARTITION p5 VALUES LESS THAN (2000) ,
	PARTITION p6 VALUES LESS THAN (2001) ,
	PARTITION p7 VALUES LESS THAN (2002) ,
	PARTITION p8 VALUES LESS THAN (2003) ,
	PARTITION p9 VALUES LESS THAN (2004) ,
	PARTITION p10 VALUES LESS THAN (2010),
	PARTITION p11 VALUES LESS THAN MAXVALUE
);</pre>
<p>Трябва да се забележи това, че създадохме части за специфична година и завършихме с една, побираща в себе си всички част, която да събере всички данни, които не попдат в никоя друга част. Сега нека създадем таблица с огледален образ в MyISAM, която да не е разделена:</p>
<pre>create table no_part_tab(
	c1 int(11) default NULL,
	c2 varchar(30) default NULL,
	c3 date default NULL
) engine=myisam;</pre>
<p>Сега нека създадем процедура, която ще запълни нашата разделена таблица с 8 милиона реда, които разпределят данните по равно между двете части. Веднъж запълнена, ще сложим същите данни в нашата клонирана, неразделена на части MyISAM таблица:</p>
<pre>DELIMITER //

CREATE PROCEDURE load_part_tab()
BEGIN
	DECLARE v INT DEFAULT 0;
	WHILE v &lt; 8000000
	DO
		INSERT INTO part_tab
		VALUES (v,'testing partitions',
				adddate('1995-01-01',
				(rand(v)*36520) mod 3652));
		SET v = v + 1;
	END WHILE;
END
//
Query OK, 0 rows affected (0.00 sec)

DELIMITER ;

CALL load_part_tab();
Query OK, 1 row affected (8 min 17.75 sec)

INSERT INTO no_part_tab
SELECT * FROM part_tab;
Query OK, 8000000 rows affected (51.59 sec)
Records: 8000000  Duplicates: 0  Warnings: 0</pre>
<p>С вече готовите таблици нека да видим какъв ще бъде резултатът от двете таблици, последвани от пояснения – неразделяната на части първо и след това тази, която разделихме:</p>
<pre>SELECT count(*)
FROM no_part_tab
WHERE c3 &gt; date '1995-01-01' and c3 &lt; date '1995-12-31';
+----------+
| count(*) |
+----------+
|   795181 |
+----------+
1 row in set (38.30 sec)

SELECT count(*)
FROM part_tab
WHERE c3 &gt; date '1995-01-01' and c3 &lt; date '1995-12-31';
+----------+
| count(*) |
+----------+
|   795181 |
+----------+
1 row in set (3.88 sec)</pre>
<p>Лесно се заблеязва, че достъпът до разделената таблица доставя до 90% по-бърз отговор в сравнение с неразделената.</p>
<p>В заключение можем да изтъкнем още някои предимства на разделянето на части:</p>
<ul>
<li>Всички акумулатори за съхранение поддържат разделяне(MyISAM, InnoDB, Archive и т.н.);</li>
<li>Поддържането на индекси за разделени таблици включва локални индекси, които копират всяка част в схема 1:1. С други думи, ако разделената таблица има 10 части, то локалните индекси за тази таблица също ще съдържат 10 части;</li>
<li>Всички SHOW команди поддържат отговор от разделени таблици и индекс метадата;</li>
</ul>
<p><strong>Използвани източници</strong>:</p>
<p><a href="http://dev.mysql.com/tech-resources/articles/performance-partitioning.html" target="_blank">http://dev.mysql.com/tech-resources/articles/performance-partitioning.html</a><br />
<a href="http://msdn.microsoft.com/en-us/library/aa177979(SQL.80).aspx" target="_blank">http://msdn.microsoft.com/en-us/library/aa177979(SQL.80).aspx</a><br />
<a href="http://www.tdan.com/i016fe03.htm" target="_blank">http://www.tdan.com/i016fe03.htm</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1866-%d1%80%d0%b0%d0%b7%d0%b4%d0%b5%d0%bb%d1%8f%d0%bd%d0%b5-%d0%bd%d0%b0-%d1%87%d0%b0%d1%81%d1%82%d0%b8-partitioning/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>FULL JOIN в MySQL</title>
		<link>http://www.cphpvb.net/db/1854-full-join-%d0%b2-mysql/</link>
		<comments>http://www.cphpvb.net/db/1854-full-join-%d0%b2-mysql/#comments</comments>
		<pubDate>Wed, 20 May 2009 18:56:56 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1854</guid>
		<description><![CDATA[След като научихме заявките, използващи UNION, вече сме готови да посочим как се прави и липсващия в MySQL FULL JOIN. Ще използваме таблиците с плодове и зеленчуци от предишната статия. Нека припомним как работеха LEFT и RIGHT JOIN. За целта ще направим многотаблична заявка по колоната &#8222;цена&#8220;: SELECT * FROM vegetables LEFT JOIN fruits ON [...]]]></description>
			<content:encoded><![CDATA[<p>След като научихме заявките, използващи <a href="http://www.cphpvb.net/db/1851-union/">UNION</a>, вече сме готови да посочим как се прави и липсващия в MySQL FULL JOIN. Ще използваме таблиците с плодове и зеленчуци от предишната статия.</p>
<p>Нека припомним как работеха LEFT и RIGHT JOIN. За целта ще направим многотаблична заявка по колоната &#8222;цена&#8220;:<span id="more-1854"></span></p>
<pre>SELECT * FROM vegetables
LEFT JOIN fruits ON vegetables.price = fruits.price;
+----+------------+-------+------+--------+-------+
| id | name       | price | id   | name   | price |
+----+------------+-------+------+--------+-------+
|  1 | krastavici |     4 | NULL | NULL   |  NULL |
|  2 | domati     |     5 | NULL | NULL   |  NULL |
|  3 | kartofi    |     3 |    1 | banani |     3 |
+----+------------+-------+------+--------+-------+
3 rows in set (0.00 sec)</pre>
<p>Резултата е точно това, което очаквахме &#8211; само banani имат една и съща цена с kartofi и затова от втората таблица само един ред не е с резултат NULL. В RIGHT JOIN се случваше същото, но в обратна посока:</p>
<pre>SELECT * FROM vegetables
RIGHT JOIN fruits ON vegetables.price = fruits.price;
+------+---------+-------+----+----------+-------+
| id   | name    | price | id | name     | price |
+------+---------+-------+----+----------+-------+
|    3 | kartofi |     3 |  1 | banani   |     3 |
| NULL | NULL    |  NULL |  2 | iagodi   |   2.5 |
| NULL | NULL    |  NULL |  3 | chereshi |     7 |
+------+---------+-------+----+----------+-------+
3 rows in set (0.00 sec)</pre>
<p>Е, вече може би се досетихте как можем да постигнем FULL JOIN &#8211; просто трябва да обединим резултата от двете заявки и да премахнем дублиращите се редове (т.е. да използваме UNION без ALL):</p>
<pre>(SELECT * FROM vegetables
LEFT JOIN fruits ON vegetables.price = fruits.price)
UNION
(SELECT * FROM vegetables
RIGHT JOIN fruits ON vegetables.price = fruits.price);
+------+------------+-------+------+----------+-------+
| id   | name       | price | id   | name     | price |
+------+------------+-------+------+----------+-------+
|    1 | krastavici |     4 | NULL | NULL     |  NULL |
|    2 | domati     |     5 | NULL | NULL     |  NULL |
|    3 | kartofi    |     3 |    1 | banani   |     3 |
| NULL | NULL       |  NULL |    2 | iagodi   |   2.5 |
| NULL | NULL       |  NULL |    3 | chereshi |     7 |
+------+------------+-------+------+----------+-------+
5 rows in set (0.00 sec)</pre>
<p>Редовете са точно 5, а това е именно каквото очаквахме от FULL JOIN. Този метод върши работа в почти всички случай, но за съжаление НЕ покрива 100% стандарта на определението за FULL JOIN. Проблемът е, че UNION премахва всички дублиращи се редове, а FULL JOIN не го прави ако има такива. За да се демонстрира това трябва в една от таблиците да има дублиращи се редов &#8211; в този случай по стандарта за FULL JOIN ще трябва този ред да излезе два пъти, а с горната реализация няма да се получи. Eто един пример:</p>
<pre>INSERT INTO vegetables (`id`, `name`, `price`)
VALUES (NULL, 'krastavici', 4);

SELECT * FROM vegetables;
+----+------------+-------+
| id | name       | price |
+----+------------+-------+
|  1 | <strong>krastavici </strong>|     4 |
|  2 | domati     |     5 |
|  3 | kartofi    |     3 |
|  4 | <strong>krastavici </strong>|     4 |
+----+------------+-------+
4 rows in set (0.00 sec)

(SELECT vegetables.name, fruits.name FROM vegetables
LEFT JOIN fruits ON vegetables.price = fruits.price)
UNION
(SELECT vegetables.name, fruits.name FROM vegetables
RIGHT JOIN fruits ON vegetables.price = fruits.price);
+------------+----------+
| name       | name     |
+------------+----------+
| <strong>krastavici </strong>| NULL     |
| domati     | NULL     |
| kartofi    | banani   |
| NULL       | iagodi   |
| NULL       | chereshi |
+------------+----------+
5 rows in set (0.00 sec)</pre>
<p>Виждаме, че в резултата има само едни &#8216;krastavici&#8217;, а при истински FULL JOIN трябваше да са две. Този проблем няма да се реши и с UNION ALL, защото в този случай ще се появят други дублиращи се редове, които НЕ трябва да присъстват:</p>
<pre>(SELECT vegetables.name, fruits.name FROM vegetables
LEFT JOIN fruits ON vegetables.price = fruits.price)
UNION <strong>ALL</strong>
(SELECT vegetables.name, fruits.name FROM vegetables
RIGHT JOIN fruits ON vegetables.price = fruits.price);
+------------+----------+
| name       | name     |
+------------+----------+
| krastavici | NULL     |
| domati     | NULL     |
| <strong>kartofi    </strong>| <strong>banani   </strong>|
| krastavici | NULL     |
| <strong>kartofi    </strong>| <strong>banani   </strong>|
| NULL       | iagodi   |
| NULL       | chereshi |
+------------+----------+
7 rows in set (0.00 sec)</pre>
<p>Тук &#8222;krastavici&#8220; се появяват два пъти, както трябва да бъде, но реда &#8222;kartofi | banani&#8220; се появява два пъти, а не трябва да бъде така! Затова за реализация на FULL JOIN се използва друг подход, който е модификация на последния:</p>
<pre>(SELECT vegetables.name, fruits.name FROM vegetables
LEFT JOIN fruits ON vegetables.price = fruits.price)
UNION ALL
(SELECT vegetables.name, fruits.name FROM vegetables
RIGHT JOIN fruits ON vegetables.price = fruits.price
<strong>WHERE vegetables.price IS NULL</strong>);
+------------+----------+
| name       | name     |
+------------+----------+
| krastavici | NULL     |
| domati     | NULL     |
| kartofi    | banani   |
| krastavici | NULL     |
| NULL       | iagodi   |
| NULL       | chereshi |
+------------+----------+
6 rows in set (0.00 sec)</pre>
<p>С добавеното условие WHERE ние премахнахме редовете, които вече са общи за двете таблици. Така вече имаме напълно функционална FULL JOIN заявка!</p>
<p>В по-старите версии на MySQL, където UNION не съществува, реализацията се прави чрез създаването на трета временна таблица. Няма да се спираме на това решение.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1854-full-join-%d0%b2-mysql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>UNION</title>
		<link>http://www.cphpvb.net/db/1851-union/</link>
		<comments>http://www.cphpvb.net/db/1851-union/#comments</comments>
		<pubDate>Wed, 20 May 2009 18:31:26 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1851</guid>
		<description><![CDATA[Не споменахме една пренебрегната досега възможност на SQL, а именно &#8211; обединението. То се използва, за да може две или повече SELECT заявки да бъдат комбинирани в една резултатна таблица. Нека преди да демонстрираме да създадем една примерна база от данни: CREATE DATABASE unions; CREATE TABLE vegetables( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NULL [...]]]></description>
			<content:encoded><![CDATA[<p>Не споменахме една пренебрегната досега възможност на SQL, а именно &#8211; обединението. То се използва, за да може две или повече SELECT заявки да бъдат комбинирани в една резултатна таблица. Нека преди да демонстрираме да създадем една примерна база от данни:<span id="more-1851"></span></p>
<pre>CREATE DATABASE unions;

CREATE TABLE vegetables(
	`id` INT NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(255) NULL DEFAULT NULL,
	`price` DOUBLE NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
);

CREATE TABLE fruits(
	`id` INT NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(255) NULL DEFAULT NULL,
	`price` DOUBLE NULL DEFAULT NULL,
	PRIMARY KEY (`id`)
);

INSERT INTO vegetables(`id`, `name`, `price`)
VALUES 	(NULL, 'krastavici', 4.00),
	(NULL, 'domati', 5.00),
	(NULL, 'kartofi', 3.00);

INSERT INTO fruits(`id`, `name`, `price`)
VALUES 	(NULL, 'banani', 3.00),
	(NULL, 'iagodi', 2.50),
	(NULL, 'chereshi', 7.00);</pre>
<p>Сега искаме да създадем таблица, която да изведе имената както на плодовете, така и на зеленчуците. Познатия досега вариант е с използване на JOIN, но така таблицата ще бъде с две колони. Ако искаме резултата да е подреден в една колона, то използваме UNION:</p>
<pre>(SELECT name as Fruits_and_veggies
FROM vegetables)
UNION
(SELECT name
FROM fruits);
+--------------------+
| Fruits_and_veggies |
+--------------------+
| krastavici         |
| domati             |
| kartofi            |
| banani             |
| iagodi             |
| chereshi           |
+--------------------+
6 rows in set (0.00 sec)</pre>
<p>Както виждате името на колоната се приема за името от първата таблица. Естествено можем да направим UNION и по две колони:</p>
<pre>(SELECT name as Fruits_and_veggies, price
FROM vegetables)
UNION
(SELECT name, price
FROM fruits);
+--------------------+-------+
| Fruits_and_veggies | price |
+--------------------+-------+
| krastavici         |     4 |
| domati             |     5 |
| kartofi            |     3 |
| banani             |     3 |
| iagodi             |   2.5 |
| chereshi           |     7 |
+--------------------+-------+
6 rows in set (0.00 sec)</pre>
<p>Не е задължително данните да са от един и същ тип. Важно е обаче броя на колоните от първия SELECT да съвпада с броя на колоните на втория! В противен случай няма как да се извърши обединението.</p>
<p>След като натрупате опит в използването на UNION рано или късно ще достигнете до един първоначално изглеждащ странен ефект &#8211; дублиращите се редове автоматично се премахват! Ето един пример:</p>
<pre>(SELECT price
FROM vegetables)
UNION
(SELECT price
FROM fruits);
+-------+
| price |
+-------+
|     4 |
|     5 |
|     3 |
|   2.5 |
|     7 |
+-------+
5 rows in set (0.00 sec)</pre>
<p>Знаем, че имаме общо 6 плода и зеленчука в таблиците, а в резултата от цените излязоха само 5. Това е така, защото цената на banani и kartofi е една и съща и UNION е премахнал дублиращите се редове. Ако това предизвиква потенциален проблем във вашата заявка, то трябва да използвате допълнителната дума ALL:</p>
<pre>(SELECT price
FROM vegetables)
UNION ALL
(SELECT price
FROM fruits);
+-------+
| price |
+-------+
|     4 |
|     5 |
|     3 |
|     3 |
|   2.5 |
|     7 |
+-------+
6 rows in set (0.00 sec)</pre>
<p>Естествено възможно е UNION да е част от под-заявка (например вложен SELECT). Затова ограждането на заявките в скоби не е задължително, но е силно препоръчително.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1851-union/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Логически оператори и цикли</title>
		<link>http://www.cphpvb.net/db/1509-%d0%bb%d0%be%d0%b3%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b8-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%82%d0%be%d1%80%d0%b8-%d0%b8-%d1%86%d0%b8%d0%ba%d0%bb%d0%b8/</link>
		<comments>http://www.cphpvb.net/db/1509-%d0%bb%d0%be%d0%b3%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b8-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%82%d0%be%d1%80%d0%b8-%d0%b8-%d1%86%d0%b8%d0%ba%d0%bb%d0%b8/#comments</comments>
		<pubDate>Sun, 19 Apr 2009 08:08:33 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1509</guid>
		<description><![CDATA[Чрез процедурите MySQL много наподобява завършен език за програмиране. За това силно спомагат възможностите за логически оператори и цикли. Ще ги разгледаме поотделно: 1. IF-ELSE: Операторите IF-ELSE имат следната структура: IF &#60;условие&#62; THEN &#60;заявки&#62;; ELSE &#60;заявки&#62;; END IF; Нека демонстрираме с един пример &#8211; процедура, на която подаваме параметри сума и номер на акаунт. Процедурата [...]]]></description>
			<content:encoded><![CDATA[<p>Чрез процедурите MySQL много наподобява завършен език за програмиране. За това силно спомагат възможностите за логически оператори и цикли. Ще ги разгледаме поотделно:</p>
<p>1. <strong>IF-ELSE</strong>:</p>
<p>Операторите IF-ELSE имат следната структура:</p>
<pre>IF &lt;условие&gt;
   THEN &lt;заявки&gt;;
   ELSE &lt;заявки&gt;;
END IF;</pre>
<p>Нека демонстрираме с един пример &#8211; процедура, на която подаваме параметри сума и номер на акаунт. Процедурата връща резултат &#8222;1&#8243; ако в акаунта има повече пари от посочените или &#8222;0&#8243; в противен случай:<span id="more-1509"></span></p>
<pre>mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE check_availability(IN acc INT, IN money DOUBLE)
       BEGIN

              DECLARE acc_avail INT;

              SELECT amount
              INTO acc_avail
              FROM accounts
              WHERE id = acc;

              IF (acc_avail &gt;= money)
                     THEN SELECT 1;
                     ELSE SELECT 0;
              END IF;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; SELECT id, amount FROM accounts WHERE id = 5;
+----+--------+
| id | amount |
+----+--------+
|  5 | 191.98 |
+----+--------+
1 row in set (0.00 sec)

mysql&gt; CALL check_availability(5, 200);
+---+
| 0 |
+---+
| 0 |
+---+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

mysql&gt; CALL check_availability(5, 150);
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)</pre>
<p>Виждате, че се получи точно желания резултат &#8211; в акаунт №5 има точно 191.98. Когато попитаме процедурата дали има 200 тя връща резултат 0, а когато я попитаме дали има 150 връща резултат 1.</p>
<p>Искаме да обърнем внимание на заявката &#8222;DECLARE x INT&#8220;. Чрез нея ние дефинираме т.нар. локална променлива за процедурата. Тя е валидна само вътре в процедурата и се изтрива след нейното приключване. Виждате, че в случая я инициализирахме като резултат от временна таблица (SELECT -&gt; INTO).</p>
<p>Трябва много да внимавате при евентуално подаване на NULL стойности. Както и при обикновените заявки, всяко сравнение с NULL стойност връща резултат FALSE.</p>
<p>2. <strong>CASE</strong>:</p>
<p>CASE е аналог на операторът за многовариантен избор switch в езика за програмиране C. Синтаксисът е следния:</p>
<pre>CASE &lt;променлива&gt;
   WHEN &lt;условие&gt;
      THEN &lt;заявки&gt;;
   WHEN &lt;условие&gt;
      THEN &lt;заявки&gt;;
   ...
   ELSE &lt;заявки&gt;
END CASE;</pre>
<p>Частта ELSE се достига тогава, когато нито едно от условията по-горе не е изпълнено.</p>
<p>Нека демонстрираме с пример &#8211; процедура, която по зададен номер на акаунт връща името на типа му:</p>
<pre>mysql&gt; DELIMITER |
mysql&gt;
mysql&gt; CREATE PROCEDURE acc_type(IN acc INT)
       BEGIN

              DECLARE acc_t TINYINT;

              SELECT type
              INTO acc_t
              FROM accounts
              WHERE id = acc;

              CASE acc_t
              WHEN 1 THEN
                     SELECT "3 months deposit";
              WHEN 2 THEN
                     SELECT "Annual deposit";
              ELSE
                     SELECT "Unknown account";
              END CASE;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; SELECT id, type FROM accounts WHERE id = 6 OR id = 7;
+----+------+
| id | type |
+----+------+
|  6 |    2 |
|  7 |    1 |
+----+------+
2 rows in set (0.00 sec)

mysql&gt; CALL acc_type(6);
+----------------+
| Annual deposit |
+----------------+
| Annual deposit |
+----------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

mysql&gt; CALL acc_type(7);
+------------------+
| 3 months deposit |
+------------------+
| 3 months deposit |
+------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)</pre>
<p>3. <strong>WHILE</strong>:</p>
<p>Процедурите в MySQL добиват още по-голяма сила с наличието на цикли. Първият и може би най-популярен е WHILE:</p>
<pre>WHILE &lt;условие&gt;
DO
   &lt;заявки&gt;;
END WHILE;</pre>
<p>Например следната процедура ще изведе сумите по акаунти на всички клиенти в зададен диапазон (x,y):</p>
<pre>mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE check_clients(IN x INT, IN y INT)
       BEGIN

              DECLARE iterator INT;

              SET iterator = x;

              WHILE (iterator &gt;= x AND iterator &lt;= y)
              DO
                     SELECT id, amount
                     FROM accounts
                     WHERE id = iterator;

                     SET iterator = iterator + 1;
              END WHILE;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; CALL check_clients(5,9);

+----+--------+
| id | amount |
+----+--------+
|  5 | 191.98 |
+----+--------+
1 row in set (0.00 sec)

+----+---------+
| id | amount  |
+----+---------+
|  6 | 1220.00 |
+----+---------+
1 row in set (0.00 sec)

+----+--------+
| id | amount |
+----+--------+
|  7 | 133.48 |
+----+--------+
1 row in set (0.00 sec)

+----+--------+
| id | amount |
+----+--------+
|  8 | 256.41 |
+----+--------+
1 row in set (0.00 sec)

+----+---------+
| id | amount  |
+----+---------+
|  9 | 1331.50 |
+----+---------+
1 row in set (0.02 sec)

Query OK, 0 rows affected (0.02 sec)</pre>
<p>4. <strong>REPEAT-UNTIL</strong>:</p>
<p>Цикълът е с абсолютно същото действие както WHILE, с изключение, че ако условието е погрешно в самото начало, то въпреки това тялото на цикъла ще се изпълни поне веднъж:</p>
<pre>REPEAT
   &lt;заявки&gt;;
UNTIL &lt;условие&gt;;
END REPEAT;</pre>
<p>Този вид цикъл е малко популярен и рядко намира приложение. Затова няма да го разглеждаме подробно с пример.</p>
<p><strong>Задача</strong>: Преработете процедурата за прехвърляне на пари от един акаунт в друг като направите така, че ако първият акаунт няма достатъчно пари, то да се изведе съобщение за грешка.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1509-%d0%bb%d0%be%d0%b3%d0%b8%d1%87%d0%b5%d1%81%d0%ba%d0%b8-%d0%be%d0%bf%d0%b5%d1%80%d0%b0%d1%82%d0%be%d1%80%d0%b8-%d0%b8-%d1%86%d0%b8%d0%ba%d0%bb%d0%b8/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Процедури и входни параметри</title>
		<link>http://www.cphpvb.net/db/1499-%d0%bf%d1%80%d0%be%d1%86%d0%b5%d0%b4%d1%83%d1%80%d0%b8-%d0%b8-%d0%b2%d1%85%d0%be%d0%b4%d0%bd%d0%b8-%d0%bf%d0%b0%d1%80%d0%b0%d0%bc%d0%b5%d1%82%d1%80%d0%b8/</link>
		<comments>http://www.cphpvb.net/db/1499-%d0%bf%d1%80%d0%be%d1%86%d0%b5%d0%b4%d1%83%d1%80%d0%b8-%d0%b8-%d0%b2%d1%85%d0%be%d0%b4%d0%bd%d0%b8-%d0%bf%d0%b0%d1%80%d0%b0%d0%bc%d0%b5%d1%82%d1%80%d0%b8/#comments</comments>
		<pubDate>Sat, 18 Apr 2009 19:58:09 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1499</guid>
		<description><![CDATA[Процедурите ни дават възможност да създаваме скриптове за извършване на типизирани заявки с различни входни данни. Нека демонстрираме една елементарна процедура, която извиква обикновена заявка SELECT: mysql&#62; DELIMITER &#124; mysql&#62; CREATE PROCEDURE show_customers() -&#62; BEGIN -&#62; SELECT * FROM customers; -&#62; END -&#62; &#124; Query OK, 0 rows affected (0.00 sec) mysql&#62; DELIMITER ; mysql&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Процедурите ни дават възможност да създаваме скриптове за извършване на типизирани заявки с различни входни данни. Нека демонстрираме една елементарна процедура, която извиква обикновена заявка SELECT:<span id="more-1499"></span></p>
<pre>mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE show_customers()
    -&gt; BEGIN
    -&gt;  SELECT * FROM customers;
    -&gt; END
    -&gt; |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; CALL show_customers();
+----+-------------------+---------+----------+
| id | name              | address | bank_mgr |
+----+-------------------+---------+----------+
|  1 | Todor Ivanov      | NULL    |        1 |
|  2 | Petko Stoianov    | NULL    |        1 |
|  3 | Neno Nenov        | NULL    |     NULL |
|  4 | Mariana Zaharieva | NULL    |        3 |
|  5 | Elica Zaharieva   | NULL    |        3 |
|  6 | Atanas Petrov     | NULL    |        4 |
|  7 | Ivan Ivanov       | NULL    |        4 |
|  8 | Zlatomir Petrov   | NULL    |        4 |
|  9 | Mihail Ivchev     | NULL    |        5 |
| 11 | Ivailo Ivanov     | NULL    |        7 |
| 12 | George Lucas      | NULL    |     NULL |
| 13 | George Harison    | NULL    |     NULL |
| 14 | Michael Jackson   | NULL    |     NULL |
| 15 | Tony Martin       | NULL    |     NULL |
| 16 | Tony McCarter     | NULL    |     NULL |
| 17 | Alexander Smith   | NULL    |       11 |
| 18 | Maria Smith       | NULL    |       11 |
| 19 | Alain Delrick     | NULL    |       12 |
| 20 | Devry Henry       | NULL    |       12 |
| 21 | Lenard Renne      | NULL    |       12 |
| 22 | Fontaine Rupert   | NULL    |       13 |
+----+-------------------+---------+----------+
21 rows in set (0.00 sec)

Query OK, 0 rows affected (0.02 sec)</pre>
<p>Обърнете внимание на командата &#8222;DELIMITER&#8220;. Чрез първото й изпълнение указваме, че края на заявка вече няма да става с &#8222;;&#8220;, а с &#8222;|&#8220;. Това е нужно, защото при създаването на процедурата трябва да използваме символа &#8222;;&#8220; при края на заявката SELECT. DELIMITER може да бъде всяка комбинация от символи.</p>
<p>Можем да създаваме процедури за изпълнение на повече от една заявка наведнъж. Ясно е обаче, че създаването на процедури с предварително дефинирани константни заявки е безсмислено. Бихме искали една процедура да изпълнява различни заявки, в зависимост от подадени към нея данни. За целта на помощ идват т.нар. параметри (parameters), които са много подобни на променливите в програмирането. Още по-добра аналогия може да се направи със потребителските сесии при програмиране в интернет (session variables). Нека демонстрираме параметрите в MySQL с елементарен пример с таблицата banks:</p>
<pre>mysql&gt; USE banks;
Database changed

mysql&gt; SET @cust_name = 'Ivan Ivanov';
Query OK, 0 rows affected (0.02 sec)

mysql&gt; SELECT * FROM CUSTOMERS
       WHERE name = @cust_name;
+----+-------------+---------+----------+
| id | name        | address | bank_mgr |
+----+-------------+---------+----------+
|  7 | Ivan Ivanov | NULL    |        4 |
+----+-------------+---------+----------+
1 row in set (0.05 sec)</pre>
<p>Казахме, че има аналогия с променливи в потребителски сесии &#8211; това е така, защото тази променлива е валидна само за текущата връзка. При приключване на връзката тя ще бъде унищожена.</p>
<p>Процедурите могат да достъпват параметри по три различни начина:</p>
<p>1. <strong>IN</strong>:</p>
<pre>mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE proc_in(IN var VARCHAR(255))
BEGIN
   SET @cust_name = var;
END
|
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; CALL proc_in('Atanas Petrov');
Query OK, 0 rows affected (0.00 sec)

mysql&gt; SELECT * FROM customers WHERE name = @cust_name;
+----+---------------+---------+----------+
| id | name          | address | bank_mgr |
+----+---------------+---------+----------+
|  6 | Atanas Petrov | NULL    |        4 |
+----+---------------+---------+----------+
1 row in set (0.02 sec)</pre>
<p>Виждаме как към процедурата подадохме параметър и чрез него променихме стойността на променливата, която беше дефинирана глобално.</p>
<p>Трябва да знаете, че ако към IN параметър на процедура подадете параметър вместо конкретна стойност и го промените вътре в процедурата, то този параметър ще се върне в първоначалното си състояние след приключване на процедурата. Можете да си направите аналогия с предаването на параметър към функция по стойност от програмирането на C++.</p>
<p>2. <strong>OUT</strong>:</p>
<pre>mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE proc_out(OUT var VARCHAR(255))
    -&gt; BEGIN
    -&gt;    SET var = 'Todor Ivanov';
    -&gt; END
    -&gt; |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; CALL proc_out(@cust_name);
Query OK, 0 rows affected (0.00 sec)

mysql&gt; SELECT * FROM customers WHERE name = @cust_name;
+----+--------------+---------+----------+
| id | name         | address | bank_mgr |
+----+--------------+---------+----------+
|  1 | Todor Ivanov | NULL    |        1 |
+----+--------------+---------+----------+
1 row in set (0.00 sec)</pre>
<p>Тук подадохме променливата като входен параметър на процедурата и я променихме вътре във функцията. Виждате, че след приключването на процедурата глобалния параметър остана променен &#8211; това нямаше да стане, ако го бяхме направили с IN. Можете да си направите аналогия с подаването на параметър към функция като псевдоним в програмирането на C++.</p>
<p>3. <strong>INOUT</strong>:</p>
<pre>mysql&gt; SET @newvar = 'Petko Stoianov';
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER |
mysql&gt; CREATE PROCEDURE proc_inout(INOUT var VARCHAR(255))
    -&gt; BEGIN
    -&gt;    SET var = @cust_name;
    -&gt; END
    -&gt; |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;

mysql&gt; CALL proc_inout(@newvar);
Query OK, 0 rows affected (0.00 sec)

mysql&gt; SELECT * FROM customers WHERE name = @newvar;
+----+--------------+---------+----------+
| id | name         | address | bank_mgr |
+----+--------------+---------+----------+
|  1 | Todor Ivanov | NULL    |        1 |
+----+--------------+---------+----------+
1 row in set (0.00 sec)</pre>
<p>Виждаме, че променливата @newvar беше със стойност &#8216;Petko Stoianov&#8217;, но след изпълнението на процедурата я променихме със стойността на @cust_name (&#8216;Todor Ivanov&#8217;). Разликата между OUT и INOUT методите е, че при INOUT параметъра трябва да бъде дефиниран глобално преди извикването на функцията &#8211; при OUT не е така (ако не съществува ще бъде създаден).</p>
<p>Сега вече можем да създаваме и по-смислени процедури. Нека дадем един пример &#8211; процедура, която премества пари от един акаунт в друг. В случая ще използваме IN входен параметър (в процедурата не променяме стойностите на параметри):</p>
<pre>mysql&gt; USE banks;
Database changed

mysql&gt; DELIMITER |

mysql&gt; CREATE PROCEDURE transfer_money(acc_in INT, acc_out INT, money DOUBLE)
       BEGIN
              START TRANSACTION;

              UPDATE accounts
              SET amount = amount - money
              WHERE id = acc_out AND amount &gt;= money;

              UPDATE accounts
              SET amount = amount + money
              WHERE id = acc_in;

              COMMIT;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql&gt; DELIMITER ;</pre>
<p>Ето как можем да преместим 20 лева от акаунт номер 5 в акаунт номер 6:</p>
<pre>mysql&gt; SELECT id, amount
       FROM accounts
       WHERE id = 5 OR id = 6;
+----+---------+
| id | amount  |
+----+---------+
|  5 |  211.98 |
|  6 | 1200.00 |
+----+---------+
2 rows in set (0.00 sec)

mysql&gt; CALL transfer_money(6, 5, 20);
Query OK, 0 rows affected (0.06 sec)

mysql&gt; SELECT id, amount
       FROM accounts
       WHERE id = 5 OR id = 6;
+----+---------+
| id | amount  |
+----+---------+
|  5 |  191.98 |
|  6 | 1220.00 |
+----+---------+
2 rows in set (0.00 sec)</pre>
<p>В следващата статия ще покажем как можем да дефинираме и използваме променливи вътре в процедура.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1499-%d0%bf%d1%80%d0%be%d1%86%d0%b5%d0%b4%d1%83%d1%80%d0%b8-%d0%b8-%d0%b2%d1%85%d0%be%d0%b4%d0%bd%d0%b8-%d0%bf%d0%b0%d1%80%d0%b0%d0%bc%d0%b5%d1%82%d1%80%d0%b8/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Заключване на данните при транзакция</title>
		<link>http://www.cphpvb.net/db/1487-%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b2%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8%d1%82%d0%b5-%d0%bf%d1%80%d0%b8-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d1%8f/</link>
		<comments>http://www.cphpvb.net/db/1487-%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b2%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8%d1%82%d0%b5-%d0%bf%d1%80%d0%b8-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d1%8f/#comments</comments>
		<pubDate>Sat, 18 Apr 2009 17:23:10 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1487</guid>
		<description><![CDATA[Синхронизацията на данните е изключително важна. За да демонстрираме това нека покажем първо един пример. Нека проверим първо колко пари има в акаунт с id = 1: mysql&#62; USE banks; Database changed mysql&#62; SELECT amount FROM accounts WHERE id = 1; +--------+ &#124; amount &#124; +--------+ &#124; 306.38 &#124; +--------+ 1 row in set (0.00 [...]]]></description>
			<content:encoded><![CDATA[<p>Синхронизацията на данните е изключително важна. За да демонстрираме това нека покажем първо един пример. Нека проверим първо колко пари има в акаунт с id = 1:</p>
<pre>mysql&gt; USE banks;
Database changed

mysql&gt; SELECT amount FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 306.38 |
+--------+
1 row in set (0.00 sec)</pre>
<p>Сега нека напишем заявка UPDATE, с която искаме да изтеглим 500 лева, но така, че ако искаме да няма такава наличност, то заявката да не се изпълни:<span id="more-1487"></span></p>
<pre>mysql&gt; <strong>UPDATE accounts SET amount = amount - 500 WHERE id = 1 AND amount &gt;= 500; </strong>Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql&gt; SELECT amount FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 306.38 |
+--------+
1 row in set (0.00 sec)</pre>
<p>Нека сега стартираме две връзки към базата от данни и да започнем транзакции. С тези транзакции ние ще се опитаме да изтеглим два пъти по 250 лева от акаунт с id = 1:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/readlock1.png"><img class="aligncenter size-medium wp-image-1488" title="Read Lock 1" src="http://www.cphpvb.net/wp-content/uploads/2009/04/readlock1-300x185.png" alt="Read Lock 1" width="300" height="185" /></a></p>
<p>Виждаме, че втората връзка не върна резултат &#8211; тя стои в &#8222;спящ&#8220; (idle) режим. Това е така, защото сървъра знае, че в този момент друга транзакция все още не е приключила, тоест резултатът от нея все още не е записан. Така втората връзка все още не &#8222;вижда&#8220;, че вече сумата в акаунта е намалена и ако вземе 250 лева, то банката ще изгуби пари. Затова при стартиране на транзакция се прави т.нар. &#8222;заключване&#8220; (lock).</p>
<p>Нека сега приключим първата транзакция:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/readlock2.png"><img class="aligncenter size-medium wp-image-1489" title="readlock2" src="http://www.cphpvb.net/wp-content/uploads/2009/04/readlock2-300x184.png" alt="readlock2" width="300" height="184" /></a></p>
<p>Виждаме, че това се отрази моментално във втората връзка. Както очаквахме там UPDATE не се направи, защото в акаунта вече няма достатъчно пари (те бяха взети от първата връзка).</p>
<p>Същото заключване на транзакции важи и за INSERT заявки. Нека създадем една примерна елементарна база от данни:</p>
<pre>mysql&gt; CREATE DATABASE locks;
Query OK, 1 row affected (0.00 sec)

mysql&gt; USE locks;
Database changed

mysql&gt; CREATE TABLE test(
                     val INT
       ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.14 sec)

mysql&gt; INSERT INTO test
       VALUES (1), (2);
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0</pre>
<p>Нека сега стартираме две транзакции, с които искаме да прочетем най-голямата стойност в колоната val и съответно добавят нова стойност с едно по-голяма от втората:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock1.png"><img class="aligncenter size-medium wp-image-1491" title="Transaction insert lock" src="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock1-300x186.png" alt="Transaction insert lock" width="300" height="186" /></a></p>
<p>Виждаме, че отново втората транзакция изчаква завършването на първата. В момента, в който извършим COMMIT в първата транзакция втората също ще изпълни заявката си:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock2.png"><img class="aligncenter size-medium wp-image-1492" title="Transaction insert lock 2" src="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock2-300x185.png" alt="Transaction insert lock 2" width="300" height="185" /></a></p>
<p>Сега ще видим, че се е получило точно това, което искахме &#8211; първата транзакция е добавила val = 3, а втората val = 4:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock3.png"><img class="aligncenter size-medium wp-image-1493" title="Transaction insert lock 3" src="http://www.cphpvb.net/wp-content/uploads/2009/04/insertlock3-300x185.png" alt="Transaction insert lock 3" width="300" height="185" /></a></p>
<p>Ако заключването на връзката не съществуваше, то и двете транзакции щяха да вмъкнат стойност &#8222;3&#8243; и щяхме да имаме повтарящи се редове.</p>
<p>От тези примери трябва да си извадим важна бележка &#8211; важно е да приключваме транзакциите си възможно най-бързо. Ако ние се &#8222;бавим&#8220;, то други транзакции ще трябва да ни изчакват. В MySQL има стандартен timeout от няколко секунди (естествено може да бъде настройван чрез my.cnf/my.ini) за изчакване &#8211; ако дадена заявка е в режим &#8222;изчакване&#8220; и този timeout бъде достигнат, то заявката ще пропадне.</p>
<p>Заключването на връзките важи за заявки UPDATE, INSERT и DELETE. По подразбиране то не е валидно за заявки от тип SELECT. MySQL обаче ни предоставя възможност да го правим ръчно:</p>
<pre>SELECT &lt;rows&gt; FROM &lt;table&gt; <strong>FOR UPDATE</strong>;</pre>
<p>В последния пример указваме, че данните, които са върнати от SELECT заявката, ще бъдат заключени. По този начин ако някоя друга транзакция се опита да чете или променя данните, то тя ще трябва да изчака изпълнението на първата започната.</p>
<p>По-слабо заключване е &#8222;LOCK IN SHARE MODE&#8220;, с което позволяваме на други транзакции да четат, но не и да променят данните. Чрез такова заключване сме сигурни, че прочетените данни ще бъдат най-новите налични в системата и никоя друга транзакция няма да ги промени междувременно:</p>
<pre>SELECT &lt;rows&gt; FROM &lt;table&gt; <strong>LOCK IN SHARE MODE</strong>;</pre>
<p>С последния пример при отваряне на втора връзка всички видове заявки ще бъдат блокирани в idle режим докато първата транзакция не завърши.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1487-%d0%b7%d0%b0%d0%ba%d0%bb%d1%8e%d1%87%d0%b2%d0%b0%d0%bd%d0%b5-%d0%bd%d0%b0-%d0%b4%d0%b0%d0%bd%d0%bd%d0%b8%d1%82%d0%b5-%d0%bf%d1%80%d0%b8-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d1%8f/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Транзакции</title>
		<link>http://www.cphpvb.net/db/1458-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d0%b8/</link>
		<comments>http://www.cphpvb.net/db/1458-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d0%b8/#comments</comments>
		<pubDate>Sat, 18 Apr 2009 16:21:24 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1458</guid>
		<description><![CDATA[Транзакция наричаме последователност от SQL заявки, която трябва да изпълняват условието или всичките да бъдат изпълнени или нито една от тях да не бъде изпълнена. Може да дадем класически пример с банковите транзакции. Например ако искаме да прехвърлим 50 лева от акаунт 1 в акаунт 2, то трябва да изпълним следните две заявки: UPDATE accounts [...]]]></description>
			<content:encoded><![CDATA[<p>Транзакция наричаме последователност от SQL заявки, която трябва да изпълняват условието или всичките да бъдат изпълнени или нито една от тях да не бъде изпълнена. Може да дадем класически пример с банковите транзакции. Например ако искаме да прехвърлим 50 лева от акаунт 1 в акаунт 2, то трябва да изпълним следните две заявки:<span id="more-1458"></span></p>
<pre>UPDATE accounts
SET amount = amount - 50
WHERE id = 1;

UPDATE accounts
SET amount = amount + 50
WHERE id = 2;</pre>
<p>Какво обаче ще се случи ако например втората заявка не се изпълни (например възникне грешка)? Отговорът е, че парите ще бъдат изгубени. Тук се явява силата на транзакциите &#8211; те гарантират че ако някоя заявка не се изпълни, то данните ще бъдат възстановени в първоначален вид.</p>
<p>Групирането на заявки в транзакция се изпълнява изключително лесно. Единствено трябва да оградим данните с BEGIN (започване на транзакция) и COMMIT (край на транзакция):</p>
<pre><strong>BEGIN;</strong>

UPDATE accounts
SET amount = amount - 50
WHERE id = 1;

UPDATE accounts
SET amount = amount + 50
WHERE id = 2;

<strong>COMMIT;</strong></pre>
<p>Ако някоя от транзакциите пропадне, то се прави т.нар. ROLLBACK. За целта се използва innodb log файл, в който се записват старите данни преди изпълнението на всяка заявка. Естествено ние можем да правим ROLLBACK и сами. Например:</p>
<pre>mysql&gt; SELECT amount
FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 106.38 |
+--------+
1 row in set (0.00 sec)

mysql&gt; <strong>BEGIN;</strong>
Query OK, 0 rows affected (0.00 sec)

mysql&gt; UPDATE accounts
SET amount = amount - 50
WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql&gt; <strong>ROLLBACK;</strong>
Query OK, 0 rows affected (0.36 sec)

mysql&gt; SELECT amount
FROM accounts
WHERE id = 1;
+--------+
| amount |
+--------+
| 106.38 |
+--------+
1 row in set (0.00 sec)</pre>
<p>Виждате, че резултатите от заявката SELECT са едни и същи, тоест ROLLBACK е &#8222;върнал&#8220; данните в първоначалния им вид преди заявката UPDATE.</p>
<p>Транзакциите трябва да отговарят на условие за консистентно четене. Това означава, че всеки SELECT чете данните записани точно след последния COMMIT. Ето един пример &#8211; отворили сме две връзки към базата данни. С едната започваме транзакция и правим UPDATE като даваме 200 лева на акаунт 1. След това във втората връзка искаме да проверим наличността на този акаунт:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/consitency1.png"><img class="aligncenter size-medium wp-image-1482" title="Transactions Consitency" src="http://www.cphpvb.net/wp-content/uploads/2009/04/consitency1-300x181.png" alt="Transactions Consitency" width="300" height="181" /></a></p>
<p>След това с първата връзка завършваме транзакцията и отново проверяваме каква е стойността във втората връзка:</p>
<p><a href="http://www.cphpvb.net/wp-content/uploads/2009/04/consitency2.png"><img class="aligncenter size-medium wp-image-1483" title="Transactions Consitency" src="http://www.cphpvb.net/wp-content/uploads/2009/04/consitency2-300x181.png" alt="Transactions Consitency" width="300" height="181" /></a></p>
<p>Важно е да споменем, че при InnoDB всяка заявка, която НЕ участва в блокове &#8222;BEGIN &#8211; COMMIT&#8220; е автоматично записана, т.е. можем да приемем, че единичните заявки са завършени транзакции сами по себе си.</p>
<p>В следващата статия ще разгледаме &#8222;заключвания&#8220; с цел синхронизация на транзакции.</p>
<p><em>Забележка</em>: За стартиране на транзакция можете да използвате и по-популярната сред останалите бази данни &#8222;START TRANSACTION&#8220; команда.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1458-%d1%82%d1%80%d0%b0%d0%bd%d0%b7%d0%b0%d0%ba%d1%86%d0%b8%d0%b8/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Настройки на MySQL server</title>
		<link>http://www.cphpvb.net/db/1409-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b8-%d0%bd%d0%b0-mysql-server/</link>
		<comments>http://www.cphpvb.net/db/1409-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b8-%d0%bd%d0%b0-mysql-server/#comments</comments>
		<pubDate>Wed, 08 Apr 2009 18:50:50 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1409</guid>
		<description><![CDATA[MySQL сървърите са изключително лесни за настройка &#8211; повечето от опциите се контролират от един текстов файл. Под Linux/BSD той е /etc/my.cnf, а под Windows файлът се казва my.ini и се намира в инсталационната директория на MySQL. По настройките има някои съществени разлики между Linux-базираните платформи и Windows варианта. Понеже MySQL се използва по-често под [...]]]></description>
			<content:encoded><![CDATA[<p>MySQL сървърите са изключително лесни за настройка &#8211; повечето от опциите се контролират от един текстов файл. Под Linux/BSD той е /etc/my.cnf, а под Windows файлът се казва my.ini и се намира в инсталационната директория на MySQL.</p>
<p>По настройките има някои съществени разлики между Linux-базираните платформи и Windows варианта. Понеже MySQL се използва по-често под Linux и FreeBSD, то ще разгледаме предимно настройки за тези платформи. Някои от тях може би не са валидни под Windows.</p>
<p>В MySQL има изключително много променливи за настройка. Някои от тях обаче са изключително важни за общото бързодействие на системата. За да видите списък на променливите и техните стойности използвайте следната команда:<span id="more-1409"></span></p>
<pre>show variables;</pre>
<p>За да видите работните процеси:</p>
<pre>show status;</pre>
<p>Сега ще разгледаме някои от най-важните променливи:</p>
<p>1. <strong>key_buffer_size</strong> &#8211; Изключително важна настройка за MyISAM таблици. Обикновено при първоначална инсталация стойността на тази променлива се слага грубо на около 30% от големината на RAM паметта на системата при положение, че ще се използва предимно MyISAM. По-фините настройки зависят от големината на индексите и информацията. В по-новите системи се използва предимно InnoDB, но въпреки това все пак трябва да се заделят като минимум 16MB key_buffer_size, защото той ще се използва за създаване на временни таблици. Ако тази променлива е с недостатъчна големина, то ще се използват дискови операции, които забавят системата значително;</p>
<p>2. <strong>innodb_buffer_pool_size</strong> &#8211; Когато става дума да InnoDB таблици, то стойността на този параметър е изключително важен. В RAM паметта се кешират и данните и индексите. Поради тази причина ако имате само бази данни от тип InnoDB, то не е неоправдано да заделите изключително голямо количество за innodb_buffer_pool_size &#8211; над 30% от паметта на системата.</p>
<p>3. <strong>innodb_additional_mem_pool_size</strong> &#8211; Тази настройка няма реален ефект върху производителността. Използва се за специфични нужди на InnoDB. Една стойност от около 20MB е добра за старт на натоварен сървър.</p>
<p>4. <strong>table_cache</strong> &#8211; Отварянето на таблици е бавна операция. Когато дадена таблица се достъпва, тя се маркира като &#8222;currently in use&#8220; в своя заглавен файл (header file). Затова е хубаво да имаме достатъчно голям table_cache, за да можем да имаме колкото се може повече отворени таблици наведнъж. Ако в системата ви има не повече от 300 таблици, то стойност на table_cache около 1000 може би ще бъде достатъчна. Имайте в предвид, че всяка една връзка към базата данни отваря поне една таблица. При изключително натоварени бази данни стойността на table_cache може да се направи огромна.</p>
<p>5. <strong>query_cache_size</strong> &#8211; Ако приложението ви изключително много чете данни, то е добре да си направите кеш на заявките и така да облекчите тяхната &#8222;компилация&#8220;. Тук обаче в никакъв случай не трябва да се прекалява, защото практиката показва влошаване при преоразмеряване. Стойности от порядъка на 32 до 128MB са нормални. Обикновено се гледа стойността от статистиките на сървъра &#8222;cache hit ratio&#8220; и ако той е нисък, то стойността на query_cache_size се увеличава.</p>
<p>6. <strong>thread_cache</strong> &#8211; Създаването и унищожаването на нишки при връзка и изход от базата данни също отнема ресурси. Създаването на кеш за нишките често може да подобри бързината на инициализация. Обикновено стойността трябва да варира между 12 и 16. Увеличава се повече само при изключително натоварени сървъри.</p>
<p>7. <strong>sort_buffer</strong> &#8211; Увеличава скоростта на операциите myisamchk. Може да бъде изключително полезна при таблици с често сортиране.</p>
<p>8. <strong>read_rnd_buffer_size</strong> &#8211; Използва се при четене на вече сортирани колони от таблици. Отново ако имате често сортиране, то увеличавайки стойността ще увеличите и бързодействието. Имайте в предвид, че за разлика от key_buffer_size и table_cache, тази памет се заделя отделно за всяка една нишка. Обикновено правилото е да заделите по 1KB за всеки 1MB от рам паметта на сървъра.</p>
<p>9. <strong>innodb_log_file_size</strong> &#8211; Ако използвате много транзакции, то тази стойност е изключително важна. По-голяма стойност ще увеличи изключително много бързодействието, но за сметка на това увеличава времето за възстановяване след неуспешна транзакция, т.е. трябва да се планира според системата. Поради таци причина препоръчителните стойности могат да варират между 64M и 512M.</p>
<p>10. <strong>innodb_log_buffer_size</strong> &#8211; Обикновено тази променлива не се променя и се използва стойността й по подразбиране. Единствената причина за увеличаване може да е ако имате много заявки от тип UPDATE и полета от тип TEXT/BLOB. Този буфер така или иначе се изтрива всяка секунда и затова стойности над 8MB до 12MB не са оправдани и ще дадат само разход на памет. При по-малки системи дори можете да занижите под стойността по подразбиране.</p>
<p>11. <strong>innodb_flush_log_at_trx_commit</strong> &#8211; Ако използвате InnoDB, то тази променлива може да окаже изключителна разлика в бързодействието, но за сметка на това е опасна за цялостта на данните. По подразбиране то е 1, т.е. за всеки COMMIT на заявка или всяка заявка извън транзакция (т.е. не участваща в транзакция) ще трябва да се изчиства log файла на диска. Най-често проблем се вижда при преминаване на едно приложение от MyISAM към InnoDB, защото MyISAM не използва транзакции. Ако сложите стойност 2, то ще се изчиства само кеша на операционната система. Стойност 0 няма да изчиства кеша и бързодействието се повишава значително, но риска от загуба на информация при непредвидено спиране на системата е огромен. При стойност 2 може да се изгуби информация само при блокиране на операционната система.</p>
<p>12.  <strong>tmp_table_size</strong> &#8211; Ако често използвате временни таблици е добре да предвидите повече рам памет, за да не се записват на хард диска.</p>
<p>13. <strong>myisam_sort_buffer_size</strong> &#8211; Използва се при REPAIR TABLE, CREATE TABLE и CREATE INDEX. Тези операции са редки и често можете да намалите тази стойност до минимум.</p>
<p>14. <strong>max_tmp_tables</strong> &#8211; Колко временни таблици могат да бъдат използвани едновременно? Отговорът е, че зависи от приложенията.</p>
<p>15. <strong>max_tmp_tables</strong> &#8211; Колко рам памет да бъде заделена за временните ви таблици? Ако работите с BLOB/TEXT, то може би доста. Ако паметта е недостатъчна е ясно, че ще се прибегне до дискови операции.</p>
<p>16. <strong>max_connections</strong> &#8211; Това е най-опасната опция. За всяка една връзка към базата данни се заделя голямо количество памет (всички буфери). Ако преоразмерите тази стойност, то обикновено няма да има проблем. При пикови моменти обаче може да се надхвърли физическата оперативна памет и това да доведе до изключително слаба производителност за всички нишки. Ако пък стойността се направи малка, то може лесно да се достигне лимита и много клиентски приложения да бъдат блокирани. Затова се казва, че max_connections е аларма за upgrade на системата. Обикновено тази стойност трябва да се държи на 200% от средната стойност на едновременни връзки към базата данни от статистиката. Ако статистическите данни покажат увеличаване на едновременните връзки към базата данни, а физическото количество памет ще бъде прехвърлено при увеличаване на max_connections, то трябва да сложите още рам на сървъра.</p>
<p>17. <strong>read_buffer_size</strong> &#8211; Ако са ви нужни бързи &#8222;full table scan&#8220;, т.е. заявки без използване на index, то стойността на read_buffer_size трябва да е голяма. На теория е така, но на практика се оказва, че поради външни фактори (най-често операционна система) производителността не се увеличава. Статистиката сочи, че дори при доста голяма таблица от около 5 000 000 записа, стойности над 128KB на read_buffer_size ще намалят производителността, вместо да я подобрят!</p>
<p>18. <strong>join_buffer_size</strong> &#8211; Буфера за правене на JOIN между таблици е стандартно доста малък, като се има в предвид, че тъй или иначе не е добра идея да се правят големи JOIN връзки между таблици. Обикновено администраторите предпочитат да го увеличат до около 1MB.</p>
<p>19. <strong>thread_stack</strong> &#8211; Обикновено не е полезно да пипате тази променлива (в никакъв случай не я намалявайте). При Linux системи увеличаването й няма да подобри почти нищо, а само ще изразходва повече рам. При FreeBSD може да се забележат минимални подобрения. Това ще бъде една от последните опции, които би трябвало да гледате за оптимизация.</p>
<p>20. <strong>max_packet_size</strong> &#8211; Стойност, която трябва да се променя спрямо базите данни в системата. Увеличаването й ще подобри скоростта, като намали броят на пакетите, но за сметка на това увеличава значително изискването за повече рам памет. Ако не се качват големи файлове в базите данни в системата, то дръжте тази стойност ниска.</p>
<p>Всичко казано дотук е добро, но все пак трябва да се съобразим с размера на рам паметта на сървъра. Общата формула(*) е:</p>
<pre>Рам паметта на сървъра &gt;=
рам паметта използвана от операционната система +
рам паметта използвана от другите програми (Apache, mail server, и т.н.)
рам паметта използвана от програмите на MySQL (средно 32MB) +
key_buffer_size +
innodb_buffer_pool_size +
innnodb_additional_memory_pool_size +
innodb_log_buffer_size +
max_tmp_tables * tmp_table_size +
query_cache_size +
3 * myisam_sort_buffer_size +
max_connections * (
	read_buffer_size +
	join_buffer_size +
	read_rnd_buffer_size +
	thread_stack +
	2*max_packet_size
)</pre>
<p><strong>*</strong> &#8211; формулата е взета от следната статия:<br />
<a href="http://www.paragon-cs.com/wordpress/?p=198" target="_blank">http://www.paragon-cs.com/wordpress/category/key_buffer_size/</a></p>
<p>Имайте в предвид, че повечето от казаните по-горе неща важат за сървъри, които работят предимно с бази данни. Ако имате други приложения, които натоварват сървъра много, то трябва да се съобразявате с тях.</p>
<p>Има и още някои тънкости, свързани с кеша. Например query_cache прави разлика между малки и големи букви. Например заявките &#8222;SELECT * FROM banks&#8220; и &#8222;select * from banks&#8220; за query_cache ще са две различни заявки. Това е и една от причините да спазваме някакви конвенции при програмирането на бази данни.</p>
<p>При силно използване на MyISAM е добре да гледате т.нар. &#8222;key_buffer_size hit ratio&#8220;. То се изчислява по следната формула:</p>
<pre>Key_reads/Key_read_requests</pre>
<p>Ако стойността е над 0,01, то е добре да помислите за увеличаване на key_buffer_size.</p>
<p>Накрая е важно да споменем и променливата за &#8222;прекъсване&#8220; (timeout). <strong>Wait_timeout</strong> е променлива, която контролира &#8222;спящите&#8220; връзки. Често програмистите на приложения забравят да затворят връзките си към базата данни. Това естествено рефлектира с изразходване на памет, което никак не е добре. Намаляване на wait_timeout ще затваря спящите връзки по-бързо. Обикновено предпочитанията на администраторите са в интервала 10 до 15 секунди. Имайте в предвид обаче, че намаляването на wait_timeout може да рефлектира в нужда от увеличаване на max_connections. Тук в голяма помощ идва thread_cache променливата, за която писахме по-горе.</p>
<p>Също така опитайте оптимизация по следната формула:</p>
<pre>table_cache = opened table / max_used_connection</pre>
<p>Трябва да знаете, че няма стриктна формула, по която нашия MySQL сървър да работи най-добре. Обикновено настройките са един постоянен и продължителен процес. Добрите администратори непрекъснато следят системата си и я &#8222;тунинговат&#8220; според натоварването.</p>
<p>В тази статия сме дали само най-важните променливи, свързани с производителност на системата. Имайте в предвид, че общо за MySQL 5.1 общо променливите са над 260 (естествено не всички са използваеми и не всички свързани с производителност)&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1409-%d0%bd%d0%b0%d1%81%d1%82%d1%80%d0%be%d0%b9%d0%ba%d0%b8-%d0%bd%d0%b0-mysql-server/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Индекси</title>
		<link>http://www.cphpvb.net/db/1404-%d0%b8%d0%bd%d0%b4%d0%b5%d0%ba%d1%81%d0%b8/</link>
		<comments>http://www.cphpvb.net/db/1404-%d0%b8%d0%bd%d0%b4%d0%b5%d0%ba%d1%81%d0%b8/#comments</comments>
		<pubDate>Wed, 08 Apr 2009 06:23:27 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1404</guid>
		<description><![CDATA[Индексите са обекти в базата данни, които ни осигуряват бърз достъп до редовете на базова таблица, чрез физическото представяне (адреси в паметта) на данните. Индексите се създават върху колони на таблиците. Присъствието или отсъствието на индекс няма ефект върху крайния резултат на заявките. Единствената разлика е в евентуалното повишено бързодействие (при по-големи таблици може разликата да [...]]]></description>
			<content:encoded><![CDATA[<p>Индексите са обекти в базата данни, които ни осигуряват бърз достъп до редовете на базова таблица, чрез физическото представяне (адреси в паметта) на данните. Индексите се създават върху колони на таблиците.</p>
<p>Присъствието или отсъствието на индекс няма ефект върху крайния резултат на заявките. Единствената разлика е в евентуалното повишено бързодействие (при по-големи таблици може разликата да е огромна). Важно е обаче да създаваме индексите правилно, защото от това зависи дали системата ще ги използва или не.</p>
<p>За да демонстрираме повишеното бързодействие сме изпълнили една проста заявка на доста бавен компютър.:<span id="more-1404"></span></p>
<pre>mysql&gt; SELECT * FROM accounts
    -&gt; WHERE customer_id = 11;
+----+--------+------+-----------+-------------+
| id | amount | type | branch_id | customer_id |
+----+--------+------+-----------+-------------+
| 12 |  99.18 |    1 |         2 |          11 |
+----+--------+------+-----------+-------------+
1 row in set (0.30 sec)</pre>
<p>Нека сега създадем индекс по колоната, която участва в условието WHERE на оператора SELECT и изпълним заявката отново:</p>
<pre>mysql&gt; CREATE INDEX accounts_customer_id
    -&gt; ON accounts(customer_id);
Query OK, 27 rows affected (1.93 sec)
Records: 27  Duplicates: 0  Warnings: 0

mysql&gt; SELECT * FROM accounts
    -&gt; WHERE customer_id = 11;
+----+--------+------+-----------+-------------+
| id | amount | type | branch_id | customer_id |
+----+--------+------+-----------+-------------+
| 12 |  99.18 |    1 |         2 |          11 |
+----+--------+------+-----------+-------------+
1 row in set (0.03 sec)</pre>
<p>Виждате, че разликата в бързодействието е 10 пъти! За тази тестова постановка изтрих базата данни, рестартирах сървъра и я създадох наново, защото обикновено системата за управление на бази данни си пази cache на вече използвани заявки. Също така трябва да отбележа, че в този тестов пример колоната customer_id НЕ Е foreign key. Системата за управление на бази данни обикновено създава автоматично индекси по първични и външни ключове.</p>
<p>Обикновено за структура от данни на индексите се използва BTREE (бинарно дърво). При различните системи има възможности за използване на алтернативни структури, но това не е задължително.</p>
<p>Истината е, че индексите не винаги са подходящи. В редица случаи те може да навредят на производителността на системата. Това се получава ако имаме таблици, върху които често правим заявки от тип UPDATE, INSERT и DELETE, то всеки път индексът трябва да се обновява. Затова таблици, в които често се добавят данни и рядко се четат такива е по-добре да не използваме индекс.</p>
<p>Същото важи и за индексите върху колони с много повтарящи се редове. Ако имаме такава колона, в която данните се повтарят изключително много, то по-добре да не създаваме индекс, защото ефектът ще бъде на забавяне на системата. Създавайте предимно индекси върху колони с ключ UNIQUE.</p>
<p>Ако вашите таблици се обновяват периодично (например в края на месеца се обновява таблица със статистики), то следвайте следната последователност:<br />
- Изтриване на индекса;<br />
- Обновяване на данните;<br />
- Създаване на индекса отново.</p>
<p>Ето още един пример за създаване и изтриване на индекс:</p>
<pre>mysql&gt; CREATE INDEX customers_name
    -&gt; ON customers(name);
Query OK, 22 rows affected (0.17 sec)
Records: 22  Duplicates: 0  Warnings: 0

mysql&gt; DROP INDEX customers_name
    -&gt; ON customers;
Query OK, 22 rows affected (0.15 sec)
Records: 22  Duplicates: 0  Warnings: 0</pre>
<p>Можете лесно да се досетите, че е възможно да направите повече от един индекс върху една таблица. В последствие когато правите заявки SELECT MySQL обикновено се досеща кой индекс е най-подходящ при заявката. Въпреки това е възможно вие сами да укажете кой индекс да се използва:</p>
<pre>mysql&gt; SELECT * FROM mytable
    -&gt; USE INDEX (col1index, col2index)
    -&gt; WHERE col1 = 1 AND col2 = 2;</pre>
<p>Ако пък желаете е възможно да укажете кой индекс да НЕ се използва чрез командата &#8222;IGNORE INDEX&#8220;:</p>
<pre>mysql&gt; SELECT * FROM mytable
    -&gt; IGNORE INDEX (col1index)
    -&gt; WHERE col1 = 1 AND col2 = 2;</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1404-%d0%b8%d0%bd%d0%b4%d0%b5%d0%ba%d1%81%d0%b8/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Виртуални таблици (view)</title>
		<link>http://www.cphpvb.net/db/1402-%d0%b2%d0%b8%d1%80%d1%82%d1%83%d0%b0%d0%bb%d0%bd%d0%b8-%d1%82%d0%b0%d0%b1%d0%bb%d0%b8%d1%86%d0%b8-view/</link>
		<comments>http://www.cphpvb.net/db/1402-%d0%b2%d0%b8%d1%80%d1%82%d1%83%d0%b0%d0%bb%d0%bd%d0%b8-%d1%82%d0%b0%d0%b1%d0%bb%d0%b8%d1%86%d0%b8-view/#comments</comments>
		<pubDate>Tue, 07 Apr 2009 16:22:07 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1402</guid>
		<description><![CDATA[Виртуалните таблици са още познати с директния си превод от английски език като &#8222;изгледи&#8220;. На практика виртуалната таблица е съхранен SQL SELECT оператор, който си има собствено име в базата данни. Използва се когато често използваме едни и същи SELECT заявки. Виртуалните таблици имат и редица други предимства: - Различните потребители в системата могат да [...]]]></description>
			<content:encoded><![CDATA[<p>Виртуалните таблици са още познати с директния си превод от английски език като &#8222;изгледи&#8220;. На практика виртуалната таблица е съхранен SQL SELECT оператор, който си има собствено име в базата данни. Използва се когато често използваме едни и същи SELECT заявки.</p>
<p>Виртуалните таблици имат и редица други предимства:<br />
- Различните потребители в системата могат да виждат едни и същи данни по различен начин;<br />
- Удобни са за ограничаване се достъпа на потребителите до базовата таблица и така те могат да достъпват само данните, които извежда виртуалната таблица.</p>
<p>Виртуална таблица се създава чрез операторът CREATE VIEW:<span id="more-1402"></span></p>
<pre>CREATE VIEW &lt;име&gt;(&lt;имена на колони&gt;)
AS SELECT &lt;имена на колони&gt; ... ;</pre>
<p>Във вложения оператор SELECT не може да се използва ORDER BY и UNION. Важно е да има съответствие между върнатите колони от оператора SELECT и изброените след името на VIEW.</p>
<p>Пример: Създаване на виртуална таблица с данните на служителите на банка Bulbank:</p>
<pre> CREATE VIEW bulbank_employees
 AS SELECT * FROM employees
    WHERE branch_id IN(
       SELECT id FROM branches
       WHERE bank_code IN(
          SELECT code FROM banks
          WHERE name = "Bulbank"
       )
    );</pre>
<p>Сега можем да го използваме също както обикновена таблица:</p>
<pre>mysql&gt; SELECT * FROM bulbank_employees;
+----+-----------------+-----------+
| id | name            | branch_id |
+----+-----------------+-----------+
|  1 | Ivan Ivanov     |         1 |
|  3 | Mihail Zahariev |         1 |
|  4 | Milen Stoilov   |         2 |
|  5 | Svilen Petrov   |         2 |
|  6 | Ilian Stoianov  |         2 |
|  7 | Petar Petrov    |         2 |
+----+-----------------+-----------+
6 rows in set (0.00 sec)</pre>
<p>Можем да изпълняваме и заявки от тип INSERT, UPDATE и DELETE &#8211; те ще бъдат трансформирани автоматично от системата за управление на бази данни върху базовата таблица.</p>
<p>Ако в операторът SELECT участва агрегатна функция или GROUP BY, то става напълно необновяемо. Изобщо използването на INSERT, UPDATE и DELETE при VIEW не се препоръчва. Най-често VIEW се използва &#8222;само за четене&#8220; и съответно потребителите се рестриктират да имат само права SELECT върху тези таблици.</p>
<p>За изтриването на VIEW става чрез оператора DROP VIEW:</p>
<pre>DROP VIEW bulbank_employees CASCADE;</pre>
<p>Ключовата дума CASCADE означава, че ако има производно на това VIEW (т.е. VIEW създадено чрез изтриваното VIEW), то също ще бъде изтрито. Алтернативата е с ключова дума RESTRICT, където ако има производно VIEW, то ще бъде върната грешка. По принцип не е добра идея да създавате производни една на друга виртуални таблици. Стойността по подразбиране на DROP VIEW e CASCADE.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1402-%d0%b2%d0%b8%d1%80%d1%82%d1%83%d0%b0%d0%bb%d0%bd%d0%b8-%d1%82%d0%b0%d0%b1%d0%bb%d0%b8%d1%86%d0%b8-view/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Заявки Delete</title>
		<link>http://www.cphpvb.net/db/1389-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-delete/</link>
		<comments>http://www.cphpvb.net/db/1389-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-delete/#comments</comments>
		<pubDate>Mon, 06 Apr 2009 16:25:31 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1389</guid>
		<description><![CDATA[Подобно на INSERT, заявките от тип DELETE са с изключително прост синтаксис: DELETE FROM &#60;таблица&#62; WHERE &#60;условие&#62;; Нека демонстрираме един пример с базата от данни &#8222;banks&#8220;. Нека видим първо списък на акаунтите: mysql&#62; SELECT * FROM accounts; +----+----------+------+-----------+-------------+ &#124; id &#124; amount &#124; type &#124; branch_id &#124; customer_id &#124; +----+----------+------+-----------+-------------+ &#124; 1 &#124; 156.38 &#124; [...]]]></description>
			<content:encoded><![CDATA[<p>Подобно на INSERT, заявките от тип DELETE са с изключително прост синтаксис:</p>
<pre>DELETE FROM &lt;таблица&gt;
WHERE &lt;условие&gt;;</pre>
<p>Нека демонстрираме един пример с базата от данни &#8222;banks&#8220;. Нека видим първо списък на акаунтите:<span id="more-1389"></span></p>
<pre>mysql&gt; SELECT * FROM accounts;
+----+----------+------+-----------+-------------+
| id | amount   | type | branch_id | customer_id |
+----+----------+------+-----------+-------------+
|  1 |   156.38 |    2 |         1 |           1 |
|  2 |   136.22 |    1 |         1 |           2 |
|  3 |    42.98 |    1 |         1 |           3 |
|  4 |  1236.33 |    1 |         1 |           4 |
|  5 |   211.98 |    2 |         1 |           5 |
|  6 |  1200.00 |    2 |         2 |           6 |
|  7 |   133.48 |    1 |         2 |           7 |
|  8 |   256.41 |    2 |         2 |           8 |
|  9 |  1331.50 |    2 |         2 |           9 |
| 10 |   116.88 |    2 |         2 |          10 |
| 11 |   200.91 |    1 |         2 |          10 |
| 12 |    99.18 |    1 |         2 |          11 |
| 13 |  6712.52 |    1 |         3 |          12 |
| 14 | 12000.56 |    1 |         3 |          12 |
| 15 |   322.99 |    2 |         3 |          12 |
| 16 |   991.63 |    1 |         3 |          13 |
| 17 |   559.32 |    2 |         3 |          14 |
| 18 |   680.13 |    1 |         3 |          15 |
| 19 |   532.57 |    1 |         3 |          15 |
| 20 |   402.26 |    1 |         3 |          16 |
| 21 |  1536.91 |    2 |         4 |          17 |
| 22 | 14921.43 |    1 |         4 |          18 |
| 23 |  3910.50 |    1 |         5 |          19 |
| 24 |   231.37 |    1 |         5 |          20 |
| 25 |  7236.60 |    1 |         5 |          21 |
| 26 |  2226.63 |    2 |         5 |          21 |
| 27 |   500.00 |    2 |         6 |          22 |
+----+----------+------+-----------+-------------+
27 rows in set (0.00 sec)</pre>
<p>Нека изтрием акаунт с id=26:</p>
<pre>DELETE FROM accounts
WHERE id = 26;</pre>
<p>Ако изпълните заявката SELECT отново ще видите, че въпросният ред е изчезнал.</p>
<p>Важно е да се знае, че при използването на FOREIGN KEYS и ON DELETE CASCADE ще бъдат изтрити и записите на редове в таблици, които &#8222;сочат&#8220; към записа, който ще бъде изтрит. Например нека видим таблицата &#8222;customers&#8220;:</p>
<pre>mysql&gt; SELECT * FROM customers;
+----+-------------------+---------+----------+
| id | name              | address | bank_mgr |
+----+-------------------+---------+----------+
|  1 | Todor Ivanov      | NULL    |        1 |
|  2 | Petko Stoianov    | NULL    |        1 |
|  3 | Neno Nenov        | NULL    |        2 |
|  4 | Mariana Zaharieva | NULL    |        3 |
|  5 | Elica Zaharieva   | NULL    |        3 |
|  6 | Atanas Petrov     | NULL    |        4 |
|  7 | Ivan Ivanov       | NULL    |        4 |
|  8 | Zlatomir Petrov   | NULL    |        4 |
|  9 | Mihail Ivchev     | NULL    |        5 |
| 10 | Todor Shtilianov  | NULL    |        6 |
| 11 | Ivailo Ivanov     | NULL    |        7 |
| 12 | George Lucas      | NULL    |        8 |
| 13 | George Harison    | NULL    |        8 |
| 14 | Michael Jackson   | NULL    |        8 |
| 15 | Tony Martin       | NULL    |        8 |
| 16 | Tony McCarter     | NULL    |       10 |
| 17 | Alexander Smith   | NULL    |       11 |
| 18 | Maria Smith       | NULL    |       11 |
| 19 | Alain Delrick     | NULL    |       12 |
| 20 | Devry Henry       | NULL    |       12 |
| 21 | Lenard Renne      | NULL    |       12 |
| 22 | Fontaine Rupert   | NULL    |       13 |
+----+-------------------+---------+----------+</pre>
<p>Нека изтрием клиент с id 10:</p>
<pre>DELETE FROM customers
WHERE id = 10;</pre>
<p>Ще видите, че в редовете в таблица &#8222;accounts&#8220;, чието поле &#8222;customer_id&#8220; е било равно на 10, също са изтрити:</p>
<pre>mysql&gt; SELECT * FROM accounts;
+----+----------+------+-----------+-------------+
| id | amount   | type | branch_id | customer_id |
+----+----------+------+-----------+-------------+
|  1 |   156.38 |    2 |         1 |           1 |
|  2 |   136.22 |    1 |         1 |           2 |
|  3 |    42.98 |    1 |         1 |           3 |
|  4 |  1236.33 |    1 |         1 |           4 |
|  5 |   211.98 |    2 |         1 |           5 |
|  6 |  1200.00 |    2 |         2 |           6 |
|  7 |   133.48 |    1 |         2 |           7 |
|  8 |   256.41 |    2 |         2 |           8 |
|  9 |  1331.50 |    2 |         2 |           9 |
| 12 |    99.18 |    1 |         2 |          11 |
| 13 |  6712.52 |    1 |         3 |          12 |
| 14 | 12000.56 |    1 |         3 |          12 |
| 15 |   322.99 |    2 |         3 |          12 |
| 16 |   991.63 |    1 |         3 |          13 |
| 17 |   559.32 |    2 |         3 |          14 |
| 18 |   680.13 |    1 |         3 |          15 |
| 19 |   532.57 |    1 |         3 |          15 |
| 20 |   402.26 |    1 |         3 |          16 |
| 21 |  1536.91 |    2 |         4 |          17 |
| 22 | 14921.43 |    1 |         4 |          18 |
| 23 |  3910.50 |    1 |         5 |          19 |
| 24 |   231.37 |    1 |         5 |          20 |
| 25 |  7236.60 |    1 |         5 |          21 |
| 27 |   500.00 |    2 |         6 |          22 |
+----+----------+------+-----------+-------------+
24 rows in set (0.00 sec)</pre>
<p>Виждате, че акаунти с id 10 и 11 са изтрити.</p>
<p>Това може да доведе до някои &#8222;неудобства&#8220;. Нека например се опитаме да изтрием служител с id = 2:</p>
<pre>mysql&gt; DELETE FROM employees
    -&gt; WHERE id = 2;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constrai
nt fails (`banks`.`customers`, CONSTRAINT `customers_ibfk_1` FOREIGN KEY (`bank_
mgr`) REFERENCES `employees` (`id`))</pre>
<p>Проблемът тук е, че в таблицата &#8222;customers&#8220; има FOREIGN KEY &#8222;bank_mgr&#8220;, който сочи към таблицата &#8222;employees&#8220;, но <strong>НЯМА</strong> ON DELETE CASCADE. Това всъщност е съвсем нормално, защото ако уволним даден служител не би следвало да прекратяваме договорите с клиентите на банката, които той обслужва. Как тогава все пак да изтрием служител с id = 2?</p>
<p>Отговорът е, че трябва да направим NULL или да променим към друг bank_mgr всички клиенти, които сочат към bank_mgr=2:</p>
<pre>UPDATE customers
SET bank_mgr = NULL
WHERE bank_mgr = 2;</pre>
<p>Вече можем да изтрием служител с id = 2:</p>
<pre>mysql&gt; DELETE FROM employees
    -&gt; WHERE id = 2;
Query OK, 1 row affected (0.08 sec)</pre>
<p>Въпреки, че първоначалното впечатление е, че FOREIGN KEYS ни &#8222;пречат&#8220; с тази си особеност, това всъщност ни помага значително, защото така се грижим да правилен интегритет на базата от данни. Не би трябвало в нашата база от данни да има неверни данни, нали?</p>
<p>Тук отново трябва да се обърне изключително внимание на дизайна на базата от данни. Хубаво е в ER диаграмата да си отбелязвате изрично коя връзка има ON DELETE CASCADE и коя не. Винаги преди изпълнение на DELETE трябва да прецените засегнатите връзки и ако има такава без ON DELETE CASCADE, то трябва първо да изпълните заявка от тип UPDATE.</p>
<p>В заключение ще кажем, че е възможно да си спестим този труд, като направим външния ключ с опция &#8222;ON DELETE SET NULL&#8220;. Това всъщност ще свърши абсолютно същата работа, която демонстрирахме по-горе със заявката UPDATE (то естествено беше с обучителна цел). Препоръчително е да се възползвате от тези удобства.</p>
<p><strong>Задача</strong>: Изтрийте банка с code = 2</p>
<p>Упътване: В случая не трябва просто да нулирате записите на външния ключ на customers, а трябва и да изтриете редовете. Предполага се, че когато банка бъде изтрита ще бъдат изтрити и нейните клиенти.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1389-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-delete/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Заявки Update</title>
		<link>http://www.cphpvb.net/db/1319-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-update/</link>
		<comments>http://www.cphpvb.net/db/1319-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-update/#comments</comments>
		<pubDate>Mon, 30 Mar 2009 18:13:11 +0000</pubDate>
		<dc:creator>Филип Петров</dc:creator>
				<category><![CDATA[Бази от Данни]]></category>

		<guid isPermaLink="false">http://www.cphpvb.net/?p=1319</guid>
		<description><![CDATA[Заявките от тип UPDATE се използват за обновяване на данни. Базовият синтаксис е: UPDATE &#60;име на таблица&#62; SET &#60;правило за обновяване&#62; WHERE &#60;условие&#62;; От примера с базата данни &#8222;banks&#8220; можем да направим следните задачи: 1. Добавя по 2% лихва на всички акаунти от тип 1: UPDATE accounts SET amount = amount + amount*2/100 WHERE type [...]]]></description>
			<content:encoded><![CDATA[<p>Заявките от тип UPDATE се използват за обновяване на данни. Базовият синтаксис е:</p>
<pre>UPDATE &lt;име на таблица&gt;
SET &lt;правило за обновяване&gt;
WHERE &lt;условие&gt;;</pre>
<p>От примера с базата данни &#8222;banks&#8220; можем да направим следните задачи:<span id="more-1319"></span></p>
<p>1. Добавя по 2% лихва на всички акаунти от тип 1:</p>
<pre>UPDATE accounts
SET amount = amount + amount*2/100
WHERE type = 1;</pre>
<p>2. Дава по 20 лева бонус на клиентите с име &#8216;Ivan Ivanov&#8217;:</p>
<pre>UPDATE accounts
SET amount = amount + 20
WHERE customer_id IN(
   SELECT id
   FROM customers
   WHERE name = 'Ivan Ivanov'
);</pre>
<p>3. Дава по 10 лева бонус на всички клиенти, чийто мениджър на акаунт е &#8216;John Smith&#8217;:</p>
<pre>UPDATE accounts
SET amount = amount + 10
WHERE customer_id IN(
   SELECT id
   FROM customers
   WHERE bank_mgr = (
      SELECT id
      FROM employees
      WHERE name = 'John Smith'
   )
);</pre>
<p>4. Променя адреса на клона на банка Societe General в град Paris:</p>
<pre>UPDATE branches
SET address = '17 cours Valmy'
WHERE name = 'Paris' AND bank_code = (
   SELECT code
   FROM banks
   WHERE name = 'Societe General'
);</pre>
<p>5. Можем да обновяваме информация и от две таблици. Следната заявка ще даде два лева на клиент с id 2, като същевременно ще въведе и неговия адрес:</p>
<pre>UPDATE customers JOIN accounts
   ON customers.id = accounts.customer_id
SET accounts.amount = accounts.amount + 2, address = 'Opalchenska 12'
WHERE customers.id = 2;</pre>
<p>Ясно е, че при по-свободни таблици имаме и гъвкави възможности чрез използването на LEFT и RIGHT JOIN.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.cphpvb.net/db/1319-%d0%b7%d0%b0%d1%8f%d0%b2%d0%ba%d0%b8-update/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

