В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов.
В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.
При написании запросов не используйте выборку всех полей - "*". Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.
1. Выборки
$news_ids = get_list("SELECT news_id FROM today_news ");
while($news_id = get_next($news_ids))
$news = get_row("SELECT title, body FROM news WHERE news_id = ". $news_id);
Правило очень простое - чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN(). Приведенный код можно написать одним запросом:
SELECT title, body FROM today_news INNER JOIN news USING(news_id)
2. Вставки
$log = parse_log();
while($record = next($log))
query("INSERT INTO logs SET value = ". $log["value"]);
Гораздо более эффективно склеить и выполнить один запрос:
INSERT INTO logs (value) VALUES (...), (...)
3. Обновления
Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто:
UPDATE news SET title="test" WHERE id IN (1, 2, 3).
Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом:
UPDATE news SET
title = CASE
WHEN news_id = 1 THEN "aa"
WHEN news_id = 2 THEN "bb" END
WHERE news_id IN (1, 2)
Наши тесты показывают, что такой запрос выполняется в 2-3 раза быстрее, чем несколько отдельных запросов.
В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:
SELECT user_id FROM users WHERE blogs_count = $value / 2;
Аналогичный пример:
SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(registered) <= 10;
Не будет использовать индекс по полю registered, тогда как
SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);
будет.
Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса:
Если в таблице auto_increment"ный первичный ключ и нет пропусков:
$rnd = rand(1, query("SELECT MAX(id) FROM table"));
$row = query("SELECT * FROM table WHERE id = ".$rnd);
Либо:
$cnt = query("SELECT COUNT(*) FROM table");
$row = query("SELECT * FROM table LIMIT ".$cnt.", 1");
что, однако, так же может быть медленным при очень большом количестве строк в таблице.
Нужно помнить, что при связи таблиц один-ко многим количество строк в выборке будет расти при каждом очередном JOIN"е. Для подобных случаев более быстрым бывает разбить подобный запрос на несколько простых.
Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).
If($row)
query("UPDATE table SET column = column + 1 WHERE id=1")
else
query("INSERT INTO table SET column = 1, id=1");
Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:
INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1
В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов.
В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.
При написании запросов не используйте выборку всех полей - "*". Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.
1. Выборки
$news_ids = get_list("SELECT news_id FROM today_news ");
while($news_id = get_next($news_ids))
$news = get_row("SELECT title, body FROM news WHERE news_id = ". $news_id);
Правило очень простое - чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN(). Приведенный код можно написать одним запросом:
SELECT title, body FROM today_news INNER JOIN news USING(news_id)
2. Вставки
$log = parse_log();
while($record = next($log))
query("INSERT INTO logs SET value = ". $log["value"]);
Гораздо более эффективно склеить и выполнить один запрос:
INSERT INTO logs (value) VALUES (...), (...)
3. Обновления
Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто:
UPDATE news SET title="test" WHERE id IN (1, 2, 3).
Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом:
UPDATE news SET
title = CASE
WHEN news_id = 1 THEN "aa"
WHEN news_id = 2 THEN "bb" END
WHERE news_id IN (1, 2)
Наши тесты показывают, что такой запрос выполняется в 2-3 раза быстрее, чем несколько отдельных запросов.
В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:
SELECT user_id FROM users WHERE blogs_count = $value / 2;
Аналогичный пример:
SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(registered) <= 10;
Не будет использовать индекс по полю registered, тогда как
SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);
будет.
Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса:
Если в таблице auto_increment"ный первичный ключ и нет пропусков:
$rnd = rand(1, query("SELECT MAX(id) FROM table"));
$row = query("SELECT * FROM table WHERE id = ".$rnd);
Либо:
$cnt = query("SELECT COUNT(*) FROM table");
$row = query("SELECT * FROM table LIMIT ".$cnt.", 1");
что, однако, так же может быть медленным при очень большом количестве строк в таблице.
Нужно помнить, что при связи таблиц один-ко многим количество строк в выборке будет расти при каждом очередном JOIN"е. Для подобных случаев более быстрым бывает разбить подобный запрос на несколько простых.
Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).
If($row)
query("UPDATE table SET column = column + 1 WHERE id=1")
else
query("INSERT INTO table SET column = 1, id=1");
Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:
INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1
В этой части статьи рассматриваются уловки для выбора одного из двух значений на основе логического условия, передача и получение произвольного числа аргументов функций, а также распространенный источник ошибок - тот факт, что дефолтные значения аргументов функции вычисляются только один раз.
Этот способ весьма новый, и я испытываю к нему смешанные чувства. Это правильная, понятная конструкция, она мне нравится… но она всё еще уродлива, особенно при использовании нескольких вложенных конструкций. Конечно, синтаксис всех уловок для выбора значений некрасив. У меня слабость к описанному ниже способу с and/or, сейчас я нахожу его интуитивным, сейчас я понимаю, как он работает. К тому же он ничуть не менее эффективен, чем «правильный» способ.
Хотя инлайновый if/else - новый, более правильный способ, вам всё же стоит ознакомиться со следующими пунктами. Даже если вы планируете использовать Python 2.5, вы встретите эти способы в старом коде. Разумеется, если вам нужна обратная совместимость, будет действительно лучше просмотреть их.
Аналогично, операция or возвращает первое true-значение, либо последнее, если ни одно из них не true.
Это вам не поможет, если вы просто проверяете логическое значение выражения. Но можно использовать and и or в других целях. Мой любимый способ - выбор значения в стиле, аналогичном тернарному оператору языка C «test? value_if_true: value_if_false»:
test = True
# test = False
result = test and "Test is True" or "Test is False"
# теперь result = "Test is True"
Как это работает? Если test=true, оператор and пропускает его и возвращает второе (последнее) из данных ему значений: "Test is True"
or
"Test is False"
. Далее, or вернет первое true выражение, т. е. "Test is True".
Если test=false, and вернет test, останется test or "Test is False" . Т. к. test=false, or его пропустит и вернет второе выражение, "Test is False".
Внимание, будьте осторожны со средним значением («if_true»). Если оно окажется false, выражение с or будет всегда пропускать его и возвращать последнее значение («if_false»), независимо от значения test.
После использования этого метода правильный способ (п. 4.1) кажется мне менее интуитивным. Если вам не нужна обратная совместимость, попробуйте оба способа и посмотрите, какой вам больше нравится. Если не можете определиться, используйте правильный.
Конечно, если вам нужна совместимость с предыдущими версиями Python, «правильный» способ не будет работать. В этом случае and/or - лучший выбор в большинстве ситуаций.
Также заметьте, что этот способ работает только тогда, когда вы уверены, что test - булево значение, а не какой-то объект. Иначе придется писать bool(test) вместо test, чтобы он работал правильно.
Решение проблемы: не используйте изменяемые объекты в качестве значений по умолчанию. Вы можете оставить всё как есть, если не изменяете их, но это плохая идея. Вот как следовало написать предыдущий пример:
def function(item, stuff = None):
if stuff is None:
stuff =
stuff.append(item)
print stuff
function(1)
# выводит ""
function(2)
# выводит "", как и ожидалось
None неизменяем (в любом случае, мы не пытаемся его изменить), так что мы обезопасили себя от внезапного изменения дефолтного значения.
С другой стороны, умный программист, возможно, превратит это в уловку для использования статических переменных, как в языке C.
Функция также может иметь переменное число именованных аргументов. После определения всех остальных аргументов укажите переменную с "**" в начале. Python присвоит этой переменной словарь полученных именованных аргументов, кроме обязательных:
def do_something_else(a, b, c, *args, **kwargs):
print a, b, c, args, kwargs
do_something_else(1,2,3,4,5,6,7,8,9, timeout=1.5)
# выводит "1, 2, 3, (4, 5, 6, 7, 8, 9), {"timeout": 1.5}"
Зачем так делать? Я считаю, самая распространенная причина - функция является оберткой другой функции (или функций), и неиспользуемые именованные аргументы могут быть переданы другой функции (см. п. 5.3).
У каждой операции есть свои операторы. В унарных и бинарных операциях применяются базовые операторы (сложение, вычитание, отрицание, унарный плюс, унарный минус, присваивание). Тернарный имеет три аргумента: условие if, выражение, если условие == true, и выражение, если условие == false.
Понять, что такое оператор, поможет следующий пример.
A = b + c
К переменной b прибавляется c, результат присваивается переменной a. Весь пример в целом a = b + c - это выражение. Переменные, которые в нем фигурируют, - это операнды. Производимая операция - сложение, а используемый для этого оператор - “+”.
Пайтон предоставляет огромное количество библиотек для решения вычислительных задач. Большой набор методов ставит Python на один уровень с Matlab и Octave. Арифметические операции применяются относительно к целым числам типа int, вещественным типа float, комплексным complex.
Если в качестве аргументов операции используются только целые числа, результат тоже будет целым. Операции между числами с плавающей точкой в результате дадут целое и дробное. Единственная операция, при которой взаимодействие целых чисел дает дробное, - это деление.
Все возможные арифметические операции приведены в таблице.
Добавление одного числа к другому выполняет оператор additional. Вычитание осуществляется с помощью subtraction. Умножение одного числа на другое происходит с multiplication. Возведение в степень осуществляется с помощью exponenta. Для деления используется division.
Оператор modulus (%) возвращает остаток от деления левого операнда на правый. Если переменная a = 10, переменная b = 20, то b%a == 0. Что такое оператор деления с остатком, легко понять на следующем примере. Если 9/2 == 4.5, то 9//2 возвращает результат, равный 4. Деление с floor division (//) возвращает целое число от операции деления левого операнда на правый.
Выполнение любых операций осуществляется непосредственно самими числами или перемеными, которым присвоены числовые значения. Результатом может являться другая переменная либо одна из существующих.
Наряду с целыми и вещественными числами в Python существуют комплексные числа. Они состоят из действительной и мнимой части. Записываются в виде c = a+bj, где а - действительная часть,
C.real() #a
b - мнимая.
C.imag() #b
Арифметические операции c комплексными числами имеют те же свойства, что и с вещественными. Использование complex numbers можно представить на плоскости с прямоугольной системой координат. Точка a пересечения оси X и оси Y соответствует комплексному числу x + yi. Таким образом, на оси X располагаются вещественные числа, а на вертикальной оси Y - мнимые.
Операторы в Python используется для сравнения переменных. Кроме стандартных, известных из математических задач, существует проверка по значению и по типу, а также проверка неравенства.
Операции сравнения осуществляются в виде a x b, где x - это оператор сравнения.
В программировании оператор “=” работает не так, как в математике. Соответствие значений каждого аргумента определяется оператором “==”, но “=” только присваивает значение. С помощью!= проверяется неравенство переменных. Этот оператор можно заменить как “<>”, что не является стандартным оператором в других языках, как Си, Джава или Джаваскрипт.
Операторы Python присваивают значение переменной.
Присваивание является одной из центральных конструкций в программировании. С его помощью переменным задаются некоторые значения, они могут изменяться в ходе программы.
Алгоритм работы:
И математической операции работают по такому принципу:
a x b, где x - это оператор, означает что a = a x b. Таким образом, a += b говорит о том, что значение переменной a прибавляется к значению переменной b, а их результат присваивается переменной a. То же самое происходит с другими примерами. Например, a **= b расшифровывается как a = a ** b, то есть a возводится в степень b, результат в итоге присваивается a.
Проверка условий выполняется с помощью тернарного оператора Python.
Он состоит из двух или трех частей:
Выражение можно задать в одной строке.
A = Y if X else Z
Части else и elseif можно отбрасывать, выражение выглядит так:
If 1: print("hello 1")
В Пайтоне существуют операторы break и continue. Break прерывает выполнение кода на всех уровнях. Continue прекращает текущую итерацию, продолжает выполнение со следующей точки.
Такие операторы Python интерпретируют операнды как последовательность нулей и единиц.
Они используют числа в двоичном представлении, возвращают результат в виде 32-битного числа.
a = 0 #a = 1 #a = 2 #a = 3 #a = 255 #
Отрицательное число в двоичном формате получается методом замены бита на противоположное и прибавления 1.
314 #-314 #+ 1 =
Отличие a >> b от a >>> b в том, что при сдвиге и отбрасывании правых значений слева добавляются копии первых битов.
9 #9 >> 2 #-9 #-9 >> 2 #
Но при a >>> b левые значение будут заполнены нулями.
9 #-9 >>> 2 #
Всего существует три логических оператора.
Оператор принадлежности проверяет, является ли переменная частью некоторой последовательности.
В списке приведены операторы и выражения, отсортированные по приоритету выполнения от меньшего к большему.
Операторы в Python и их приоритет выполнения:
Первый пункт в списке - лямбда-выражение. Lambda expression используется для создания анонимных функций. Лямбда ведет себя как обычная функция, и объявляется в виде
Def
После лямбда-выражения следуют операции, выполняемые тернарным оператором Python.
В конце списка располагаются методы манипуляции массивом и функциями. Обращение к элементу массива по индексу выглядит так:
В рассматриваемом случае а - это массив, i - это индекс элемента.
Слайсинг означает передачу полной копии массива или выборочную последовательность из членов списка. Диапазон желаемых значения указывается в . В качестве аргументов x представляется начало отсчета, y - конец, а z - шаг прохождения по элементам массива при каждой итерации. X по умолчанию обозначает начало списка, y - конец, z равняется единице. Если указать z как отрицательное число, значения списка передаются в обратном порядке с конца к началу.
Оператор | Описание | Примеры |
---|---|---|
+ | Сложение - Суммирует значения слева и справа от оператора |
15 + 5 в результате будет 20 |
- | Вычитание - Вычитает правый операнд из левого | 15 - 5 в результате будет 10 20 - -3 в результате будет 23 13.4 - 7 в результате будет 6.4 |
* | Умножение - Перемножает операнды | 5 * 5 в результате будет 25 7 * 3.2 в результате будет 22.4 -3 * 12 в результате будет -36 |
/ | Деление - Делит левый операнд на правый | 15 / 5 в результате будет 3 5 / 2 в результате будет 2 (В Python 2.x версии при делении двух целых чисел результат будет целое число) 5.0 / 2 в результате будет 2.5 (Чтобы получить "правильный" результат хотя бы один операнд должен быть float) |
% | Деление по модулю - Делит левый операнд на правый и возвращает остаток. | 6 % 2 в результате будет 0 7 % 2 в результате будет 1 13.2 % 5 в результате 3.2 |
** | Возведение в степень - возводит левый операнд в степень правого | 5 ** 2 в результате будет 25 2 ** 3 в результате будет 8 -3 ** 2 в результате будет -9 |
// | Целочисленное деление - Деление в котором возвращается только целая часть результата. Часть после запятой отбрасывается. | 12 // 5 в результате будет 2 4 // 3 в результате будет 1 25 // 6 в результате будет 4 |
Оператор | Описание | Примеры |
---|---|---|
== | Проверяет равны ли оба операнда. Если да, то условие становится истинным. | 5 == 5 в результате будет True True == False в результате будет False "hello" == "hello" в результате будет True |
!= | 12 != 5 в результате будет True False != False в результате будет False "hi" != "Hi" в результате будет True |
|
<> | Проверяет равны ли оба операнда. Если нет, то условие становится истинным. |
12 <> 5 в результате будет True. Похоже на оператор!= |
> | Проверяет больше ли значение левого операнда, чем значение правого. Если да, то условие становится истинным. | 5 > 2 в результате будет True. True > False в результате будет True. "A" > "B" в результате будет False. |
< | Проверяет меньше ли значение левого операнда, чем значение правого. Если да, то условие становится истинным. | 3 < 5 в результате будет True. True < False в результате будет False. "A" < "B" в результате будет True. |
>= | Проверяет больше или равно значение левого операнда, чем значение правого. Если да, то условие становится истинным. | 1 >= 1 в результате будет True. 23 >= 3.2 в результате будет True. "C" >= "D" в результате будет False. |
<= | Проверяет меньше или равно значение левого операнда, чем значение правого. Если да, то условие становится истинным. | 4 <= 5 в результате будет True. 0 <= 0.0 в результате будет True. -0.001 <= -36 в результате будет False. |
Оператор | Описание | Примеры |
---|---|---|
= | Присваивает значение правого операнда левому. | c = 23 присвоит переменной с значение 23 |
+= | Прибавит значение правого операнда к левому и присвоит эту сумму левому операнду. |
с = 5 |
-= | Отнимает значение правого операнда от левого и присваивает результат левому операнду. |
с = 5 |
*= | Умножает правый операнд с левым и присваивает результат левому операнду. |
с = 5 |
/= | Делит левый операнд на правый и присваивает результат левому операнду. | с = 10 а = 2 с /= а равносильно: с = с / а. c будет равно 5 |
%= | Делит по модулю операнды и присваивает результат левому. | с = 5 а = 2 с %= а равносильно: с = с % а. c будет равно 1 |
**= | Возводит в левый операнд в степень правого и присваивает результат левому операнду. | с = 3 а = 2 с **= а равносильно: с = с ** а. c будет равно 9 |
//= | Производит целочисленное деление левого операнда на правый и присваивает результат левому операнду. | с = 11 а = 2 с //= а равносильно: с = с // а. c будет равно 5 |
Побитовые операторы предназначены для работы с данными в битовом (двоичном) формате. Предположим, что у нас есть два числа a = 60; и b = 13. В двоичном формате они будут иметь следующий вид:
Оператор | Описание | Примеры |
---|---|---|
& | Бинарный "И" оператор, копирует бит в результат только если бит присутствует в обоих операндах. | (a & b) даст нам 12, которое в двоичном формате выглядит так 0000 1100 |
| | Бинарный "ИЛИ" оператор копирует бит, если тот присутствует в хотя бы в одном операнде. | (a | b) даст нам 61, в двоичном формате 0011 1101 |
^ | Бинарный "Исключительное ИЛИ" оператор копирует бит только если бит присутствует в одном из операндов, но не в обоих сразу. | (a ^ b) даст нам 49, в двоичном формате 0011 0001 |
~ | Бинарный комплиментарный оператор. Является унарным (то есть ему нужен только один операнд) меняет биты на обратные, там где была единица становиться ноль и наоборот. | (~a) даст в результате -61, в двоичном формате выглядит 1100 0011. |
<< | Побитовый сдвиг влево. Значение левого операнда "сдвигается" влево на количество бит указанных в правом операнде. | a << 2 в результате даст 240, в двоичном формате 1111 0000 |
>> | Побитовый сдвиг вправо. Значение левого операнда "сдвигается" вправо на количество бит указанных в правом операнде. | a >> 2 даст 15, в двоичном формате 0000 1111 |
Оператор | Описание | Примеры |
---|---|---|
and | Логический оператор "И". Условие будет истинным если оба операнда истина. |
True and True равно True. |
or | Логический оператор "ИЛИ". Если хотя бы один из операндов истинный, то и все выражение будет истинным. | True or True равно True. True or False равно True. False or True равно True. False or False равно False. |
not | Логический оператор "НЕ". Изменяет логическое значение операнда на противоположное. | not True равно False. not False равно True. |
В добавок к перечисленным операторам, в Python присутствуют, так называмые, операторы членства, предназначенные для проверки на наличие элемента в составных типах данных, таких, как строки, списки, кортежи или словари :
Операторы тождественности сравнивают размещение двух объектов в памяти компьютера.
В следующей таблице описан приоритет выполнения операторов в Python от наивысшего (выполняется в первую очередь) до наинизшего.
Оператор | Описание |
---|---|
** | Возведение в степень |
~ + - | Комплиментарный оператор |
* / % // | Умножение, деление, деление по модулю, целочисленное деление. |
+ - | Сложение и вычитание. |
>> << | Побитовый сдвиг вправо и побитовый сдвиг влево. |
& | Бинарный "И". |
^ | | Бинарный "Исключительное ИЛИ" и бинарный "ИЛИ" |
<= < > >= | Операторы сравнения |
<> == != | Операторы равенства |
= %= /= //= -= += *= **= | Операторы присваивания |
is is not | Тождественные операторы |
in not in | Операторы членства |
not or and | Логические операторы |