Mysql вложенный select: SQL — Урок 5. Вложенные запросы
Вложенные SQL запросы
Вложенный запрос — это запрос на выборку, который используется внутри инструкции SELECT, INSERT, UPDATE или DELETE или внутри другого вложенного запроса. Подзапрос может быть использован везде, где разрешены выражения.
Пример структуры вложенного запроса
MySQL
SELECT поля_таблиц
FROM список_таблиц
WHERE конкретное_поле IN (
SELECT поле_таблицы FROM таблица
)
Здесь, SELECT поля_таблиц FROM список_таблиц WHERE конкретное_поле IN (…) — внешний запрос, а SELECT поле_таблицы FROM таблица — вложенный (внутренний) запрос.
Каждый вложенный запрос, в свою очередь, может содержать один или несколько вложенных запросов. Количество вложенных запросов в инструкции не ограничено.
Подзапрос может содержать все стандартные инструкции, разрешённые для использования в обычном SQL-запросе: DISTINCT, GROUP BY, LIMIT, ORDER BY, объединения таблиц, запросов и т.д.
Подзапрос может возвращать скаляр (одно значение), одну строку, один столбец или таблицу (одну или несколько строк из одного или нескольких столбцов). Они называются скалярными, столбцовыми, строковыми и табличными подзапросами.
Подзапрос как скалярный операнд
Скалярный подзапрос — запрос, возвращающий единственное скалярное значение (строку, число и т.д.).
Следующий простейший запрос демонстрирует вывод единственного значения (названия компании). В таком виде он не имеет большого смысла, однако ваши запросы могут быть намного сложнее.
MySQL
SELECT (SELECT name FROM company LIMIT 1);
Таким же образом можно использовать скалярные подзапросы для фильтрации строк с помощью WHERE, используя операторы сравнения.
MySQL
SELECT *
FROM FamilyMembers
WHERE birthday = (SELECT MAX(birthday) FROM FamilyMembers);
С помощью данного запроса возможно получить самого младшего члена семьи. Здесь используется подзапрос для получения максимальной даты рождения, которая затем используется для фильтрации строк.
Подзапросы с ANY, IN, ALL
ANY — ключевое слово, которое должно следовать за операцией сравнения (>, <, <>, = и т.д.), возвращающее TRUE, если хотя бы одно из значений столбца подзапроса удовлетворяет обозначенному условию.
MySQL
SELECT поля_таблицы_1
FROM таблица_1
WHERE поле_таблицы_1 <= ANY (SELECT поле_таблицы_2 FROM таблица_2);
ALL — ключевое слово, которое должно следовать за операцией сравнения, возвращающее TRUE, если все значения столбца подзапроса удовлетворяет обозначенному условию.
MySQL
SELECT поля_таблицы_1
FROM таблица_1
WHERE поле_таблицы_1 > ALL (SELECT поле_таблицы_2 FROM таблица_2);
IN — ключевое слово, являющееся псевдонимом ключевому слову ANY с оператором сравнения = (эквивалентность), либо <> ALL для NOT IN. Например, следующие запросы равнозначны:
MySQL
...
WHERE поле_таблицы_1 = ANY (SELECT поле_таблицы_2 FROM таблица_2);
MySQL
...
WHERE поле_таблицы_1 IN (SELECT поле_таблицы_2 FROM таблица_2);
Строковые подзапросы
Строковый подзапрос — это подзапрос, возвращающий единственную строку с более чем одной колонкой. Например, следующий запрос получает в подзапросе единственную строку, после чего по порядку попарно сравнивает полученные значения со значениями во внешнем запросе.
MySQL
SELECT поля_таблицы_1
FROM таблица_1
WHERE (первое_поле_таблицы_1, второе_поле_таблицы_1) =
(
SELECT первое_поле_таблицы_2, второе_поле_таблицы_2
FROM таблица_2
WHERE id = 10
);
Данную конструкцию удобно использовать для замены логических операторов. Так, следующие два запроса полностью эквивалентны:
MySQL
SELECT поля_таблицы_1 FROM таблица_1 WHERE (первое_поле_таблицы_1, второе_поле_таблицы_1) = (1, 1);
SELECT поля_таблицы_1 FROM таблица_1 WHERE первое_поле_таблицы_1 = 1 AND второе_поле_таблицы_1 = 1;
Связанные подзапросы
Связанным подзапросом является подзапрос, который содержит ссылку на таблицу, которая была объявлена во внешнем запросе. Здесь вложенный запрос ссылается на внешюю таблицу «таблица_1»:
MySQL
SELECT поля_таблицы_1 FROM таблица_1
WHERE поле_таблицы_1 IN
(
SELECT поле_таблицы_2 FROM таблица_2
WHERE таблица_2.поле_таблицы_2 = таблица_1.поле_таблицы_1
);
Подзапросы как производные таблицы
Производная таблица — выражение, которое генерирует временную таблицу в предложении FROM, которая работает так же, как и обычные таблицы, которые вы указываете через запятую. Так выглядит общий синтаксис запроса с использованием производных таблиц:
MySQL
SELECT поля_таблицы_1 FROM (подзапрос) [AS] псевдоним_производной_таблицы
Обратите внимание на то, что для производной таблицы обязательно должен указываться её псевдоним, для того, чтобы имелась возможность обратиться к ней в других частях запроса.
Обработка вложенных запросов
Вложенные подзапросы обрабатываются «снизу вверх». То есть сначала обрабатывается вложенный запрос самого нижнего уровня. Далее значения, полученные по результату его выполнения, передаются и используются при реализации подзапроса более высокого уровня и т.д.
Вложенные запросы SQL — CodeTown.ru
Здравствуйте, уважаемые читатели! В этой статье мы поговорим о том, что такое вложенные запросы в SQL. Традиционно, рассмотрим несколько примеров с той базой данных, которую создавали в первых статьях.
Введение
Итак, само название говорит о том, что запрос во что-то вложен. Так вот, вложенный запрос в SQL означает, что запрос select выполняется в еще одном запросе select — на самом деле вложенность может быть и многоуровневой, то есть select в select в select и т.д.
Такие запросы обычно используются для получения данных из двух и более таблиц. Они нужны чтобы данные из разных таблиц можно было соотнести и по зависимости осуществить выборку. У вложенных запросов есть и недостаток — зачастую слишком долгое время работы занимает запрос, потому что идет большая нагрузка на сервер. Тем не менее, саму конструкцию необходимо знать и использовать при возможности.
Структура ранее созданных таблиц
Прежде чем перейдем к простому примеру, напомним структуру наших таблиц, с которыми будем работать:
- Таблица Salespeople (продавцы):
snum | sname | city | comm |
---|---|---|---|
1 | Колованов | Москва | 10 |
2 | Петров | Тверь | 25 |
3 | Плотников | Москва | 22 |
4 | Кучеров | Санкт-Петербург | 28 |
5 | Малкин | Санкт-Петербург | 18 |
6 | Шипачев | Челябинск | 30 |
7 | Мозякин | Одинцово | 25 |
8 | Проворов | Москва | 25 |
- Таблица Customers (покупатели):
сnum | сname | city | rating | snum |
---|---|---|---|---|
1 | Деснов | Москва | 90 | 6 |
2 | Краснов | Москва | 95 | 7 |
3 | Кириллов | Тверь | 96 | 3 |
4 | Ермолаев | Обнинск | 98 | 3 |
5 | Колесников | Серпухов | 98 | 5 |
6 | Пушкин | Челябинск | 90 | 4 |
7 | Лермонтов | Одинцово | 85 | 1 |
8 | Белый | Москва | 89 | 3 |
9 | Чудинов | Москва | 96 | 2 |
10 | Лосев | Одинцово | 93 | 8 |
- Таблица Orders (заказы)
onum | amt | odate | cnum | snum |
---|---|---|---|---|
1001 | 128 | 2016-01-01 | 9 | 4 |
1002 | 1800 | 2016-04-10 | 10 | 7 |
1003 | 348 | 2017-04-08 | 2 | 1 |
1004 | 500 | 2016-06-07 | 3 | 3 |
1005 | 499 | 2017-12-04 | 5 | 4 |
1006 | 320 | 2016-03-03 | 5 | 4 |
1007 | 80 | 2017-09-02 | 7 | 1 |
1008 | 780 | 2016-03-07 | 1 | 3 |
1009 | 560 | 2017-10-07 | 3 | 7 |
1010 | 900 | 2016-01-08 | 6 | 8 |
Основы вложенных запросов в SQL
Вывести сумму заказов и дату, которые проводил продавец с фамилией Колованов.
Начнем с такого примера и для начала вспомним, как бы делали этот запрос ранее: посмотрели бы в таблицу Salespeople, определили бы snum продавца Колыванова — он равен 1. И выполнили бы запрос SQL с помощью условия WHERE. Вот пример такого SQL запроса:
SELECT amt, odate FROM orders WHERE snum = 1
Очевидно, какой будет вывод:
amt | odate |
---|---|
348 | 2017-04-08 |
80 | 2017-09-02 |
Такой запрос, очевидно, не очень универсален, если нам захочется выбрать тоже самое для другого продавца, то всегда придется определять его snum. А теперь посмотрим на вложенный запрос:
SELECT amt, odate FROM orders where snum = (SELECT snum FROM salespeople WHERE sname = 'Колованов')
В этом примере мы определяем с помощью вложенного запроса идентификатор snum по фамилии из таблицы salespeople, а затем, в таблице orders определяем по этому идентификатору нужные нам значения. Таким образом работают вложенные запросы SQL.
Рассмотрим еще один пример:
Показать уникальные номера и фамилии продавцов, которые провели сделки в 2016 году.
SELECT snum, sname FROM salespeople where snum IN (SELECT snum FROM orders WHERE YEAR(odate) = 2016)
Этот SQL запрос отличается тем, что вместо знака = здесь используется оператор IN. Его следует использовать в том случае, если вложенный подзапрос SQL возвращает несколько значений. То есть в запросе происходит проверка, содержится ли идентификатор snum из таблицы salespeople в массиве значений, который вернул вложенный запрос. Если содержится, то SQL выдаст фамилию этого продавца.
Получился такой результат:
snum | sname |
---|---|
3 | Плотников |
4 | Кучеров |
7 | Мозякин |
8 | Проворов |
Вложенные запросы SQL с несколькими параметрами
Те примеры, которые мы уже рассмотрели, сравнивали в условии WHERE одно поле. Это конечно хорошо, но стоит отметить, что в SQL предусмотрена возможность сравнения сразу нескольких полей, то есть можно использовать вложенный запрос с несколькими параметрами.
Вывести пары покупателей и продавцов, которые осуществили сделку между собой в 2017 году.
Запрос чем то похож на предыдущий, только теперь мы добавляем еще одно поле для сравнения. Итоговый запрос SQL будет выглядеть таким образом:
SELECT cname as 'Покупатель', sname as 'Продавец' FROM customers cus, salespeople sal where (cus.cnum, sal.snum) IN (SELECT cnum, snum FROM orders WHERE YEAR(odate) = 2017)
Вывод запроса:
Покупатель | Продавец |
---|---|
Краснов | Колованов |
Колесников | Кучеров |
Лермонтов | Колованов |
Кириллов | Мозякин |
В этом примере мы сравниваем сразу два поля одновременно по идентификаторам. То есть из таблицы orders берутся те строки, которые удовлетворяют условию по 2017 году, затем вместо идентификаторов подставляются значение имен покупателей и продавцов.
На самом деле, такой запрос SQL используется крайне редко, обычно используют оператор INNER JOIN, о котором будет сказано в следующей статье.
Дополнительно скажем о конструкциях, которые использовались в этом запросе. Оператор as нужен для того, чтобы при выводе SQL показывал не имена полей, а то, что мы зададим. И после оператора FROM за именами таблиц стоят сокращения, которые потом используются — это псевдонимы. Псевдонимы можно называть любыми именами, в этом запросе они используются для явного определения поля, так как мы несколько раз обращаемся к одному и тому же полю, только из разных таблиц.
Примеры на вложенные запросы SQL
1.Напишите запрос, который бы использовал подзапрос для получения всех Заказов для покупателя с фамилией Краснов. Предположим, что вы не знаете номера этого покупателя, указываемого в поле cnum.
SELECT * FROM orders where cnum = (SELECT cnum FROM customers WHERE cname = 'Краснов')
2. Напишите запрос, который вывел бы имена и рейтинг всех покупателей, которые имеют Заказы, сумма которых выше средней.
SELECT cname, rating FROM customers where cnum IN (SELECT cnum FROM orders WHERE amt > (SELECT AVG(amt) from orders))
3. Напишите запрос, который бы выбрал общую сумму всех приобретений в Заказах для каждого продавца, у которого эта общая сумма больше, чем сумма наибольшего Заказа в таблице.
SELECT snum, SUM(AMT) FROM orders GROUP BY snum HAVING SUM(amt) > (SELECT MAX(amt) FROM orders)
4. Напишите запрос, который бы использовал подзапрос для получения всех Заказов для покупателей проживающих в Москве.
SELECT * FROM orders where cnum IN (SELECT cnum FROM customers WHERE city = 'Москва')
5. Используя подзапрос определить дату заказа, имеющего максимальное значение суммы приобретений (вывести даты и суммы приобретений).
SELECT amt, odate FROM orders WHERE AMT = (SELECT MAX(AMT) FROM orders)
6. Определить покупателей, совершивших сделки с максимальной суммой приобретений.
SELECT cname FROM customers WHERE cnum IN (SELECT cnum FROM orders WHERE amt = (SELECT MAX(amt) FROM orders))
Заключение
На этом сегодня все, мы познакомились с вложенными запросам в SQL. Очевидно, что это достаточно удобный и понятный способ получения данных из таблиц, но не всегда рационален с точки зрения скорости и нагрузки на сервер. Основные примеры, которые мы разобрали, действительно встречаются на практике языка SQL.
Поделиться ссылкой:
Похожее
Как сделать вложенный запрос select в mysql — mysql
У меня есть ниже таблица, которая связана друг с другом, как
Info_Table -> RoomGuests_Table -> ChildAge_Table
Это таблицы
Info_Table
+---------------------------+
| ID | Name | Rooms |
+---------------------------+
| INFO1 | ABC | 2 |
| INFO2 | DEF | 1 |
| INFO3 | GHI | 3 |
+---------------------------+
RoomGuests_Table
+-----------------------------------+
| ID | R_ID | Adult | Child |
+-----------------------------------+
| RG1 | INFO1 | 2 | 2 |
| RG2 | INFO1 | 3 | 0 |
| RG3 | INFO2 | 2 | 1 |
| RG4 | INFO3 | 2 | 1 |
| RG5 | INFO3 | 2 | 2 |
| RG6 | INFO3 | 2 | 1 |
+-----------------------------------+
ChildAge_Table
+-----------------------+
| ID | R_ID | Age |
+-----------------------+
| CA1 | RG1 | 4 |
| CA2 | RG1 | 5 |
| CA3 | RG3 | 2 |
| CA4 | RG4 | 7 |
| CA5 | RG5 | 1 |
| CA6 | RG5 | 5 |
| CA7 | RG6 | 3 |
+-----------------------+
Я хочу такой результат
Если Info_Table ID = 'INFO1';
, то результат должен быть показан следующим образом.
Result
+-----------------------------------------------------------------------------------------------+
| ID | Name | Rooms | RoomGuests |
+-----------------------------------------------------------------------------------------------+
| INFO1 | ABC | 2 | [{"NoOfAdults" : "2", "NoOfChild" : "2", "ChildAge" : "[4,5]"}, |
| | | | {"NoOfAdults" : "3", "NoOfChild" : "", "ChildAge" : "[]"}] |
+-----------------------------------------------------------------------------------------------+
Для всех результат должен быть похожим на шоу
Result
+-----------------------------------------------------------------------------------------------+
| ID | Name | Rooms | RoomGuests |
+-----------------------------------------------------------------------------------------------+
| INFO1 | ABC | 2 | [{"NoOfAdults" : "2", "NoOfChild" : "2", "ChildAge" : "[4,5]"}, |
| | | | {"NoOfAdults" : "3", "NoOfChild" : "", "ChildAge" : "[]"}] |
| | | | |
| INFO2 | DEF | 1 | [{"NoOfAdults" : "2", "NoOfChild" : "1", "ChildAge" : "[2]"}] |
| | | | |
| INFO3 | GHI | 3 | [{"NoOfAdults" : "2", "NoOfChild" : "1", "ChildAge" : "[7]"}, |
| | | | {"NoOfAdults" : "2", "NoOfChild" : "2", "ChildAge" : "[1,5]"}, |
| | | | {"NoOfAdults" : "2", "NoOfChild" : "1", "ChildAge" : "[3]"}] |
+-----------------------------------------------------------------------------------------------+
Я пробовал ниже код, но не работает. я не смогу понять, как это сделать
SELECT
S.`ID`, A.`Name`, A.`Rooms`,
CONCAT(
'[',
GROUP_CONCAT(
CONCAT(
'{
\"NoOfAdults\":\"', R.Adults,'\",
\"NoOfChild\":\"', R.Child,'\",
\"ChildAge\":
\"',
CONCAT(
'[',
GROUP_CONCAT(
CONCAT('{',C.Age,'}')
),
']'
),
,'\",
}'
)
),
']'
) AS RoomGuests,
FROM `Info_Table` AS I
LEFT JOIN `RoomGuests_Table` AS R ON R.`R_ID` = A.`ID`
LEFT JOIN `ChildAge_Table` AS C ON C.`R_ID` = R.`R_ID`
GROUP BY A.R_ID;
Или есть какой-нибудь лучший способ сделать массив, как это, пожалуйста, дайте мне знать
Array
(
[ID] => INFO1
[Name] => ABC
[Rooms] => 2
[RoomGuests] => Array
(
[0] => Array
(
[NoOfAdults] => 2
[NoOfChild] => 2
[ChildAge] => Array
(
[0] => 4
[1] => 5
)
)
[1] => Array
(
[NoOfAdults] => 3
[NoOfChild] => 0
[ChildAge] => Array
(
)
)
)
)
mysql
sql
Поделиться
Источник
User97798
30 августа 2016 в 11:41
3 Ответа
1
Попробуйте использовать это
SELECT i.ID, i.name, i.rooms, RG.RoomGuests
FROM Info_Table i
LEFT JOIN (
SELECT
R.ID, R.R_ID AS RG_ID,
CONCAT(
'[',
GROUP_CONCAT(
CONCAT(
'{
\"NoOfAdults\":\"', Adult,'\",
\"NoOfChild\":\"', Child,'\",
\"ChildAge\":', IFNULL(CA.ChildAge, '[]'),'
}'
)
),
']'
) AS RoomGuests
FROM RoomGuests_Table R
LEFT JOIN (
SELECT
C.R_ID AS CA_ID,
CONCAT(
'[',
GROUP_CONCAT( Age SEPARATOR ','),
']'
) AS ChildAge
FROM ChildAge_Table C
GROUP BY CA_ID
) CA ON CA.CA_ID = R.ID
GROUP BY RG_ID
) RG ON RG.RG_ID = i.ID
WHERE i.ID = INFO1;
Поделиться
Ajay Kumar
05 сентября 2016 в 19:02
0
Надеюсь, это поможет.
select i.name,i.rooms , group_concat(jsonres separator ',') from Info_Table i right join ( select r.r_id, CONCAT('[{"NoOfAdults":"', Adult,'","NoOfChild":"', Child,'","ChildAge":[',group_concat(C.Age separator ','),']"}') jsonres from test.RoomGuests_Table r left join test.ChildAge_Table c on r.ID=c.R_ID group by c.R_ID) as r on i.id= r.r_id group by i.name,i.rooms;
Поделиться
Faizan Younus
30 августа 2016 в 12:37
0
Несмотря на то, что я считаю, что SGBD не должен принимать участие в оформлении данных, вам нужно сделать это в два этапа.
Сначала вам нужно сделать group_concat для агрегирования данных child_table.
Тогда вы сможете сделать основной group_concat с RoomGuests_Table и с результатом первого запроса.
Если вы попытаетесь выполнить только 1 запрос только с одним group_concat, то у вас будут дубликаты данных.
Пример :
SELECT ...
FROM info_table it
left outer join RoomGuests_Table rt on it.ID = rt.R_ID
left outer join
( SELECT ct.R_ID AS RG_ID, GROUP_CONCAT(ct.Age SEPARATOR ',') as concat_age
FROM ChildAge_Table as ct
GROUP BY ct.R_ID) tmp on tmp.RG_ID = rt.ID
GROUP BY ....
Поделиться
Nemeros
30 августа 2016 в 11:54
Похожие вопросы:
Как изменить этот вложенный запрос php/mysql на 1 более эффективный запрос?
Я ищу некоторую помощь, чтобы сделать мой код php/MySQL более эффективным. На данный момент у меня есть вложенный оператор SQL в моем коде PHP, который работает целую вечность. Я знаю, что есть…
Вложенный Запрос mysqli
Я пытаюсь выполнить вложенный запрос, который не работает с помощью метода mysqli::query. Запрос отлично работает в командной строке mysql. Вот соответствующий фрагмент кода: $select_records =…
3-й вложенный запрос Mysql Select вызывает огромные проблемы с производительностью
Я делаю некоторые запросы SQL в базе данных Magento, чтобы получить некоторую информацию о заказе клиента. Запрос, который я хочу выполнить, выглядит следующим образом SELECT * FROM…
Как создать вложенный запрос SQL в MySQL?
При написании запроса ниже он жалуется, что столбец so.id неизвестен! Приведенный ниже запрос отлично работает в MS SQL, но не в MySQL? Как написать вложенный оператор select в MySQL? Каков его…
Mysql SELECT вложенный запрос, очень сложный?
Хорошо, сначала следуют мои таблицы: Стол дома: идентификатор | items_id | 1 / 1,5,10,20 | Элементы таблицы: идентификатор | room_name | см 1 / Кухня / 3 5 / room1 / 10 Стол кухонный: идентификатор…
вложенный select — SQL-запрос
В моем MySQL DB у меня есть такая таблица | — value — | — date — | |————————| | 1 | 01.01.2016 | | 2 | 02.01.2016 | | 3 | 04.02.2016 | | 5 | 01.01.2017 | | 1 | 02.01.2017 | | 5 |…
Вложенный запрос MySQL?
У меня есть это MySQL заявление, которое отлично работает: SELECT claim_items.item_id, claim_items.quantity*items_rate.rate as per_item FROM claim_items, items_rate WHERE claim_items.claim_id = 1…
Вложенный запрос SELECT в кадрах данных Pyspark
Предположим, у меня есть два DataFrames в Pyspark, и я хотел бы запустить вложенный запрос SQL-like SELECT, на строках SELECT * FROM table1 WHERE b IN (SELECT b FROM table2 WHERE c=’1′) Теперь я…
Как обработать этот вложенный запрос select с помощью арифметики?
Поэтому я пытаюсь сделать вложенный запрос select с арифметикой в нем, чтобы ответить на следующее: покажите страны в Европе с душевым GDP больше, чем ‘United Kingdom’. Я пробую что-то вроде ниже с…
MySQL вложенный Select с присоединенной таблицей
Я предполагаю, что есть способ сделать это с MySQL, но мой опыт работы с реляционными базами данных ограничен, поэтому я надеюсь получить некоторые рекомендации. У меня есть таблица users ,…
Как оптимизировать вложенные запрос mysql? — Хабр Q&A
Всем привет.
Я пытаюсь разгрузить один запрос или думаю вообще переделать архитектуру хранения записей. Надеюсь вы мне поможете определиться 😉
На проекте есть 3 таблицы: Объект, к которым принадлежат Детали и Материалы. Мне нужно получить: Список объектов, у которых есть кол-во (items_count) принадлежащих к нему Детали+Материалы, а так же получить последнюю дату обновлению записи, т.е. пройтись по этим таблицам (по полю last_update) и вытащить последнюю таймстамп дату GREATEST() .
Поле status = 1 , item доступен или нет.
SELECT
`Ob`.`id` AS `id`,
`Ob`.`title` AS `title`,
`Ob`.`poster` AS `poster` ,
@count_details := (SELECT count(*) FROM game.Details D WHERE D.objectId = Ob.id AND D.status = 1 ),
@count_materials := (SELECT count(*) FROM game.Materials Ma WHERE Ma.objectId = Ob.id AND Ma.status = 1 ),
@count_details + @count_materials as items_count,
@max_update_details := (SELECT MAX(D.last_update) FROM game.Details D WHERE D.objectId = Ob.id AND D.status = 1 ),
@max_update_materials := (SELECT MAX(Ma.last_update) FROM game.Materials Ma WHERE Ma.objectId = Ob.id AND Ma.status = 1 ),
GREATEST(
`Ob`.`last_update`, @max_update_details, ifnull(@max_update_materials, 0 )
) as last_update
FROM game.`Object` Ob
WHERE status=1
ORDER BY last_update DESC;
В принципе все это работает, но проблема в том, что это чудо долго будет обрабатываться с нарастанием записей. 🙁
Реально ли как-то оптимизировать этот запрос?
Я думаю, что при сохранении формы добавления/изменения Details или Materials пересчитывать их кол-во к принадлежащему объекту. И заносить в Object таблицу, в какую нибудь новую колонку, а не делать каждый раз столько вложенных запросов. Тоже самое и касается время изменения last_update.
Еще наткнулся на один вопрос, где во вложенном запросе добавлена переменная @i в качестве инкремента. Можно ли сделать, чтобы на момент вычисления максимального значения, можно было подсчитать кол-во найденных записей, (псевдокод):
SELECT MAX(D.last_update), @i := @i +1
FROM game.Details D
WHERE D.objectId = Ob.id;
Забыл кстати скинуть результаты Explain:
id |select_type |table |type |possible_keys |key |key_len |ref |rows |Extra |
---|-------------------|------|-----|------------------------------------------|------------------------------|--------|------|-------|---------------------------------------------|
1 |PRIMARY |Ob |ALL | | | | |139 |Using where; Using temporary; Using filesort |
5 |DEPENDENT SUBQUERY |D |ref |IX_Details_status_last_update |IX_Details_status_last_update |2 |const |25909 |Using where |
4 |DEPENDENT SUBQUERY |Ma |ref |IX_Materials_status,IX_Materials_objectId |IX_Materials_status |2 |const |125431 |Using where |
3 |DEPENDENT SUBQUERY |D |ref |IX_Details_status_last_update |IX_Details_status_last_update |2 |const |25909 |Using where |
2 |DEPENDENT SUBQUERY |Ma |ref |IX_Materials_status,IX_Materials_objectId |IX_Materials_status |2 |const |125431 |Using where |
Вот схема Таблиц:
Спасибо за внимание!
SELECT оператор MySQL — Oracle PL/SQL •MySQL •MariaDB •SQL Server •SQLite
В этом учебном материале вы узнаете, как использовать MySQL оператор SELECT с синтаксисом и примерами.
Описание
MySQL оператор SELECT используется для извлечения записей из одной или нескольких таблиц в MySQL.
Синтаксис
Простой синтаксис для оператора SELECT в MySQL:
SELECT expressions
FROM tables
[WHERE conditions];
Полный синтаксис для оператора SELECT в MySQL:
SELECT [ ALL | DISTINCT | DISTINCTROW ]
[ HIGH_PRIORITY ]
[ STRAIGHT_JOIN ]
[ SQL_SMALL_RESULT | SQL_BIG_RESULT ] [ SQL_BUFFER_RESULT ]
[ SQL_CACHE | SQL_NO_CACHE ]
[ SQL_CALC_FOUND_ROWS ]
expressions
FROM tables
[WHERE conditions]
[GROUP BY expressions]
[HAVING condition]
[ORDER BY expression [ ASC | DESC ]]
[LIMIT [offset_value] number_rows | LIMIT number_rows OFFSET offset_value]
[PROCEDURE procedure_name]
[INTO [ OUTFILE ‘file_name’ options
| DUMPFILE ‘file_name’
| @variable1, @variable2, … @variable_n]
[FOR UPDATE | LOCK IN SHARE MODE];
Параметры или аргументы
ALL — необязательный. Возвращает все совпадающие строки
DISTINCT — необязательный. Удаляет дубликаты из набора результатов. Подробнее о DISTINCT.
DISTINCTROW — необязательный. Синоним DISTINCT. Удаляет дубликаты из набора результатов.
HIGH_PRIORITY — необязательный. Он сообщает MySQL, что он запускает SELECT перед любыми операторами UPDATE, ожидающими того же ресурса. Он может использоваться с таблицами MyISAM, MEMORY и MERGE, которые используют блокировку на уровне таблицы.
STRAIGHT_JOIN — необязательный. Он сообщает MySQL о соединении таблиц в том порядке, в котором они перечислены в предложении FROM.
SQL_SMALL_RESULT — необязательный. Использует быстрые временные таблицы для хранения результатов (используется с DISTINCT и GROUP BY).
SQL_BIG_RESULT — необязательный. Предпочитает сортировку, а не временную таблицу для хранения результатов (используется с DISTINCT и GROUP BY).
SQL_BUFFER_RESULT — необязательный. Использует временные таблицы для хранения результатов (не может использоваться с подзапросами).
SQL_CACHE — необязательный. Сохраняет результаты в кеше запросов.
SQL_NO_CACHE — необязательный. Не сохраняет результаты в кеше запросов.
SQL_CALC_FOUND_ROWS — необязательный. Вычисляет, сколько записей находится в результирующем наборе (не принимая во внимание атрибут LIMIT), который затем можно получить с помощью функции FOUND_ROWS.
expressions — столбцы или вычисления, которые вы хотите получить. Используйте *, если вы хотите выбрать все столбцы.
tables — таблицы, из которых вы хотите получить записи. Должна быть хотя бы одна таблица, перечисленная в предложении FROM.
WHERE conditions — необязательный. Условия, которые должны быть выполнены для выбранных записей.
GROUP BY expressions — необязательный. Он собирает данные по нескольким записям и группирует результаты по одному или нескольким столбцам. Подробнее о GROUP BY.
HAVING condition — необязательный. Он используется в сочетании с GROUP BY, чтобы ограничить группы возвращаемых строк только теми, чье условие TRUE. Подробнее о HAVING.
ORDER BY expression — необязательный. Он используется для сортировки записей в вашем результирующем наборе. Подробнее о ORDER BY.
LIMIT — необязательный. Если LIMIT указан, то он контролирует максимальное количество извлекаемых записей. Максимальное количество записей, заданных number_rows, будет возвращено в результирующем наборе. Первая строка, возвращаемая LIMIT, будет определяться значением offset_value.
PROCEDURE — необязательный. Если указано, то — это имя процедуры, которая должна обрабатывать данные в результирующем наборе.
INTO — необязательный. Если указан, это позволяет вам записать результирующий набор в файл или переменную.
Значение | Пояснение |
---|---|
INTO OUTFILE ‘filename’ options | «Записывает результирующий набор в файл с именем filename на хосте сервера. Для параметров вы можете указать: FIELDS ESCAPED BY ‘character’ FIELDS TERMINATED BY ‘character’ [ OPTIONALLY ENCLOSED BY ‘character’ ] LINES TERMINATED BY ‘character’ где character — символ, отображаемый как символ ESCAPE, ENCLOSED или TERMINATED. Например: SELECT supplier_id, supplier_name FROM suppliers INTO OUTFILE ‘results.txt’ FIELDS TERMINATED BY ‘,’ OPTIONALLY ENCLOSED BY ‘»»‘ LINES TERMINATED BY ‘\n’;» |
INTO DUMPFILE ‘filename’ | Записывает одну строку набора результатов в файл с именем filename на хосте сервера. С помощью этого метода не происходит прерывания столбца, не прерывается линия или обработка перехода. |
INTO @variable1, @variable2, … @variable_n | Записывает набор результатов в одну или несколько переменных, как указано в параметрах @ variable1, @ variable2, … @variable_n |
FOR UPDATE — необязательный. Записи, затронутые запросом, блокируются, пока транзакция не завершится.
LOCK IN SHARE MODE — необязательный. Записи, затронутые запросом, могут использоваться другими транзакциями, но не могут быть обновлены или удалены этими и другими транзакциями.
Пример — ВЫБОРКА ВСЕХ ПОЛЕЙ ИЗ ОДНОЙ ТАБЛИЦЫ
Давайте посмотрим, как использовать MySQL оператор SELECT для выбора всех полей из таблицы.
SELECT *
FROM order_details
WHERE quantity >= 100
ORDER BY quantity DESC;
SELECT * FROM order_details WHERE quantity >= 100 ORDER BY quantity DESC; |
В этом MySQL примере SELECT мы использовали * для обозначения того, что мы хотим выбрать все поля из таблицы order_details, где количество больше или равно 100. Результирующий набор сортируется по quantity в порядке убывания.
Пример — ВЫБОРКА ОТДЕЛЬНЫХ ПОЛЕЙ ИЗ ОДНОЙ ТАБЛИЦЫ
Вы также можете использовать MySQL оператор SELECT для выбора отдельных полей из таблицы.
Например:
SELECT order_id, quantity, unit_price
FROM order_details
WHERE quantity < 300
ORDER BY quantity ASC, unit_price DESC;
SELECT order_id, quantity, unit_price FROM order_details WHERE quantity < 300 ORDER BY quantity ASC, unit_price DESC; |
В этом MySQL примере SELECT возвращает только поля order_id, quantity и unit_price из таблицы order_details, где количество меньше 300. Результаты сортируются по quantity в порядке возрастания, а затем unit_price в порядке убывания.
Пример — ВЫБОРКА ПОЛЕЙ ИЗ НЕСКОЛЬКИХ ТАБЛИЦ
Вы также можете использовать MySQL оператор SELECT для извлечения полей из нескольких таблиц.
SELECT order_details.order_id, customers.customer_name
FROM customers
INNER JOIN order_details
ON customers.customer_id = order_details.customer_id
ORDER BY order_details.order_id;
SELECT order_details.order_id, customers.customer_name FROM customers INNER JOIN order_details ON customers.customer_id = order_details.customer_id ORDER BY order_details.order_id; |
В этом MySQL примере SELECT соединяет вместе две таблицы, чтобы выдать набор результатов, который отображает поля order_id и customer_name, где значение customer_id совпадает как в таблице customers, так и в таблице order_details. Результаты сортируются по полю order_details.order_id в порядке возрастания.
Пример — запись в файл
Вы также можете использовать MySQL оператор SELECT для записи набора результатов в файл.
Например:
SELECT order_id, quantity, unit_price
FROM order_details
WHERE quantity < 300
ORDER BY quantity
INTO OUTFILE ‘result.txt’
FIELDS TERMINATED BY ‘,’ OPTIONALLY ENCLOSED BY ‘»‘
LINES TERMINATED BY ‘\n’;
SELECT order_id, quantity, unit_price FROM order_details WHERE quantity < 300 ORDER BY quantity INTO OUTFILE ‘result.txt’ FIELDS TERMINATED BY ‘,’ OPTIONALLY ENCLOSED BY ‘»‘ LINES TERMINATED BY ‘\n’; |
В этом MySQL примере SELECT возвращает только поля order_id, quantity и unit_price из таблицы order_details, где quantity меньше 300. Результаты сортируются по quantity в порядке возрастания и записываются в файл с именем result.txt.
MySQL и JOINы / Хабр
Поводом для написания данной статьи послужили некоторые дебаты в одной из групп linkedin, связанной с MySQL, а также общение с коллегами и хабролюдьми 🙂
В данной статье хотел написать что такое вообще JOINы в MySQL и как можно оптимизировать запросы с ними.
Что такое JOINы в MySQL
В MySQL термин JOIN используется гораздо шире, чем можно было бы предположить. Здесь JOINом может называться не только запрос объединяющий результаты из нескольких таблиц, но и запрос к одной таблице, например, SELECT по одной таблице — это тоже джоин.
Все потому, что алгоритм выполнения джоинов в MySQL реализован с использованием вложенных циклов. Т.е. каждый последующий JOIN это дополнительный вложенный цикл. Чтобы выполнить запрос и вернуть все записи удовлетворяющие условию MySQL выполняет цикл и пробегает по записям первой таблицы параллельно проверяя соответствия условиям описанных в теле запроса, когда находятся записи, удовлетворяющие условиям — во вложенном цикле по второй таблице ищутся записи соответствующие первым и удовлетворяющие условиям проверки и т.д.
Прмер обычного запроса с INNER JOIN
SELECT
*
FROM
Table1
INNER JOIN
Table2 ON P1(Table1,Table2)
INNER JOIN
Table3 ON P2(Table2,Table3)
WHERE
P(Table1,Table2,Table3).* This source code was highlighted with Source Code Highlighter.
где Р — условия склейки таблиц и фильтры в WHERE условии.
Можно представить такой псевдокод выполнения такого запроса.
FOR each row t1 in Table1 {
IF(P(t1)) {
FOR each row t2 in Table2 {
IF(P(t2)) {
FOR each row t3 in Table3 {
IF P(t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
}
}* This source code was highlighted with Source Code Highlighter.
где конструкция t1||t2||t3 означает конкатенацию столбцов из разных таблиц.
Если в запросе встречаются OUTER JOINs, например, LEFT OUTER JOIN
SELECT
*
FROM
Table1
LEFT JOIN
(
Table2 LEFT JOIN Table3 ON P2(Table2,Table3)
)
ON P1(Table1,Table2)
WHERE
P(Table1,Table2,Tabke3)* This source code was highlighted with Source Code Highlighter.
то алгоритм выполнения этого запроса MySQL будет выглядеть как-то так
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t2 in T2 such that P1(t1,t2) {
BOOL f2:=FALSE;
FOR each row t3 in T3 such that P2(t2,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f2=TRUE;
f1=TRUE;
}
IF (!f2) {
IF P(t1,t2,NULL) {
t:=t1||t2||NULL; OUTPUT t;
}
f1=TRUE;
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}* This source code was highlighted with Source Code Highlighter.
Более подробно почитать об этом можно здесь — dev.mysql.com/doc/refman/5.1/en/nested-joins.html
Итак, как мы видим, JOINы это просто группа вложенных циклов. Так почему же в MySQL и UNION и SELECT и запросы с SUBQUERY тоже джоины?
MySQL оптимизатор старается приводить запросы к тому виду к которому ему удобней обрабатывать и выполнять запросы по стандартной схеме.
С SELECT все понятно — просто цикл без вложенных циклов. Все UNION выполняются как отдельные запросы и результаты складываются во временную таблицу, и потом MySQL работает уже с этой таблицей, т.е. проходясь циклом по записям в ней. С Subquery та же история.
Приводя все к одному шаблону, например, МySQL переписывает все RIGHT JOIN запросы на LEFT JOIN эквиваленты.
Но стратегия выполнения запросов через вложенные циклы накладывает некоторые ограничения, например, в связи с такой схемой MySQL не поддерживает выполнение FULL OUTER JOIN запросов.
Но результат такого запроса можно получить с помощью UNION двух запросов на LEFT JOIN и на RIGHT JOIN
Пример самого запроса можно посмотреть по ссылке на вики.
План выполнения JOIN запросов
В отличии от других СУРБД MySQL не генерирует байткод для выполнения запроса, вместо этого MySQL генерирует список инструкций в древовидной форме, которых придерживается engine выполнения запроса выполняя запрос.
Это дерево имеет следующий вид и имеет название «left-deep tree»
В отличии от сбалансированных деревьев (Bushy plan), которые применяются в других СУБД (например Oracle)
JOIN оптимизация
Теперь перейдем к самому интересному — к оптимизации джоинов.
MySQL оптимизатор, а именно та его часть, которая отвечает за оптимизацию JOIN-ов выбирает порядок в котором будет производиться склейка имеющихся таблиц, т.к. можно получить один и тот же результат (датасет) при различном порядке таблиц в склейке. MySQL оптимизатор оценивает стоимость различных планов и выбирает с наименьшей стоимостью. Единицей оценки является операция единичного чтения страницы данных размером в 4 килобайта из произвольного места на диске.
Для выбранного плана можно узнать стоимость путем выполнения команды
SHOW SESSION STATUS LIKE ‘Last_query_cost’;
после выполнения интересующего нас запроса. Переменная Last_query_cost является сессионной переменной. Описание переменной Last_query_cost в MySQL документации можно найти здесь — dev.mysql.com/doc/refman/5.1/en/server-status-variables.html#option_mysqld_Last_query_cost
Оценка основана на статистике: количество страниц памяти, занимаемое таблицей и/или индексами для этой таблицы, cardinality (число уникальных значений) индексов, длинна записей и индексов, их распределение и т.д. Во время своей оценки оптимизатор не рассчитывает на то, что какие-то части попадут в кеш, оптимизатор предполагает, что каждая операция чтения это обращение к диску.
Иногда анализатор-оптимизатор не может проанализировать все возможные планы выполнения и выбирает неправильный. Например, если у нас INNER JOIN по 3м таблицам, то возможных вариантов у анализатора — 3! = 6, а если у нас склейка по 10 таблицам, то тут возможных вариантов уже 10! = 3628800… MySQL не может проанализировать столько вариантов, поэтому в таком случае он использует алгоритм «жадного» поиска.
И вот как раз для решения данной проблемы, нам может пригодиться конструкция STRAIGHT_JOIN. На самом деле я противник подобных хаков как FORCE INDEX и STRAIGH_JOIN, точней против их бездумного использования везде где только можно и нельзя. В данном случае — можно 🙂 Выяснив (либо экспериментальным путем делая запросы с STRAIGH_JOIN и оценивая Last_query_cost, либо эмпирическим путем) нужный порядок джоинов можно переписать запрос с таблицами в соответствующем порядке и добавить STRAIGH_JOIN к данному запросу, таким образом мы сразу убьем двух зайцев — определим правильный план выполнения запроса (это главный заяц) и сэкономим время на стадии «Statistic» (Все стадии выполнения запроса можно посмотреть установив профайлинг запросов командой SET PROFILING =1, я описывал это в своей предыдущей статье по теме профайлинга запросов в MySQL )
Но не стоит применять этот хак ко всем запросам, расчитывая произвести оптимизацию на спичках и сэкономить время на составление плана выполнения запроса оптимизатором и добавлять STRAIGH_JOIN ко всем запросам с джоинами, т.к. данные меняются и склейка, которая оптимальна сейчас может перестать быть оптимальной со временем, и тогда запросы начнуть очень сильно лагать.
Также, как уже говорилось выше, результаты джоинов помещаются во временные таблицы, поэтому зачастую уместно применять «derived table» в котором мы накладываем все необходимые нам условия на выборку, а также указываем LIMIT и порядок сортировки. В данном случае мы избавимся от избыточности данных во временной таблице, а также проведем сортировку на раннем этапе (по результату одной выборки, а не финальной склейки, что уменьшит размеры записей которые будут сортироваться).
Стандартный пример подхода описанного выше. Простая выборка для отношения много к многим: новости и теги к ним.
SELECT
t.tid, t.description, n.nid, n.title, n.extract, n.modtime
FROM
(
SELECT
n.nid
FROM
news n
WHERE
n.type = 1321
AND n.published = 1
AND status = 1
ORDER BY
n.modtime DESC
LIMIT
200
) as news
INNER JOIN
news n ON n.nid = news.nid
INNER JOIN
news_tag nt ON n.nid = nt.nid
INNER JOIN
tags t ON nt.tid = t.tid* This source code was highlighted with Source Code Highlighter.
Ну и на последок небольшая задачка, которую я иногда задаю на собеседованиях 🙂
Есть новостной блоггерный сайт. Есть такие сущности как новости и комментарии к ним.
Задача — нужно написать запрос, который выводит список из 10 новостей определенного типа (задается пользователем) отсортированные по времени издания в хронологическом порядке, а также к каждой из этих новостей показать не более 10 последних коментариев, т.е. если коментариев больше — показываем только последние 10.
Все нужно сделать одним запросом. Да, это, может, и не самый лучший способ, и вы вольны предложить другое решение 🙂
Выбор по условию в MySQL (SELECT WHERE)
В одной из предыдущих статей мы узнали, как извлечь все записи из таблицы или столбцов таблицы. Но в реальном сценарии нам обычно нужно выбирать, обновлять или удалять только те записи, которые соответствуют определенным условиям, например, пользователи, принадлежащие к определенной возрастной группе или стране и т.д.
Ключевое слово WHERE используется с SELECT, UPDATE и DELETE. Тем не менее, вы увидите использование этого предложения с другими утверждениями в следующих статьях.
Синтаксис
Ключевое слово WHERE используется с оператором SELECT для извлечения только тех записей, которые удовлетворяют указанным условиям. Основной синтаксис может быть задан с помощью:
SELECT column_list FROM table_name WHERE condition;
Здесь column_list — это имена столбцов/полей, таких как имя, возраст, страна и т.д. таблицы базы данных, значения которой вы хотите получить. Однако, если вы хотите получить значения всех столбцов, доступных в таблице, вы можете использовать следующий синтаксис:
SELECT * FROM table_name WHERE condition;
Теперь давайте рассмотрим несколько примеров, которые демонстрируют, как это на самом деле работает.
Предположим, у нас есть таблица с именем сотрудников в нашей базе данных со следующими записями:
+--------+--------------+------------+--------+---------+ | emp_id | emp_name | hire_date | salary | dept_id | +--------+--------------+------------+--------+---------+ | 1 | Vasya Pupkin | 2001-05-01 | 5000 | 4 | | 2 | Vanya Pupkin | 2002-07-15 | 6500 | 1 | | 3 | Petya Pupkin | 2005-10-18 | 8000 | 5 | +--------+--------------+------------+--------+---------+
Фильтрация записей с помощью условия WHERE
Следующая команда SQL возвращает всех сотрудников из таблицы сотрудников, чья зарплата превышает 6000. Ключевое слово WHERE просто отфильтровывает нежелательные данные.
SELECT * FROM employees WHERE salary > 6000;
После выполнения этой команды, вы получите записи только тех сотрудников, заработная плата которых больше 6000.
Так же, мы можем изменять количество выводимых столбцов, указывая их в запросе. Давайте выведем сотрудников в таблицу содержащую только их айди, имена и заработные платы.
SELECT emp_id, emp_name, salary FROM employees WHERE salary > 6000;
В результате вы получите таблицу состоящую из указаных выше столбцов:
+--------+--------------+--------+ | emp_id | emp_name | salary | +--------+--------------+--------+ | 1 | Vasya Pupkin | 5000 | | 2 | Vanya Pupkin | 6500 | | 3 | Petya Pupkin | 8000 | +--------+--------------+--------+
В обоих примерах мы использовали оператор «больше чем». Однако, вы можете менять операторы в зависимости от результатов, которые хотите получить. Давайте рассмотрим подробнее, какие операторы можно использовать с ключевым словом WHERE.
Операторы, используемые с WHERE
MySQL поддерживает ряд различных операторов, которые можно использовать с ключевым словом WHERE, наиболее важные из которых приведены в следующей таблице.
Оператор | Описание | Пример |
---|---|---|
= | Равно | WHERE id = 2 |
> | Больше чем | WHERE age > 18 |
< | Меньше чем | WHERE age < 30 |
>= | Больше или равно | WHERE age >= 18 |
<= | Меньше или равно | WHERE age <= 30 |
LIKE | Совпадение с паттерном | WHERE name LIKE ‘Vas’ |
IN | Совпадение со списком значений | WHERE city IN (‘Moscow’, ‘Sochi’) |
BETWEEN | Находится ли в диапазоне значений | WHERE rating BETWEEN 3 AND 5 |
sql — MySQL — вложенный выбор с вложенной группировкой
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
.
sql — MySQL вложенный оператор Select с подзапросом
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
.
Может ли MySQL Nested Select вернуть список результатов
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
Загрузка…
- Авторизоваться
зарегистрироваться текущее сообщество
.
sql — Вложенный оператор MySql Select с пунктом
«где в»
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
Загрузка…