Форматирование длинных SQL-запросов

Вступил недавно в локальный оффлайн-холивор на тему форматирования длинных SQL-запросов.

Собственно, весь холивор сводится к тому, что удобнее читать — INNER JOIN ДО таблицы, или ПОСЛЕ неё, а так же — AND — до или после обьявления условия.

Два варианта и вопрос к хабровчанам под катом:


Вариант 1:


SELECT
    t.field,
    t.field1,
    ...
FROM
    table t INNER JOIN
    table1 t1 ON t1.id = t.id INNER JOIN
    table2 t2 ON t2.id=t1.id
WHERE
    t.value = 'foo' AND
    t1.value = 'bar'
GROUP BY t.field
ORDER BY t.field


Вариант 2:


SELECT
    t.field,
    t.field1,
    ...
FROM table t
    INNER JOIN table1 t1
        ON t1.id=t.id
    INNER JOIN table2 t2
        ON t2.id=t1.id
WHERE t.value='foo'
    AND t1.value='bar'
GROUP BY t.field
ORDER BY t.field


Не можем найти стандарты по форматированию запросов — по программированию — полно, по запросам — нет. И до сих пор спорим.

Что скажете, какой вариант используете вы? Первый или второй? А может быть, третий?
+17
3 декабря 2008, 15:26
25
skazkin 34,8

комментарии (94)

0
skazkin #
Что-то отступы у меня не получаются(

Ну, в целом и так понятно
+36
maxshopen #
Не уверен, что для такого вопроса надо было делать целый топик..., но скажу про свои «вкусы»

я привык, что всегда строка начинается с ключевого слова (подобно FROM или ORDER BY), а группировка основных секций-блоков идет с помощью отсутпов. + ON не выделяется отдельно а идет вместе с JOIN (ибо неразрывно с ним связан), т.е. мой вариант такой:

SELECT
	t.field,
	t.field1,
	...
FROM tbl t
	INNER JOIN table1 t1	ON t1.id=t.id
	INNER JOIN table2 t2	ON t2.id=t1.id
WHERE 1=1
	AND t.value='foo'
	AND t1.value='bar'
GROUP BY t.field
ORDER BY t.field

Но не навязываю и ни к чему призываю, само собой..., дело вкуса :)
+3
Xenkok #
я тоже такой использую.
0
Crypto #
Именно так.
0
maghamed #
Я тоже так думаю. Топик на самом деле ни о чем. Все сразу начинают меряться длинной и писать как они форматируют запросы.

Между тем когда я с кем-то тут общаюсь или кто-то высылает комменты к моим статьям по мускулу, то запросы приводят естественнно одной сторокой, даже без намека на форматирование.

О стандартах кодирования написано уже очень много, на самом деле на всех фирмах которые я работал. Мне перед началом работы давали время с ними ознакомиться и писать в указанном стиле.

Хотя я пишу запросы в таком же стиле как и вы.
0
maghamed #
*… на всех фирмах, на которых я работал.
0
maxshopen #
Мне наверно повезло (или наоборот) — но я не прочитал ни одного документа, посвященного стандартам кодирования, и ни разу не попал в команду с устоявшимися стандартами, так что мои вкусы — результат личной практики, ну и что-то где-то бессознательно впитал на чужих примерах :)

Для запросов без форматирования есть несколько причин — тут не очень дружелюбный парсер, имхо. Понять как он работает, и что и когда выкусывает — можно только после некоторой практики, но после пары использований лично у меня было желание им больше не пользоваться. К тому же о том, что тут есть такие возможности лично я как то даже и не сразу заметил (+где то вроде натыкался, что html-тэги доступны только выше некоторого количества кармы, может ошибаюсь).
Ну и потом наверно многим лень этим заниматься.

Кстати не все запросы требуют форматирования. Запросы вида «SELECT name FROM obj_types» я, например, часто так и пишу в одну строку, ибо они слишком просты.
0
maghamed #
Не знаю, я пользовался всезда Source Code Highlighter-ом, меня вполне удовлетворяло всегда как он подсвечивает и представляет в хтмл мои SQL запросы.

Да, вы правы короткие запросы я тоде не форматирую. В этом нет смысла. Но нечто подобное
habrahabr.ru/blogs/mysql/46068/#comment_1171518
Тут уж без форматирования никак. (Дал ссылку на запрос даже не смотря что это за запрос, просто ориентируясь на размер)
0
nileriver #
И я так.
0
pixxxel #
Поддерживаю
0
saratovdae #
точно так )
НЛО прилетело и опубликовало эту надпись здесь
0
s0b3r #
Насчёт регистра — пишу на PHP, и строки подсвечиваются исключительно как строки, одним цветом =)
Поэтому, всё-таки, удобнее, когда ключевые слова в верхнем регистре
0
rlaovai #
Ваш способ благословлен настоятелем.
0
DYPA #
вариант 2, но с
WHERE '
t.value='foo'
AND
t1.value='bar'
0
DYPA #
пробелы сьело
WHERE 
    t.value='foo'
    AND 
    t1.value='bar'
+1
xSeth #
Поддерживаю этот вариант
+1
dparfyonov #
присоединяюсь
+3
tigerman #
Второй вариант, на мой взгляд, более удобоваримый, чем первый.
Даже при беглом взгляде, видно какое действие в какой строчке выполняется.
+1
Saben #
второе. потому что так более читабельно для людей, которые будут читать после меня. Но это мое мнение, что большинство программистов делает так =)
+1
sokiche #
Я бы проверил таким образом: вбил бы запрос в phpmyadmin и посмотрел как он его на выходе отформатирует.
0
skazkin #
К сожалению, для оппонента phpMyAdmin — не аргумент=)
+1
skazkin #
* вбил для проверки, форматирует примерно так же, как предложил maxshopen

=) То есть — phpMyAdmin за второй вариант=)
–10
sokiche #
Того, кто поставил минус сейчас забаню.
–1
JackFrost #
я использую первый вариант.
0
tigerman #
Надо было сделать топик-опрос. Результаты были бы нагляднее.
+1
skazkin #
Тогда не поместились бы примеры туда(
0
trevel #
Тема вложенных запросов не раскрыта, вот там уж точно читабельность необходима.
+1
skazkin #
Мы не используем вложенные запросы в селектах)
+1
trevel #
А другие люди используют. Все зависит от задачи.
+6
maxshopen #
А в чем собственно проблема, следуем тем же принципам:

SELECT
  t.field,
  t.field1,
  ...
FROM 
  ( SELECT
      t_in.field,
      t_in.field1,
      ...
    FROM t_in
    WHERE t_in.id = 100
    GROUP BY t.field
    ORDER BY t.field
  ) AS tbl_in
  INNER JOIN table1 t1 ON t1.id=t.id
  INNER JOIN table2 t2 ON t2.id=t1.id
WHERE 1=1
  AND t.value='foo'
  AND t1.value='bar'
GROUP BY t.field
ORDER BY t.field


К сожалению tab внутри pre разносит сильно вширь, поэтому поменял на nbsp, в редакторе удобнее конечно использовать табуляцию (хотя обять же это дело вкуса)
смысл самого запроса нулевой — только для демки форматирования.

0
Freax #
расскажите смысл «1=1», плз
0
skazkin #
Тут же пример, а не реальные запросы

нет в нём смысла никакого, очевидно
0
dndred #
что бы все реальные условия начинались одинаково — с AND. Сам так часто делаю.
0
FB3 #
Человеку хочется, чтобы условия с новой строчки пошли, но без 1=1 исключается один AND, пропадает форматирование одинаковое для всех условий.
Я бы просто после AND на каждой строчке ставил бы TAB и первую строчку соответственно оттабил, т.е. примерно так:
WHERE
        t.value='foo'
  AND   t1.value='bar'

Здесь пробелы, а не табы, для наглядности.
Кому по душе форматирование пробелами, соответственно, может и пробелами так выровнять.
+4
maxshopen #
Ну во первых, как уже сказали — для красоты :)
Но это не самое главное. Представьте себе ситацию, у нас есть функиция, в которой запрос генерится исходя из переданных ей параметров, например (опять же пример от балды, только для демонстрации)

SELECT ... FROM ...
WHERE
if(def condition1){	f1='condition1'	}
if(def condition2){	f2='condition2'	}

тут видно, что будет ошибка, т.к. в момент первой проверки, мы не знаем результат второй, и поэтому не можем поставит AND после нее. Во стором условии мы не знаем результат первого, поэтому не можем поставить вначале. Более того, если не выполится ни одно их них — то мы получим ошибку синтаксиса из-за пустого WHERE. Эти проблемы можно решить кучей дополнительных if-ов, количество и громоздкость которых будет увеличиваться от количества условий. А можем решить по простому:

SELECT ... FROM ...
WHERE 1=1
if(def condition1){	AND f1='condition1'	}
if(def condition2){	AND f2='condition2'	}


Тут никаких доп. проверок делать не надо, и если не будет задано ни одного условия, то ошибки тоже не произойдет. Причем если мы уже сгенерированный запрос кладем для анализа в лог — то там мы увидим вполне красиво отформатированный запрос. Что же касается MySQL — то он это выражение 1=1 попросту отбросит, потому что оно всегда true.

Поскольку заранее не всегда ясно будет ли этот запрос динамическим или не будет — я приучил себя писать 1=1 всегда.
+1
nmike #
phpMyAdmin ставит 1
WHERE 1 AND…
0
krig #
Пользуюсь форматированием, похожим на вариант №2.
0
pietrovich #
даже не знаю, по джойнам предпочитаю первый вариант, с ним проще охватить список задействованных таблиц, а в WHERE предпочитаю второй…
0
artyfarty #
Второй лучше: в начале строки видно, что делается. Читается как на родном.
0
bar_boss #
Мне кажется что лучший вариант для INNER'ов №2, а для AND'ов №1
0
MoonRabbit #
У Кена Хендерсона в какой-то из книг есть целый раздел посвященный его рекомендациям по написанию запросов.
В целом, я с ним согласен, и при оформлении кода в основном пользуюсь его рекомендациями.
Получается достаточно читабельно с точки зрения стороннего разработчика.
0
MoonRabbit #
P.S. И кто-нибудь может подсказать: что случлось с «избранным»?
Почему я не могу добавить топик в избранное? Куда делась кнопка?
+1
Rusan #
за второй с маленькой модификацией
SELECT field
    , field1
    , field2
...


Так проще строку удалять.
0
AntonShevchuk #
удалять/комментировать
НЛО прилетело и опубликовало эту надпись здесь
0
nolock #
Мне лично нравится так:
   SELECT  polls.`uid`, polls.`valid_days`
     FROM  yr_polls as polls
LEFT JOIN  yr_polls_choice as choice
       ON  polls.uid = choice.poll_id
    WHERE  polls.uid = p_id

Или

    UPDATE yr_polls_choice as pc
       SET pc.`choice` = 'aaaa'
     WHERE pc.`uid` = 1;


0
Approved_by_Biolante #
Вам не тяжело это все выравнивать по правому краю?
0
FB3 #
Наверняка строчки копипастятся и юзается волшебная кнопочка insert, которая позволяет включить режим замены. Главное, один раз подобрать, где будет находиться пробел.
0
Approved_by_Biolante #
Ну, разве что так =)
0
Psih #
Лично мой стиль:

SELECT
    field1, , ..., fieldN
    fieldN+1, ... fieldN+M
FROM table AS t1
LEFT JOIN table2 AS t2 ON t2.field = t1.field
LEFT JOIN table3 AS t3 ON t3.field = t1.field AND t3.field2 = t2.fieldX
WHERE t3.field = 'something' AND t1.field = 'nothing' AND
        t2.field = 'anything'
ORDER BY t1.id DESC
GROUP BY t2.field
LIMIT 0, 10
–1
zabr #
SELECT
    t.field,
    t.field1,
    ...
FROM table t
INNER JOIN table1 t1  ON t1.id=t.id
INNER JOIN table2 t2  ON t2.id=t1.id
WHERE t.value='foo' AND t1.value='bar'
GROUP BY t.field
ORDER BY t.field
0
LeMure #
№2
–7
John_Cherep #
не используте длинные запросы =)
лучше пара коротких. база шустрее работать будет =)
+2
LoneCat #
лучше вообще без базы, так совсем быстро :P
–7
John_Cherep #
заебали своими минусами =)
–1
LoneCat #
Мой вариант:
SELECT
	t1.row1,
	t1.row2,
	t1.row3
FROM table1 AS t1
INNER JOIN table2 AS t2 ON t2.row1 = t1.row1
INNER JOIN table3 AS t3 ON
	t3.row1 = t2.row1 AND
	t3.row2 = t2.row2
WHERE
	t1.row1 = x AND
	t1.row2 = y
ORDER BY t1.row3

тоесть все повторящиеся элементы переносятся на новую строку с отступом, если элемент один — он оставляется на той-же строке, ну и запятые, AND'ы и т.п. оставляются на предыдущей строке, чтобы все повторяющиеся элементы были на одном уровне.
НЛО прилетело и опубликовало эту надпись здесь
+1
azproduction #
Использую ООПодход к форматированию :)

             $query = $db->select()
                         ->from('apartaments')
                         ->join(
                                 'apartaments_types',
                                 'apartaments.atype = apartaments_types.atype',
                                 array(
                                     'atype_name'
                                 )
                             )
                         ->join(
                                 'apartaments_pricetype', 
                                 'apartaments.pricetype = apartaments_pricetype.id', 
                                 array(
                                     'pricetype_text' => 'apartaments_pricetype.description'
                                 )
                            )
                         ->where('uid = ?', $id)
                         ->where('df = ?', 0)
                         ;
0
kurokikaze #
Имхо второй удобнее.
НЛО прилетело и опубликовало эту надпись здесь
0
alexdf #
Кстати, у меня вопрос. Разве необходимо постоянно прописывать JOIN-ы? Т.е. я понимаю когда хочется явно указать порядок поиска при множественных связях, но большенство запросов элементарные и внутренние механизмы современный DBMS легко справляются с распознаванием джойнов. Так ли это?
0
skazkin #
Ну, джоины бывают разные
0
alexdf #
SQL-стандарт он как бы с 1992 года сильно расширился :-) Ну а многие кроме всего прочего свои расширения пихают. Почитайте что в Оракле используется для указаний разных видов соединений в where.

К примеру — where table1.id = table2.id (+)
0
bat #
> К примеру — where table1.id = table2.id (+)
Это костыль старых версий, которые не позволяет сделать сделать более одного левого соединения. 9-ка уже поддерживает джойны по человечески.

P.S.
Если вы считаете (+) удобным — значит вы с этим работали мало или совсем не работали.
+1
maovrn #
Явное описание джоинов облегчит понимание запроса. Кроме того, условие соединение таблиц с джоинами записывается посе ON, а фильтрация выборки после WHERE. Cогласитесь, это лучше, чем мешать все в одну кашу после WHERE.
0
alexdf #
>>Cогласитесь, это лучше, чем мешать все в одну кашу после WHERE.

Кстати не соглашусь. Настоящая каша получается когда указаны все джойны, запрос читается куда хуже. Элементарной писанины куда больше. в Оракле даже специальные вещи придуманы типа (+) чтобы описывать типы разных соединений в where.

На sql.ru многие так и делают. К тому же видел несколько систем где отстутсвтие JOIN-ов в коде было частью их «code style convention». Сперва указываешь соедининия, потом уже бизнесс правила. Все просто, без какой-либо каши :-)
0
bat #
Мое мнение, «каша» — это когда в WHERE перемешаны условия соединения с условиями выборки.
0
bat #
Т.е. я понимаю когда хочется явно указать порядок поиска при множественных связях, но

Порядок соединения не задается порядком джойнов
0
alexdf #
А чем?
0
bat #
Зависит от СУБД. В оракл, например, специальными комментариями (хинтами). В MySQL есть ключевое слово STRAIGHT_JOIN. А в общем случае, когда порядок явно не задан, он определяется оптимизатором.
0
alexdf #
Точно, именно так.
0
Umkus #
Я делаю это так:

#Gets Nike's products
SELECT
	DISTINCT types.*
FROM
	(
		SELECT
			products.id,
			products.name,
			products.item,
			products.brand,
			products.sex,
			products.season
		FROM
			products
		INNER JOIN
			display
			ON
			display.item = products.season
			AND
			MID(
				IF(
					LENGTH(display.status) < 3,
					CONCAT(
						REPEAT(
							0,
							3 - LENGTH(display.status)
						),
						display.status
					),
					display.status
				),
				1,
				1
			) = 1
		INNER JOIN
			types
			ON
			types.brand = products.brand
			AND
			types.sex = products.sex
			AND
			types.season = products.season
		WHERE
			display.page = 289
		LIMIT	100
	) AS activeProducts
INNER JOIN
	types
	ON
	types.id = activeProducts.sex
WHERE
	types.brand = 'nike'
НЛО прилетело и опубликовало эту надпись здесь
0
maghamed #
Привет. А чего ужасного-то? А что с БД сервером такого станется от такого запроса или от какого-либо стиля форматирования?
0
neuro159 #
ужасно не форматирование а код с функциями :(
а почему — гм… думаю правильно будет порекомендовать прочесть «Art of SQL». Легко находится на flazx.com
0
maghamed #
Я читал эту книгу и многие другие тоже, не стоит сразу так категоричто всех посылать пусть даже и в книжные магазины.

Да, я стараюсь не использовать сложные конструкции с функциями в своих запросах, но иногда это весьма удобно, а иногда и просто необходимо.

Поэтому я не вижу причин сразу плеваться на запрос увидев ф-ии в нем.
+1
Umkus #
Спасибо за совет, по=читаю. Я выбрал этот запрос как пример, покрывающий наибольшее число конструкций.
И, если честно, оригинал этого запроса cодержит целых 3 (три) уровня derived. Да, я знаю, что это страшно. Если интересно, могу постараться донести почему.
НЛО прилетело и опубликовало эту надпись здесь
0
maghamed #
в моих проектах встречались запросы и побольше, гораздо больше :-)
0
maghamed #
хотя я не хотел меряться длинной :-))
НЛО прилетело и опубликовало эту надпись здесь
–3
zupernintendo #
только так!

SELECT t.field, t.field1,…
    FROM table t INNER JOIN table1 t1 ON t1.id = t.id INNER JOIN table2 t2 ON t2.id=t1.id
    WHERE t.value = 'foo' AND t1.value = 'bar' GROUP BY t.field ORDER BY t.field

поля, from, where — сколько в строку влезет, остальное отступом
если все влазит в одну строку экрана то в одну строку.

а все остальные стили — ненужное изъебство с претензией на эстетство.
+1
bat #
Соедините в своем стиле десяток таблиц + парочку derived tables и покажите коллеге, чтобы он разобрался что делает такой запрос…
+2
maxshopen #
думаю, лучше сразу пристрелить коллегу, чтобы не мучился :)

–2
Regis #
select
t
from Tools t
join t.parents t1
join t.childs t2
where
t.value='foo' and
t1.value='bar'
group by
t.field
order by
t.field
–2
Regis #
Упс, это не SQL, а HQL ^_^"""

На SQL уже не пишу ;)
0
Regis #
Пардон за убитые отступы:
select
     t
from Tools t
     join t.parents t1
     join t.childs t2
where
     t.value='foo' and
     t1.value='bar'
group by
     t.field
order by
     t.field
0
Regis #
Черт. Никак не отправлю то, что хотелось :(

from
     Tools t
0
nmike #
мои пять копеек

SELECT t1.a, t1.b, t3,t, t3.s
       t2.c, t2.d, t3.h
  FROM db.table1 AS t1 
 INNER JOIN db.table2 AS t2 ON t1.g=t2.j
 INNER JOIN db.table3 AS t2 ON t3.g=t2.l
 WHERE t1.u > 10 
   AND t2.u > 10
   AND t3 < 10
 GROUP BY t2.a ASC
 ORDER BY t1.a DESC
 LIMIT 10

с вложенными вопросами намного сложнее.

кстати, я обратил внимание, что явно не указывается база данных… а ведь очень сильно помогает, когда проект работает с несколькими базами и еще хуже, с несколькими серверами. а при анализе slowquery.log то как помогает…

+1
alexbig #
всегда пишу так: все ключевые слова отдельно и прописными — так легко понять, что за запрос. каждая новая строчка отдельно, чтобы можно было легко закомментировать без переноса запятых и ключевых слов (обычно для этого есть быстрая клавиша, которая срабатывает на той же строчке). имена всегда обособляю, чтобы проще искать при рефакторинге.

SELECT
	`t`.`field`
,	`t`.`field1` `another_field`

FROM
	`table` `t`
	INNER JOIN `table1` `t1` ON (`t1`.`id` = `t`.`id`)
	LEFT JOIN `table2` `t2` ON (`t2`.`id` = `t1`.`id`)
WHERE
	`t`.`value` = 'foo'
	AND `t1`.`value` = 'bar'
GROUP BY
	`t`.`field`
ORDER BY
	`t`.`field` DESC
0
tyhon #
чо то типа этого :)

SELECT
`t`.`field`,
`t`.`field1` `another_field`

FROM
`table` `t`
INNER JOIN
`table1` `t1`
ON
(`t1`.`id` = `t`.`id`)
LEFT JOIN
`table2` `t2`
ON
(`t2`.`id` = `t1`.`id`)
WHERE
`t`.`value` = 'foo' AND
`t1`.`value` = 'bar'
GROUP BY
`t`.`field`
ORDER BY
`t`.`field` DESC

блин как тут всталять отступы…
но думаю и так поянтно будет :)
+2
croatian #
Непонятно =)
0
dfitiskin #
второй вариант, только ON не переношу на новую строку

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.