Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Применение агрегатных функций и вложенных запросов в операторе выбораСодержание книги
Поиск на нашем сайте
В SQL добавлены дополнительные функции, которые позволяют вычислять обобщенные групповые значения. Для применения агрегатных функций предполагается предварительная операция группировки. В чем состоит суть операции группировки? При группировке все множество кортежей отношения разбивается на группы, в которых собираются кортежи, имеющие одинаковые значения атрибутов, которые заданы в списке группировки. Например, сгруппируем отношение R1 по значению столбца Дисциплина. Мы получим 4 группы, для которых можем вычислить некоторые групповые значения, например количество кортежей в группе, максимальное или минимальное значение столбца Оценка. Это делается с помощью агрегатных функций. Агрегатные функции вычисляют одиночное значение для всей группы таблицы. Список этих функций представлен в таблице 5.7. Таблица 5.7. Агрегатные функции
Агрегатные функции используются подобно именам полей в операторе SELECT, но с одним исключением: они берут имя поля как аргумент. С функциями SUM и AVG могут использоваться только числовые поля. С функциями COUNT, MAX и MIN могут использоваться как числовые, так и символьные поля. При использовании с символьными полями МАХ и MIN будут транслировать их в эквивалент ASCII кода и обрабатывать в алфавитном порядке. Некоторые СУБД позволяют использовать вложенные агрегаты, но это является отклонением от стандарта ANSI со всеми вытекающими отсюда последствиями. Например, можно вычислить количество студентов, сдававших экзамены по каждой дисциплине. Для этого надо выполнить запрос с группировкой по полю «Дисциплина» и вывести в качестве результата название дисциплины и количество строк в группе по данной дисциплине. Применение символа * в качестве аргумента функции COUNT означает подсчет всех строк в группе. SELECT R1.Дисциплина. СОUNТ(*) FROM R1 GROUP BY R1 Дисциплина Результат:
Если же мы хотим сосчитать количество сдавших экзамен по какой-либо дисциплине, то нам необходимо исключить неопределенные значения из исходного отношения перед группировкой. В этом случае запрос будет выглядеть следующим образом: SELECT R1.Дисциплина. COUNT(*) FROM R1 WHERE R1. Оценка IS NOT NULL GROUP BY Rl.Дисциплина Получим результат:
В этом случае строка со студентом
не попадет в набор кортежей перед группировкой, поэтому количество кортежей в группе для дисциплины «Теория информации» будет на 1 меньше. Можно применять агрегатные функции также и без операции предварительной группировки, в этом случае все отношение рассматривается как одна группа и для этой группы можно вычислить одно значение на группу. Обратившись снова к базе данных «Сессия» (таблицы Rl, R2, R3), найдем количество успешно сданных экзаменов: SELECT COUNT(*) FROM Rl WHERE Оценка > 2: Это, конечно, отличается от выбора поля, поскольку всегда возвращается одиночное значение, независимо от того, сколько строк находится в таблице. Аргументом агрегатных функций могут быть отдельные столбцы таблиц. Но для того, чтобы вычислить, например, количество различных значений некоторого столбца в группе, необходимо применить ключевое слово DISTINCT совместно с именем столбца. Вычислим количество различных оценок, полученных по каждой дисциплине: SELECT Rl.Дисциплина. COUNT(DISTINCT R1.Оценка) FROM R1 WHERE R1.Оценка IS NOT NULL GROUP BY Rl.Дисциплина Результат:
В результат можно включить значение поля группировки и несколько агрегатных функций, а в условиях группировки можно использовать несколько полей. При этом группы образуются по набору заданных полей группировки. Операции с агрегатными функциями могут быть применены к объединению множества исходных таблиц. Например, поставим вопрос: определить для каждой группы и каждой дисциплины количество успешно сдавших экзамен и средний балл по дисциплине. SELECT R2.Группа. R1.Дисциплина. COUNT(*), АVР(Оценка) FROM R1.R2 WHERE Rl.ФИО = R2.ФИО AND Rl.Оценка IS NOT NULL AND Rl.Оценка > 2 GROUP BY R2.Группа. Rl.Дисциплина Результат:
Мы не можем использовать агрегатные функции в предложении WHERE, потому что предикаты оцениваются в терминах одиночной строки, а агрегатные функции — в терминах групп строк. Предложение GROUP BY позволяет определять подмножество значений в особом поле в терминах другого поля и применять функцию агрегата к подмножеству. Это дает возможность объединять поля и агрегатные функции в едином предложении SELECT. Агрегатные функции могут применяться как в выражении вывода результатов строки SELECT, так и в выражении условия обработки сформированных групп HAVING. В этом случае каждая агрегатная функция вычисляется для каждой выделенной группы. Значения, полученные при вычислении агрегатных функций, могут быть использованы для вывода соответствующих результатов или для условия отбора групп. Построим запрос, который выводит группы, в которых по одной дисциплине на экзаменах получено больше одной двойки: SELECT R2.Группа FROM R1.R2 WHERE Rl.ФИО = R2.ФИО AND Rl.Оценка = 2 GROUP BY R2.Группа. R1.Дисциплина HAVING count(*)> 1 В дальнейшем в качестве примера будем работать не с БД «Сессия», а с БД «Банк», состоящей из одной таблицы F, в которой хранится отношение F, содержащее информацию о счетах в филиалах некоторого банка: F = <N, ФИО, Филиал, ДатаОткрытия, ДатаЗакрытия, Остаток>; Q = (Филиал, Город); поскольку на этой базе можно ярче проиллюстрировать работу с агрегатными функциями и группировкой. Например, предположим, что мы хотим найти суммарный остаток на счетах в филиалах. Можно сделать раздельный запрос для каждого из них, выбрав SUM(Остаток) из таблицы для каждого филиала. GROUP BY, однако, позволит поместить их все в одну команду: SELECT Филиал, SUM GROUP BY Филиал: GROUP BY применяет агрегатные функции независимо для каждой группы, определяемой с помощью значения поля Филиал. Группа состоит из строк с одинаковым значением поля Филиал, и функция SUM применяется отдельно для каждой такой группы, то есть суммарный остаток на счетах подсчитывается отдельно для каждого филиала. Значение поля, к которому применяется GROUP BY, имеет, по определению, только одно значение на группу вывода, как и результат работы агрегатной функции. Поэтому мы можем совместить в одном запросе агрегат и поле. Вы можете также использовать GROUP BY с несколькими полями. Предположим, что мы хотели бы увидеть только те суммарные значения остатков на счетах, которые превышают $5000. Чтобы увидеть суммарные остатки свыше $5000, необходимо использовать предложение HAVING. Предложение HAVING определяет критерии, используемые, чтобы удалять определенные группы из вывода, точно так же как предложение WHERE делает это для индивидуальных строк. Правильной командой будет следующая: SELECT Филиал, SUM(Остаток) FROM F GROUP BY Филиал HAVING SUM(Остаток) > 5000; Аргументы в предложении HAVING подчиняются тем же самым правилам, что и в предложении SELECT, где используется GROUP BY. Они должны иметь одно значение на группу вывода. Следующая команда будет запрещена: SELECT Филиал.SUM(Остаток) FROM F GROUP BY Филиал HAVING ДатаОткрытия = 27/12/1999; Поле ДатаОткрытия не может быть использовано в предложении HAVING, потому что оно может иметь больше чем одно значение на группу вывода. Чтобы избежать такой ситуации, предложение HAVING должно ссылаться только на агрегаты и поля, выбранные GROUP BY. Имеется правильный способ сделать вышеупомянутый запрос: SELECT Филиал,SUM(Остаток) FROM F WHERE ДатаОткрытия = '27/12/1999' GROUP BY Филиал; Смысл данного запроса следующий: найти сумму остатков по каждому филиалу счетов, открытых 27 декабря 1999 года. Как и говорилось ранее, HAVING может использовать только аргументы, которые имеют одно значение на группу вывода. Практически, ссылки на агрегатные функции — наиболее общие, но и поля, выбранные с помощью GROUP BY, также допустимы. Например, мы хотим увидеть суммарные остатки на счетах филиалов в Санкт-Петербурге, Пскове и Урюпинске: SELECT Филиал.SUМ(Остаток) FROM F.Q WHERE F.Филиал = Q.Филиал GROUP BY Филиал HAVING Филиал IN ("Санкт-Петербург". "Псков". "Урюпинск"); Поэтому в арифметических выражениях предикатов, входящих в условие выборки раздела HAVING, прямо можно использовать только спецификации столбцов, указанных в качестве столбцов группирования в разделе GROUP BY. Остальные столбцы можно специфицировать только внутри спецификаций агрегатных функций COUNT, SUM, AVG, MIN и MAX, вычисляющих в данном случае некоторое агрегатное значение для всей группы строк. Аналогично обстоит дело с подзапросами, входящими в предикаты условия выборки раздела HAVING: если в подзапросе используется характеристика текущей группы, то она может задаваться только путем ссылки на столбцы группирования. Результатом выполнения раздела HAVING является сгруппированная таблица, содержащая только те группы строк, для которых результат вычисления условия поиска есть TRUE. В частности, если раздел HAVING присутствует в табличном выражении, не содержащем GROUP BY, то результатом его выполнения будет либо пустая таблица, либо результат выполнения предыдущих разделов табличного выражения, рассматриваемый как одна группа без столбцов группирования. Вложенные запросы Теперь вернемся к БД «Сессия» и рассмотрим на ее примере использование вложенных запросов. С помощью SQL можно вкладывать запросы внутрь друг друга. Обычно внутренний запрос генерирует значение, которое проверяется в предикате внешнего запроса (в предложении WHERE или HAVING), определяющего, верно оно или нет. Совместно с подзапросом можно использовать предикат EXISTS, который возвращает истину, если вывод подзапроса не пуст. В сочетании с другими возможностями оператора выбора, такими как группировка, подзапрос представляет собой мощное средство для достижения нужного результата. В части FROM оператора SELECT допустимо применять синонимы к именам таблицы, если при формировании запроса нам требуется более чем один экземпляр некоторого отношения. Синонимы задаются с использованием ключевого слова AS, которое может быть вообще опущено. Поэтому часть FROM может выглядеть следующим образом: FROM Rl AS A, Rl AS В ИЛИ FROM Rl A. Rl В: оба выражения эквивалентны и рассматриваются как применения оператора SELECT к двум экземплярам таблицы R1. Например, покажем, как выглядят на SQL некоторые запросы к БД «Сессия»: · Список тех, кто сдал все положенные экзамены. SELECT ФИО FROM Rl as a WHERE Оценка > 2 GROUP BY ФИО HAVING COUNT(*) = (SELECT COUNT(*) FROM R2.R3 WHERE R2.Группа=R3.Группа AND ФИОа.ФИО) Здесь во встроенном запросе определяется общее число экзаменов, которые должен сдавать каждый студент, обучающийся в группе, в которой учится данный студент, и это число сравнивается с числом экзаменов, которые сдал данный студент. · Список тех, кто должен был сдавать экзамен по БД, но пока еще не сдавал. SЕLЕСТ ФИО FROM R2 a, R3 WHERE R2.Fpynna=R3.Группа AND Дисциплина = "БД" AND NOT EXISTS (SELECT ФИО FROM Rl WHERE ФИО=а.ФИО AND Дисциплина = "БД") Предикат EXISTS (SubQuery) истинен, когда подзапрос SubQuery не пуст, то есть содержит хотя бы один кортеж, в противном случае предикат EXISTS ложен. Предикат NOT EXISTS обратно — истинен только тогда, когда подзапрос SubQuery пуст. Обратите внимание, каким образом NOT EXISTS с вложенным запросом позволяет обойтись без операции разности отношений. Например, формулировка запроса со словом «все» может быть выполнена как бы с двойным отрицанием. Рассмотрим пример базы, которая моделирует поставку отдельных деталей отдельными поставщиками, она представлена одним отношением SP «Поставщики—детали» со схемой SP (Номер_поставщика. номер_детали) Р (номер_детали. наименование) Вот каким образом формулируется ответ на запрос: «Найти поставщиков, которые поставляют все детали». SELECT DISTINCT НОМЕР_ПОСТАВЩИКА FROM SP SP1 WHERE NOT EXISTS (SELECT номер_детали FROM P WHERE NOT EXISTS (SELECT * FROM SP SP2 WHERE SР2.номер_поставщика=SР1.номер_поставщика AND sр2.номер_детали = Р.номер_детали)): Фактически мы переформулировали этот запрос так: «Найти поставщиков таких, что не существует детали, которую бы они не поставляли». Следует отметить, что этот запрос может быть реализован и через агрегатные функции с подзапросом: SELECT DISTINCT Номер_поставщика FROM SP GROUP BY Номер_поставщика HAVING CounKDISTINCT номер_детали) = (SELECT Count(номер_детали) FROM P) В стандарте SQL92 операторы сравнения расширены до многократных сравнений с использованием ключевых слов ANY и ALL. Это расширение используется при сравнении значения определенного столбца со столбцом данных, возвращаемым вложенным запросом. Ключевое слово ANY, поставленное в любом предикате сравнения, означает, что предикат будет истинен, если хотя бы для одного значения из подзапроса предикат сравнения истинен. Ключевое слово ALL требует, чтобы предикат сравнения был бы истинен при сравнении со всеми строками подзапроса. Например, найдем студентов, которые сдали все экзамены на оценку не ниже чем «хорошо». Работаем с той же базой «Сессия», но добавим к ней еще одно отношение R4, которое характеризует сдачу лабораторных работ в течение семестра: R1 = (ФИО, Дисциплина, Оценка); R2 = (ФИО, Группа); R3 = (Группы, Дисциплина) R4 = (ФИО, Дисциплина, Номер_лаб_раб, Оценка); Select R1.ФИО From R1 Where 4 > = All (Select Rl.Оценка From Rl as R11 Where R1.Фио = R11.Фио) Рассмотрим еще один пример: Выбрать студентов, у которых оценка по экзамену не меньше, чем хотя бы одна оценка по сданным им лабораторным работам по данной дисциплины: Select R1.Фио From R1 Where R1.Оценка >= ANY (Select R4.Оценка From R4 Where Rl.Дисциплина = R4. Дисциплина AND R1.Фио = R4.Фио) Внешние объединения Стандарт SQL2 расширил понятие условного объединения. В стандарте SQL1 при объединении отношений использовались только условия, задаваемые в части WHERE оператора SELECT, и в этом случае в результирующее отношение попадали только сцепленные по заданным условиям кортежи исходных отношений, для которых эти условия были определены и истинны. Однако в действительности часто необходимо объединять таблицы таким образом, чтобы в результат попали все строки из первой таблицы, а вместо тех строк второй таблицы, для которых не выполнено условие соединения, в результат попадали бы неопределенные значения. Или наоборот, включаются все строки из правой (второй) таблицы, а отсутствующие части строк из первой таблицы дополняются неопределенными значениями. Такие объединения были названы внешними в противоположность объединениям, определенным стандартом SQL1, которые стали называться внутренними. В общем случае синтаксис части FROM в стандарте SQL2 выглядит следующим образом: FROM <список исходных таблиц> | < выражение естественного объединения > | < выражение объединения > < выражение перекрестного объединения > | < выражение запроса на объединение > <список исходных таблиц>::= <имя_таблицы_1> [ имя синонима таблицы_1] [...] [,<имя_таблицы_п>[ <имя синонима таблицы_n> ] ] <выражение естественного объединениям:: = <имя_таблицы_1> NATURAL { INNER | FULL [OUTER] | LEFT [OUTER] | RIGHT [OUTER]} JOIN <имя_таблицы_2> <выражение перекрестного объединениям: = <имя_таблицы_1> CROSS JOIN <имя_таблицы_2> <выражение запроса на объединением:= <имя_таблицы_1> UNION JOIN <имя_таблицы_2> <выражение объединениям:= <имя_таблицы_1> { INNER | FULL [OUTER] | LEFT [OUTER] | RIGHT [OUTER]} JOIN {ON условие [USING (список столбцов)]} <имя_таблицы_2> В этих определениях INNER — означает внутреннее объединение, LEFT — левое объединение, то есть в результат входят все строки таблицы 1, а части результирующих кортежей, для которых не было соответствующих значений в таблице 2, дополняются значениями NULL (неопределено). Ключевое слово RIGHT означает правое внешнее объединение, и в отличие от левого объединения в этом случае в результирующее отношение включаются все строки таблицы 2, а недостающие части из таблицы 1 дополняются неопределенными значениями, Ключевое слово FULL определяет полное внешнее объединение: и левое и правое. При полном внешнем объединении выполняются и правое и левое внешние объединения и в результирующее отношение включаются все строки из таблицы 1, дополненные неопределенными значениями, и все строки из таблицы 2, также дополненные неопределёнными значениями. Ключевое слово OUTER означает внешнее, но если заданы ключевые слова FULL, LEFT, RIGHT, то объединение всегда считается внешним. Рассмотрим примеры выполнения внешних объединений. Снова вернемся к БД «Сессия». Создадим отношение, в котором будут стоять все оценки, полученные всеми студентами по всем экзаменам, которые они должны были сдавать. Если студент не сдавал данного экзамена, то вместо оценки у него будет стоять неопределенное значение. Для этого выполним последовательно естественное внутреннее объединение таблиц R2 и R3 по атрибуту Группа, а полученное отношение соединим левым внешним естественным объединением с таблицей R1, используя столбцы ФИО и Дисциплина. При этом в стандарте разрешено использовать скобочную структуру, так как результат объединения может быть одним из аргументов в части FROM оператора SELECT. SELECT Rl.ФИО, R1.Дисциплина. Rl.Оценка FROM (R2 NATURAL INNER JOIN R3) LEFT JOIN Rl USING (ФИО. Дисциплина) Результат:
Рассмотрим еще один пример, для этого возьмем БД «Библиотека». Она состоит из трех отношений, имена атрибутов здесь набраны латинскими буквами, что является необходимым в большинстве коммерческих СУБД. BOOKS(ISBN, TITL. AUTOR. COAUTOR. YEARJZD, PAGES) READER(NUM_READER. NAME_READER, ADRESS. HOOM_PHONE. WORK_PHONE. BIRTH_DAY) EXEMPLARE (INV, ISBN, YES_NO. NUM_READER. DATE_IN. DATE_DUT) Здесь таблица BOOKS описывает все книги, присутствующие в библиотеке, она имеет следующие атрибуты: · ISBN — уникальный шифр книги; · TITL — название книги; · AUTOR — фамилия автора; · COAUTOR — фамилия соавтора; · YEARIZD — год издания; · PAGES — число страниц. Таблица READER хранит сведения обо всех читателях библиотеки, и она содержит следующие атрибуты: · NUM_READER — уникальный номер читательского билета; · NAME_READER — фамилию и инициалы читателя; · ADRESS — адрес читателя; · HOOM_PHONE — номер домашнего телефона; · WORK_PHONE — номер рабочего телефона; · BIRTH_DAY — дату рождения читателя. Таблица EXEMPLARE содержит сведения о текущем состоянии всех экземпляров всех книг. Она включает в себя следующие столбцы: · INV — уникальный инвентарный номер экземпляра книги; · ISBN — шифр книги, который определяет, какая это книга, и ссылается на сведения из первой таблицы; · YES_NO — признак наличия или отсутствия в библиотеке данного экземпляра в текущий момент; · NUM_READER — номер читательского билета, если книга выдана читателю, и Null в противном случае; · DATE_IN — если книга у читателя, то это дата, когда она выдана читателю; a DATE_OUT — дата, когда читатель должен вернуть книгу в библиотеку. Определим перечень книг у каждого читателя; если у читателя нет книг, то номер экземпляра книги равен NULL. Для выполнения этого поиска нам надо использовать левое внешнее объединение, то есть мы берем все строки из таблицы READER и соединяем со строками из таблицы EXEMPLARE, если во второй таблице нет строки с соответствующим номером читательского билета, то в строке результирующего отношения атрибут EXEMPLARE.INV будет иметь неопределенное значение NULL: SELECT READER.NAME_READER, EXEMPLARE.INV FROM READER RIGHT JOIN EXEMPLARE ON READER.NUM_READER=EXEMPLARE.NUM_READER Операция внешнего объединения, как мы уже упоминали, может использоваться для формирования источников в предложении FROM, поэтому допустимым будет, например, следующий текст запроса: SELECT * FROM (BOOKS LEFT JOIN EXEMPLARE) LEFT JOIN (READER NATURAL JOIN EXEMPLARE) USING (ISBN) При этом для книг, ни один экземпляр которых не находится на руках у читателей, значения номера читательского билета и дат взятия и возврата книги будут неопределенными. Перекрестное объединение в трактовке стандарта SQL2 соответствует операции расширенного декартова произведения, то есть операции соединения двух таблиц, при которой каждая строка первой таблицы соединяется с каждой строкой второй таблицы. Операция запроса па объединение эквивалентна операции теоретико-множественного объединения в алгебре. При этом требование эквивалентности схем исходных отношений сохраняется. Запрос на объединение выполняется по следующей схеме: SELECT - запрос UNION SELECT - запрос UNION SELECT - запрос Все запросы, участвующие в операции объединения, не должны, содержать выражений, то есть вычисляемых полей. Например, нужно вывести список читателей, которые держат на руках книгу «Идиот» или книгу «Преступление и наказание». Вот как будет выглядеть запрос: SELECT READER. NAME_READER FROM READER, EXEMPLARE.BOOKS WHERE EXEMPLARE.NUM_READER= READER.NUM_READER AND EXEMPLRE.ISBN = BOOKS.ISBN AND BOOKS.TITLE = "Идиот" UNION SELECT READER.NAME_READER FROM READER, EXEMPLARE,BOOKS WHERE EXEMPLARE.NUM_READER= READER.NUM_READER AND EXEMPLRE.ISBN = BOOKS.ISBN AND BOOKS.TITLE = "Преступление и наказание" По умолчанию при выполнении запроса на объединение дубликаты кортежей всегда исключаются. Поэтому, если найдутся читатели, у которых находятся на руках обе книги, то они все равно в результирующий список попадут только один раз. Запрос на объединение может объединять любое число исходных запросов. Так, к предыдущему запросу можно добавить еще читателей, которые держат на руках книгу «Замок»: UNION SELECT READER. NAME_READER FROM READER. EXEMPLARE,BOOKS WHERE EXEMPLARE.NUM_READER= READER.NUM_READER AND. EXEMPLRE.ISBN = BOOKS.ISBN AND BOOKS.TITLE = "Замок" В том случае, когда вам необходимо сохранить все строки из исходных отношений, необходимо использовать ключевое слово ALL в операции объединения. В случае сохранения дубликатов кортежей схема выполнения запроса на объединение будет выглядеть следующим образом: SELECT - запрос UNION ALL SELECT - запрос UNION ALL SELECT - запрос Однако тот же результат можно получить простым изменением фразы WHERE первой части исходного запроса, соединив локальные условия логической операцией ИЛИ и исключив дубликаты кортежей. SELECT DISTINCT READER.NAME_READER FROM READER. EXEMPLARE.BOOKS WHERE EXEMPLARE.NUM_READER= READER.NUM_READER AND EXEMPLRE.ISBN = BOOKS.ISBN AND BOOKS.TITLE = "Идиот" OR BOOKS.TITLE = "Преступление и наказание" OR BOOKS.TITLE = "Замок" Ни один из исходных запросов в операции UNION не должен содержать предложения упорядочения результата ORDER BY, однако результат объединения может быть упорядочен, для этого предложение ORDER BY с указанием списка столбцов упорядочения записывается после текста последнего исходного SELECT-запроса.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Последнее изменение этой страницы: 2016-07-16; просмотров: 270; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.145.152.168 (0.01 с.) |