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 (продавцы):
snumsnamecitycomm
1КоловановМосква10
2ПетровТверь25
3ПлотниковМосква22
4КучеровСанкт-Петербург28
5МалкинСанкт-Петербург18
6ШипачевЧелябинск30
7МозякинОдинцово25
8ПроворовМосква25
  • Таблица Customers (покупатели):
сnumсnamecityratingsnum
1ДесновМосква906
2КрасновМосква957
3КирилловТверь963
4ЕрмолаевОбнинск983
5КолесниковСерпухов985
6ПушкинЧелябинск904
7ЛермонтовОдинцово851
8БелыйМосква893
9ЧудиновМосква962
10ЛосевОдинцово938
  • Таблица Orders (заказы)
onumamtodatecnumsnum
10011282016-01-0194
100218002016-04-10107
10033482017-04-0821
10045002016-06-0733
10054992017-12-0454
10063202016-03-0354
1007802017-09-0271
10087802016-03-0713
10095602017-10-0737
10109002016-01-0868

Основы вложенных запросов в SQL

Вывести сумму заказов и дату, которые проводил продавец с фамилией Колованов.

Начнем с такого примера и для начала вспомним, как бы делали этот запрос ранее: посмотрели бы в таблицу Salespeople, определили бы snum продавца Колыванова — он равен 1. И выполнили бы запрос SQL с помощью условия WHERE. Вот пример такого SQL запроса:

SELECT amt, odate
FROM orders 
WHERE snum = 1

Очевидно, какой будет вывод:

amtodate
3482017-04-08
802017-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 выдаст фамилию этого продавца.

Получился такой результат:

snumsname
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 — вложенный выбор с вложенной группировкой

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

sql — MySQL вложенный оператор Select с подзапросом

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

Может ли MySQL Nested Select вернуть список результатов

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании

Загрузка…

  1. Авторизоваться зарегистрироваться
  2. текущее сообщество

.

sql — Вложенный оператор MySql Select с пунктом

«где в» Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании

Загрузка…

    .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Theme: Overlay by Kaira Extra Text
Cape Town, South Africa