25 важных мелочей, о которых ты можешь забыть в процессе подготовки

Готовый перевод Re: Zero kara Hajimeru Isekai Seikatsu / Re: Жизнь в альтернативном мире с нуля: Глава 14 — «Момент истины»

Розваль: Ты смог одолеть Белого кита и отразил атаку культа ведьмы на особняк. Также ты смог заключить альянса с Круш, одной из кандидатов на королевский престол. Как ни посмотри, твои заслуги велики.

Упиравшись об кровать, Розваль взял себя за подбородок и закрыл глаза. Посреди ночи во время их разговора с Субару он решил упомянуть его достижения. Тот воздержался от привычных раздражающих комментариев. Конечно, Субару любит чересчур похвалить себя — но сказанного Розвалем было более чем достаточно.

Рам: . если честно, все эти безумные поступки совсем не сочетаются с твоей личностью. И когда ты успел стать таким героем?

Субару: Что, хочешь подразнить меня? Хотя, если честно, я задаю себе тот же вопрос. Как у меня получилось провернуть все это. Как я рассчитал нужные шаги. Но, в любом случае, что сделано — то сделано.

Его фраза была из разряда «капитан-очевидность». Тем не менее, пусть Рам и подшучивала над ним, уважение в ее голосе мог не почувствовать только совсем недалекий. Ощущая это, Субару с каждой секундой ощущал, как его распирает от гордости.

Розваль: Никто не ожида-а-ал такого. Это настолько неве-е-ероятно, что даже я. да никто бы не смог преду-у-угадать.

Пока все вокруг пытались переварить свой восторг, Розваль, важно задрав голову, высказал свою похвалу. С серьезным лицом он сел на стул перед кроватью и посмотрел на Субару своими разноцветными глазами.

Розваль: Во-пе-е-ервых, я бы хотел еще раз поблагодарить тебя за все, что ты сделал — за то, что защитил мой дом и моих подданных. И, как сторонник лагеря Эмилии, я хочу поблагодарить тебя за весь вклад в отношении налаживания политических связей.

Субару: Ммм. ну да. Почему-то твой торжественный тон заставляет меня краснеть. В моих действиях нет ничего такого выдающегося, так что.

Рам: Господин Розваль, похоже, Барусу просто не в состоянии полностью оценить вашу благодарность.

Она перебила Субару и посмотрела на того уничтожающим взглядом.

Рам: Прервать своего работодателя и отказаться от слов благодарности. что за невежество? Не говоря о том, что господин Розваль имеет статус маркграфа — и его решения обладают силой власти королевства Лугуника. Его благодарность — это не просто слова.

Субару не знал, что и сказать.

Рам: Для господина Розваля с его статусом даже сама благодарность — уже щедрый дар, который могут получить далеко не многие. Так что, впредь, думай, что говоришь.

Ее слова звучали как пощечина для Субару. После такой речи он закрыл глаза — не в состоянии что-либо ответить. Но Розваль поднял руку и слегка замахал ей.

Розваль: Не стои-и-ит, не стои-и-т! Рам слишком преувеличивает. Мои слова не обладают такой ценностью.

Рам: Но господин Розваль!

Воспротивилась она, но Розваль лишь помахал своей головой.

Розваль: Не нужно быть маркграфом, что понять уровень заслуг Субару. Поэтому если я как следует не отблагодарю его, это будет, как минимум, странно.

Субару: . И как же ты хочешь сделать это?

Розваль: Я дам тебе то, чего ты заслуживаешь. Субару, ты помнишь, что случилось во время королевских выборов?

Заметив сконфуженный взгляд Субару, Розваль усмехнулся. В голове у того сразу же всплыли те отвратительные воспоминания, о которых он пожелал бы забыть — его мерзкое поведение, непонимание некоторых важных вещей и бездумные действия, вызванные его же глупостью. Но.

Субару: Я помню. Как такое забудешь. Хотя сейчас я понимаю, что это станет хорошим наставлением на будущее.

Розваль: Тогда, в соответствии с твоими деяниями, я дают тебе. Даже не знаю, чтобы такого предложить. Шучу. На рассвете я выдам тебе титул рыцаря.

Субару удивленно расширил свои глаза. Не давай секунды на обдумывание, Розваль кивнул в ответ.

Розваль: Участие в охоте на Белого кита вместе со сторонниками Круш, а также убийство одного из архиепископов греха культа ведьмы — деяния, достойнейшие настоящего героя. Поэтому с одного дня ты, Нацуки Субару, станешь рыцарем, чтобы с гордостью и честью нести свое имя. Никто больше не сможет посмеяться над твоими словами.

После боли. унижений. трудностей. и ненависти он смог наконец-то стать настоящим защитником Эмилии. Его дорога, полная опасностей, все-таки привела его к тому, о чем он так давно мечтал. Наверное, его поступки действительно достойны всего того, о чем сказал Розваль. Правда, он не смог всего этого сделать без той, кого нет с ними. без Рем, которая сейчас существует теперь лишь в его сердце.

Субару: . Я с благодарностью принимаю это. Похоже, что мои заслуги, и правда, чего-то значат.

Розваль: То, что ты сделал, не поддается оценки. И теперь ты смог заслужить полноценное право стоять рядом с госпожой Эмилией — с гордо поднятой головой. И все это ты сделал сам.

Субару: Нет, не сам.

В ответ он тихо пробормотал эти слова. Не понимая, о чем говорит Субару, Розваль удивлённо приподнял свои брови. Субару закрыл свои глаза и глубоко вздохнул. Открыв их, он наигранно пожал плечами и.

Субару: Я хотел сказать, что в такой сложившей ситуации мне пришлось превзойти самого себя — и сейчас, вспоминая об этом, чувствую себя слегка смущенным.

Розваль: . Ну если та-а-ак. Что-то ты сли-и-ишком серьезный — обычно Субару ведет себя не та-а-ак.

Пытаясь слегка расслабить атмосферу, Розваль подшутил над Субару. Рам, наблюдавшая за разговором между ними, слегка вздохнула и произнесла.

Рам: Теперь, Барусу, твоя очередь. Кажется, ты хотел что-то спросить у господина Розваля? Поэтому я и увела госпожу Эмилию отсюда.

Субару: Спасибо, это очень помогло. Но я не считаю, что для нее было бы лишним услышать все, что будет произнесено здесь. Просто боюсь, что дядька Рос может при ней о чем-то умолчать.

В ответ на слова Рам Субару печально улыбнулся. Заметив это, она еще посмотрела на то пустое место, где была сейчас Эмилия.

Рам: Тогда мне, наверное, стоит присоединиться к госпоже Льюис и проверить храм. Но что насчет Эмилии? Не станет ли ей одиноко без тебя?

Субару: У меня тоже появлялись такие опасения. Скорее всего, с ней сейчас Отто. Этот хитрый и наглый бабник! Надеюсь, он не распустит руки в сторону моей невероятно милой Эмилии.

Розваль: Не стоит забивать голову посторо-о-онними мыслями. В любом случае, ты прав — я не хоте-е-ел бы, что госпожа Эмилия присутствовала при нашем разговоре.

Пока Субару нянчил свои бесполезные опасения, Розваль кивнул головой — подтвердив догадки касательно Эмилии. Услышав это, Субару закрыл один глаз и с недовольным видом произнёс.

Субару: Как я и думал, ты намеренно скрываешь от моей Эмилии некоторые факты. И как ты это объяснишь?

Розваль: Есть вещи, о которых лучше умолчать. Госпожа Эмилия как кандидат на престол гораздо важнее, чем я. Поэтому моя обязанность заботиться не только об ее знаниях и способностях, но и об ее духовном состоянии. Лишнее может ей пойти только во вред.

Субару: Хочешь сказать, что лучше давать всего понемногу и по мере готовности? Но если она не будет знать всего, то не сможет расти как личность. Для тебя, я думаю, это тоже плохо.

Кратко пересказав слова Розваля, он запротестовал в ответ. Розваль замолчал и, закрыв глаз, посмотрел на Субару, который при этом старался казаться спокойным. Ее левый желтый глаз продолжал смотреть на него, будто пытаясь проделать в нем дыру. Субару уже начал волноваться, как Розваль засмеялся и.

Розваль: Хм, умеешь же ты ста-а-авить словами палки в колеса. Но поэтому я и собираюсь это сделать.

Розваль: Больше я не буду ничего скрывать и постараюсь ответить на все вопросы Субару. С учетом моих ран я не смогу никуда сбежать. По-моему, для тебя все сложилось лучши-и-им образом, не так ли?

Сказав это, Розваль тихо засмеялся. На мгновение Субару растерялся, но потом удивленно вытянулся вперед.

Субару: . Ну наконец-то.

Розваль: То, что ты до сих по-о-ор мне не доверяешь, слегка обижает. Но если брать всю ситуа-а-ацию в целом, это не удиви-и-ительно.

Субару: Честно, я понимаю, что осторожность превыше всего, но нужно знать меру. Уж слишком много разных мелочей, о которых я был совершенно не в курсе. Но надеюсь, что теперь все изменится.

Он кивнул в ответ Субару, который наконец-то перестал смотреть на него подозрительно и слегка расслабился.

Розваль: Заслуги, которые ты совершил последние несколько дней, доказали, что Нацуки Субару заслуживает доверия. Так что, теперь я спокоен. С этого дня я могу полагаться на тебя. Я осознал, что могу считать тебя своим полноценным союзником.

Субару: Эй-эй, погоди немного, такое ощущение, будто на меня накладывается слишком большая ответственность. Не нужно так широко открывать свою душу — всего лишь немного доверия более чем достаточно.

Розваль: Ха, как пе-е-ечально, когда твои глубокие порывы ни-и-икто не ценит.

Субару: Да я не о том. Просто все уж слишком быстро, я немного не могу прийти в себя.

Со стороны их разговор мог показаться как общение между недавно встречающейся парой, разбирающейся в своих романтических чувствах. Подумав об этом, Субару откашлялся и, отмахнувшись от этих мыслей, продолжил.

Субару: Давай пока отложим все, что касается «союзника». Просто ответь, пожалуйста, на все мои вопросы. Во-первых, почему ты действительно скрываешь информацию от моей Эмилии?

Опять Розваль замолчал и, закрыв один глаз, уставился на Субару. Причина, по которой он скрыл все это, так и не была понятна — даже с учетом всех знаний, который Субару получил в своих предыдущих жизнях. Если бы Эмилия, будучи полуэльфийкой от рождения, заранее знала, что ее положение станет причиной надвигающейся опасности со стороны культа, они могли заранее подготовиться нападению на деревню Алам и территорию Розваля. А вместо этого Субару лишь чудом удалось избежать худшего варианта — при этом пожертвовав Рем.

Субару: Ответь мне, Розваль. Если ты хочешь сделать Эмилию королевой, то для тебя было бы проблемой, если бы с ней что-то случилось в процессе выборов. Тем не менее, ты скрыл важную для нее информацию — тем самым, поставив ее в тяжелую ситуацию. Я не понимаю такой логики.

Розваль: Хорошо, я отвечу. Все действительно так, как ты описал. Я скрывал от госпожи Эмилии часть информации.

Субару: Я не понимаю тебя! Разве не правильно делиться с ней теми знаниями, которые необходимы ей для выживания в дальнейших выборах?

Розваль: Это верно. Но действительно ли это пошло на пользу для нее?

Ее ответ поразил Субару, который нахмурился — не понимая, о чем речь. Облокотившись на кровать, Розваль продолжил.

Розваль: Субару, в целом, ты прав. Узнав о том, что госпожа Эмилия собирается участвовать в королевских выборах, культ ведьмы, несомненно, сделал бы свой шаг. И напал бы на мою территорию. Естественно, если я знаю об этом, то логично было заранее подготовиться к их встрече.

Субару: Ну да. Любой бы сделал подобное. Насколько мне известно, все знают о том, как относится культ ведьмы к полуэльфам? Уж ты-то точно должен знать. В таком случае, почему же ничего не было сделано. И, вообще, почему ты покинул особняк и остался в храме?

Розваль: Ну, я ведь был привязан к нему, поэтому и не мог вернуться.

Субару: Хватит уже с этим. Мы уже проехали тот момент, в котором ты специально довел себя до такого состояния, чтобы избежать недовольства со стороны жителей деревни Алам. Таким образом, это произошло уже после того, как они были эвакуированы мной. И, получается, все это ты сделал по своему личному желанию, а не по воле событий.

Розваль: Задавить логикой своего оппонента. Ммм, смо-о-отрю, ты растешь.

Субару легко парировал его слова — и тот, не зная, что сказать, неуверенно пожал плечами. Не удовлетворённый таким ходом вещей, Субару сделал шаг вперед и.

Рам: Господин Розваль ранен. Пусть Субару и позволено задавать вопросы, но всему есть предел. Рам не позволит больше мучить господина Розваля.

Субару: Кажется, ты сама была в курсе всего. Не нужно ко мне относиться, как к мусору. Ты ведь знала, что эти психи придут в деревню. А он просто-напросто сбежал. Как ты можешь простить такое?

Рам: Ты не понимаешь. Любое действие господина Розваля абсолютно для меня. То, как я отношусь к этому, совершенно не важно.

Не понимая такую верность, Субару почувствовал, как его переполняет гнев. Тем не менее, он заставил себя успокоиться и произнес.

Субару: . Теперь, получается, совсем не понятно, зачем вообще Рем принесла себя в жертву.

Рам: О чем ты? Не знаю, кто это, но этот человек совсем не связан с Рам. Для меня лишь важен господин Розваль — и ничего больше.

Отчаянный шаг, на который пошел Субару, не принес ничего. Впрочем, он понимал, что не было никого смысла упоминать о Рем. Ведь она исчезла в этом мире для всех — в том числе, и для Рам. Изначально Субару и так был в курсе о необычной преданности Рам по отношению к Розвалю. Но раньше он был не единственным человеком, находившимся в ее сердце. Сейчас после потери памяти о Рем все изменилось. Субару не был в курсе прошлого Рам и Рем. Но если собрать воедино все крупицы информации, которые ему поведала Рем, можно было понять, что с самого детства они были неразделимы. Чувствуя свою вину и обладая комплексом неполноценности, Рем стала зависимой от своей сестры. И в ответ Рам чувствовала себя ответственной по отношению к своей младшей сестре. Для Рем ее старшая сестра была целой вселенной. Рам испытывала к ней аналогичные чувства, но в ее сердце Рем делила это место еще и с Розвалем. Правда, стоит заметить, что в свое время Субару смог повлиять на мировоззрение Рем — и она стала смотреть на вещи другими глазами. Но вот Рам осталась прежней. И позабыв свою младшую сестру, она полностью предалась воле Розваля. Возможно, такое объяснение покажется слегка надуманным, но в этом и была причина ее такой неудержимой преданности по отношению к своему господину.

Розваль: Рам, ну не сто-о-оит так давить на Субару. Все-таки, он не хотел говорить что-то пло-о-охое. Не стоит так си-и-ильно напрягаться.

Рам: Слушаюсь, господин Розваль.

Розваль: Да-да, все в по-о-орядке. Правда, Субару? Похоже, что ты не собираешь изливать на меня свое недовольство. Так что, мы мо-о-ожем вполне спокойно продолжать разговор, разве не-е-ет?

Субару: О чем ты говоришь.

Розваль: Все просто. Если бы со мной говорил сейчас тот ты, что был пару дней назад, была бы уже давно истерика и крики. Но ты сумел удержать себя в руках. Другим словами, ты вырос.

Легко похлопав, он похвалил поведение Субару. Но тот все еще чувствовал взрывающее чувство злости в груди — настолько, что хотелось кричать и крушить. Однако он взял себя в руки и, глубоко вздохнув, успокоился. А затем понял, что Розваль действительно прав — и после этого еще больше почувствовал недовольство.

Каждая невеста должна знать:  Фото дня невеста на Санторини

Розваль: И-и-итак, издеваться над молодежью — это не слишком взрослый поступок. Как я могу вести себя по-детски, если ты показываешь себя самым достойнейшим образом?

Субару: . Хорошо. И, все-таки, я хотел бы услышать ответ на мой предыдущий вопрос — и честный ответ. Почему ты скрыл от Эмилии информацию о культе ведьмы? И почему ты покинул нас в такой ответственный момент? Ведь ты наш самый сильный козырь.

Розваль: Все просто. Я делал это все для того, чтобы избежать открытого конфликта с культом ведьмы.

Он тихо промямлил, пытаясь собрать в голове нужные мысли — однако это происходило с трудом. Сжав зубы, он медленно переварил услышанное и.

Субару: Не понимаю. Ты сказал, что не хочешь открытого конфликта с культом ведьмы. Но почему? Разве они натворили много всего в королевстве? Этого тебе не достаточно? Если ты был с нами, мы бы смогли решить проблему сразу. Без каких-либо потерь.

Розваль: Понимаю. Если бы я был с вами, было бы гораздо меньше ущерба. Я прекрасно осознаю силу своих способностей — как один из десяти сильнейших магов королевства. Могу заявить точно: если бы с вами был я, то наверняка бы смог отразить атаку культа ведьмы.

Субару: Так почему же.

Розваль: Как раз, именно поэтому.

Злобно плюнув на пол, Субару тыкнул пальцем в него — но Розваль схватил его и направил в потолок.

Розваль: Если бы я участвовал в этом, это бы не считалось твоим достижением или Эмилии, так? Я бы забрал вашу славу. к сожалению.

Несмотря на это, он все еще не мог понять. Мысленно он надеялся, что это какая-то шутка – и, держа язык за зубами, ждал продолжения его объяснению. А тот наклонился поближе к самому Субару.

Розваль: Нечего сказать? Наконец-то, ты понял все полноту ситуации. Как думаешь, почему я оставил такого помощника, как ты, в столице?

Субару: Ты. что ты хочешь. Ты хоть понимаешь, что несешь?

Розваль: Я не понимаю тебя, Субару. В чем проблема? О чем ты? О потерях в деревни Алам? Или о том, сколько сил ты потратил, чтобы заслужить расположение Круш и тех наемников? Или о своих каких-то собственных утратах?

Как будто прочитав мысли Субару, он назвал все, о чем тот думал сейчас — так, словно все эти вещи не заслуживают внимания. Субару почувствовал, как дрогнуло его сердце. Ранее, когда он разговаривал с Паком, и тот вскользь упомянул заслуги Рем как само собой разумеющееся, Субару впал в ярость. Сейчас, правда, он понимает, что чувства человека и духа совершенно отличаются — поэтому Субару не имел право судить Пака. Но Розваль был такой же человек, как и он. Он понимал, о чем говорит Субару, понимал его страдания — но, тем не менее, считает разумным свое жесткое решение. И поэтому.

Субару: Я понимаю! Не хочу этого признавать, но я понимаю, о чем ты. О том, что если бы ты сразился с культом, то это не имела никакого эффекта по отношению к королевским выборам. Я все это понимаю! Но даже так. Даже так!

Он сжал зубы и яростно замахал своими руками.

Субару: Ты хоть знаешь, сколько людей отдало свои жизни, поэтому ты свалил и нехера ничего не сделал? Пусть это не измеряется сотнями-тысячами. Но там гибли люди. Наши союзники, даже эти чокнутые фанатики.

Розваль: Даже мое участие вряд ли что-то изменило. Я бы просто превратил их в пепел. Я могу понять твою злость в отношении потерь наших союзников, но ставить меня ответственным за потери врага как-то уже пере-е-ебор, не находишь?

Субару: Плевать, мы бы могли решить это более мирно. Хотя ладно, опустим это! Да, наши потери малы, враг уничтожен. Моя Эмилия в порядке, жители Алам спасены. Но все это. все это получилось благодаря счастливому стечению обстоятельств. И если это так.

Если это так, то один просчет Субару — и деревня, особняк и Эмилия.

Субару: Все бы погибли. Если ситуация не сложилась так, как произошло. была бы настоящая трагедия. Замученные до смерти, они бы все. просто погибли.

Прикрыв свое лицо, он замолчал, чувствуя, как к глазам поступают слезы. Перед его глазами всплыли те адские сцены, когда ему не удавалось защитить то, что для него было так дорого: горящая деревня, разбросанные тела, мертвые дети, труп Рем, пригвождённый в саду особняка, и холод. ужасные невыносимый холод. И именно после этого Субару вернулся снова после смерти, чтобы исправить все.

Субару: Если бы ты был тогда, этого бы не произошло. Можно сказать, что ты позволил им умереть. Как думаешь, сколько раз твоя халатность убила этих людей.

Розваль: Не понимаю, о чем ты. Вообще-то, на них напал не я, а культ ве-е-едьмы. К тому же, он был остановлен тобой, поэтому никаких жертв, о которых ты говоришь сейчас, и не было. Это просто пустые слова.

Субару: Да ты что.

Он тихо пробормотал это в ответ на бездушные слова Розваля. Значит, вот чем они для него являются? Пустыми словами? Но Субару был не в силах объяснить всего. Ведь никто не знает о его способности возвращаться после смерти — и он не мог поделиться этим с кем-нибудь. Поэтому, действительно, у Субару не было право обвинять в чем-то Розваля. Ведь только сам Субару испытал эти ужасные события — и он же сам исправил их, избавил Розваля от ответственности.

Субару: . Что бы ты делал, если бы я был всего лишь бесполезным созданием? Ты, как и я, желаешь сделать Эмилию королевой. Если бы я опоздал всего лишь на миг, ничего нельзя было уже изменить. Шансы того, что все бы погибли, были очень высоки.

Розваль: Но ты исправил все. Разве не это главное?

Субару: Нет. Ведь ты не похож на человека, который верит в случайности.

Есть люди, которые готовы рисковать. Они полагаются на удачу — не зная, победят или нет. А есть те, кто делает все для подготовки к будущему — совершенно не веря в удачу. И эти вторые обычно и выходят победителями.

Субару: Ты из тех людей, которые продумывает все мелочи наперед. Поэтому на что ты надеялся?

Розваль: Я. просто верил в тебя.

В итоге Розваль ответил самым неожиданным образом. Услышав это, Субару не смог сдержать свой смех.

Субару: Ты издеваешься надо мной?

Розваль: Веришь ты или нет — это не важно. Но я говорю правду, понимаешь? Сегодня я решил говорить тебе только пра-а-авду — и ничего кроме правды. Да, есть вещи, о которых я бы хотел умолчать — и я это делаю. Но все, что было сказано мной, не является ложью.

Серьезным голосом произнес эту речь Розваль — но даже это не впечатлило Субару. О каком доверии вообще идет речь? После таких слов Субару еще больше утратил веру в слова Розваля. Но тот покачал головой — заметив презрительный взгляд Субару.

Розваль: Я скажу это еще раз — все мои решения были основаны на вере в тебя. Я знал, что если бы госпожа Эмилия попала в беду, то ты бы непременно направил все свои усилия на создание альянса с фракцией Круш — и в дальнейшем смог бы отразить атаку культа ведьмы, заработав всенародную славу.

Субару: Хорошо, представим на секунду, что это все это правда. Но как? Как ты смог довериться кому-то вроде меня? Да что ты обо мне знаешь! Мы были знакомы лишь месяц. Разве я был похож на кого-то, на кого можно возложить такую ответственность?

Субару от гнева ударил ногой в пол, не соглашаясь с приведенными доводами Розваля. Он ткнул в него пальцем, не дав Розвалю даже время возразить на его слова.

Субару: Ты даже не знаешь, что я такое. До нашей встречи я был настоящим отбросом. Может, сейчас что-то и поменялось. Но все это вряд ли видно с первого взгляда. Поэтому что. что ты увидел во мне такого, что заставило тебя поверить в меня?

Розваль закрыл свой глаз — уставившись желтым на Субару также пронзительно, как в прошлый раз. Пытаясь совладать с собой, он вновь ударил ногой в пол — на этот раз, сильнее.

Субару: Хватить городить чушь. Хочешь сказать, что ты поверил в такого пустоголового мелкого идиота, поставив на кон свою землю, свой статус и свое будущее? Даже не смей мне говорить о какой-то вере в меня так, будто это само собой разумеющееся!

Розваль: . Похоже, на се-е-егодня доста-а-аточно.

Рядом с Субару, который громогласно изливал свой гнев, Розваль выглядел на редкость спокойным и рассудительным. Не сдерживая свои эмоции, Субару недовольно закричал и высунул свой язык.

Розваль: Ты больше не в состоянии вести адекватный разговор. Все, что я бы тебе не сказал, воспримется тобой лишь отрицательно — даже если мне есть, что еще сказать. Похоже, что твое доверие ко мне упало. как жа-а-аль. Тем не менее, сегодняшняя ночь посвящена не мне или тебе, а госпоже Эмилии.

Субару: Без тебя это знаю. Хватит заливать всякую чушь — наш разговор опять движется по бессмысленному кругу.

Вне зависимости от того, чего хотел Розваль, Эмилия и только Эмилия была самой важной темой здесь. И для Субару было важно, чтобы она смогла заслужить право стать полноценным представителем фракции Розваля, завоевав доверие каждого его члена. Пусть его воротит лишь от самой мысли, что придется действовать согласно плану этого клоуна — но если участие в испытаниях принесет пользу, значит, тому оно и быть. К тому же, он сам мог повлиять на результат.

Субару: Несмотря на все, я буду сдерживать свои эмоции, чтобы не испортить план. Так что, уверен, ты будешь рад.

Сжав зубы, он недовольно посмотрел на Розваля. Выражение того сразу же поменялось на глазах.

Розваль: Все-таки, ты, действительно, мой самый верный союзник.

Субару: . Мразь, надеюсь, для тебя уже припасено место в аду.

Розваль: Так и есть. Гореть мне в вечном пламени. Но пока я жив, то выжму все, что можно из этого мира.

Услышав его слова, Субару выразительно посмотрел на него, молча повернулся и покинул комнату. Говорить было больше не о чем — вряд ли Розваль желал поделиться с ним еще какой-то информацией. Однако.

Розваль: . Все, как ты и предвидел.

Сжав кулаки, Субару шел по ночному храму и затерялся в своих мыслях. Завтра Розваль даст Эмилии возможность бросить вызов испытаниями на глазах жителей деревни Алам и храма — и это изменит мнение о ней в их глазах. К сожалению, Субару не знал сути самих испытаний — об этом Розваль не упомянул. Кто знает, какие мучения ей придется вытерпеть там. сколько раз ее сердце разобьется. Но ведь Субару хочет, чтоб она по-прежнему оставалась такой светлой и невинной. Посему.

Субару: Я сделаю это сам. И защищу ее, чего бы мне это не стоило. Я защищу Эмилию.

Если его видения в гробнице были не простым сном, значит, у него есть право самому принять участие в испытаниях. Может быть, это произошло лишь по простой прихоти ведьмы, но так он сможет изменить замысел Розваля. Возможно, тот, действительно, и был их союзником, но неизвестно, чего им будет стоить такая победа. Поэтому всю боль, мучения и слезы — все это он заберет себе.

Субару: Ведь именно для этого я пришел в этот храм.

Он резко выбросил руку в сторону ярко светящейся луны — будто пытаясь дотянуться до чего-то. В его глазах ему представилась его любимая сереброволосая девушка — которую он поклялся защитить любой ценой.

Рам: . Действительно ли это правильно?

Наблюдавшая до этого препирания Субару и Розваля, она тихо задала вопрос своему хозяину — который слегка покачал головой.

Розваль: Это то, чего я ожида-а-ал. Все, чего бы наш молодой друг бы не придумал себе, не так стра-а-ашно.

Рам: Мне нужно говорить, что я вижу вашу ложь насквозь?

Розваль: Я рад, что ты беспокоишься обо мне, но все, что я сказал, действительно, было необходимо. Разве я похож на кого-то, кто бы бездумно говорил такие вещи?

Вместо ответа Рам отвернула глаза от своего господина и подняла бумаги, которые разбросались во время их беседы. Нащупав что-то, она вытащила.

Рам: Господин Розваль, это.

Розваль: Ах, про-о-ости. Если бы Субару увидел это, ситуация стала бы слегка напряже-е-еной. Но стоит быть аккуратенее — все-таки, это далеко не самая добрая вещь.

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

Розваль: Я проверил возможность Эмилии участвовать в испытаниях и подогрел боевой дух Субару. Да начнутся завтра вечером испытания! Рам, как думаешь, что-о-о случится?

Рам: Все, что вам будет угодно, господин. К тому же, разве вы не знаете этого сами?

Розваль: Все-таки, последние события были для нас большой удачо-о-ой. По сравнению с тем слабым шоу, которое пытался сделать культ ведьмы, у нас все будет гораздо лучше — пусть и не так, как бы и хотелось. Как думаешь, многое ли понял Субару из нашего разговора?

Глубоко вздохнув, Рам повернулась в сторону Розваля — и затем нерешительно произнесла.

Рам: И как много из того, что вы сказали Барусу, было правд.

Розваль: Где-то я слегка приукрасил. Но, в основном, все было правдой. Неприятно, конечно, было увидеть его реакцию — но это было необходимо. Он должен был знать то, что я ему ска-а-азал.

Как будто ставя точку над разговором, Розваль помахал рукой Рам и сказал.

Розваль: Все-таки, мне так не нравится, как мой напарник так плохо думает обо мне. Да и вообще, он так по-детски враждебно смотрет на все вокруг. прям как я.

Произнес он эти слова, тихо смеясь. В руках он очень-очень осторожно держал небольшую книгу в черном переплете. Своими пальцами Розваль нежно касался обложки — будто гладя свою любимую.

Что может подстерегать новичков при работе с SQL Server

В свое время я зачитывался Рихтером и усиленно штудировал Шилдта. Думал, что буду заниматься разработкой под .NET, но судьба на первом месяце работы распорядилась иначе. Один из сотрудников неожиданно покинул проект, и во вновь образовавшуюся дыру докинули свежего людского материала. Именно тогда и началось мое знакомство с SQL Server.

С тех пор прошло чуть меньше 6 лет, и вспомнить можно многое. Про бывшего клиента Джозефа из Англии, который переосмыслил жизнь за время отпуска в Таиланде, и в моем скайпе стал подписываться Жозефиной. Про веселых соседей по офису, с которыми приходилось сидеть в одной комнате: один страдал от аллергии на свежий воздух, а другой маялся от неразделенной любви к С++, дополняя это аллергией на солнечный свет. Чего только не было. Один раз по команде свыше пришлось на время стать Александром, отцом двух детей, и изображать из себя обросшего скилами сениора по JS. Но самый лютый треш, наверное, связан с историей про резиновую утку-пищалку. Один коллега снимал ею стресс и, однажды, в порыве эмоций, отгрыз ей голову. С тех пор уточка потеряла прежний лоск и вскоре была заменена на мячик, который он пытался иногда грызть. увы, уже безуспешно.

К чему это было рассказано? Если хотите посвятить свою жизнь работе с базами данных, то первое чему нужно научиться — это стрессоустойчивости. Второе — взять на вооружение несколько правил при написании запросов на T-SQL, которые многие из начинающих разработчиков не знают или игнорируют, а потом сидят и ломают голову: «Почему что-то не работает?»

Каждая невеста должна знать:  Свадьба в стиле Jo Malone парфюмерия, музыка и бондиана

NOT IN vs NULL

Долго думал, с какого примера стоило бы начать. Бесспорный лидер среди вопросов на собеседовании Junior DB Developer — конструкция NOT IN.

Например, нужно написать запрос, который вернет всем записи из первой таблицы, которых нет во второй. Очень часто начинающие разработчики не заморачиваются и используют NOT IN:

Запрос вернул нам двойку. Давайте теперь во вторую таблицу добавим еще одно значение — NULL:

При выполнении мы не получим никаких результатов. Поменяем NOT IN на IN и сможем увидеть какую-то магию — IN работает, а NOT IN отказывается. Это первое, что нужно «понять и простить» при работе с SQL Server, который при операции сравнения руководствуется третичной логикой: TRUE, FALSE, UNKNOWN.

При выполнении SQL Server интерпретирует условие IN:

a IN (1, NULL) === a=1 OR a=NULL

a NOT IN (1, NULL) === a<>1 AND a<>NULL

При сравнении любого значения с NULL возвращается UNKNOWN. 1=NULL, NULL=NULL. Результат будет один — UNKNOWN. А поскольку у нас в условии используется оператор AND, то все выражение вернет неопределенное значение.

Скажу честно, написано реально скучно. Но важно понимать, что такая ситуация встречается достаточно часто. Хороший пример из жизни: раньше колонка была NOT NULL, потом какой-то добрый человек разрешил записывать в нее NULL значение. Итог: у клиента перестает работать отчет после того, как в таблицу попадет хотя бы одно NULL значение.

Что делать? Можно явно отбрасывать NULL значения:

Можно использовать EXCEPT:

Если нет желания много думать, то проще использовать NOT EXISTS:

Какой вариант запроса более оптимальный? Чуточку предпочтительнее выглядит последний вариант с NOT EXISTS, который генерирует более оптимальный predicate pushdown оператор при доступе к данным из второй таблицы.

Date Format

Еще часто спотыкаются на различных нюансах с типами данных. Например, нужно получить текущее время. Выполнили функцию GETDATE. Скопировали результат и вставили его в запрос. Корректно ли так делать? Дата задается строковой константой, и в некоторой степени SQL Server позволяет вольности при ее написании:

Все значения однозначно интерпретируются:

И это не будет приводить к проблемам до тех пор, пока бизнес-логику не начнут выполнять на другом сервере, на котором настройки могут отличаться:

Первый вариант может привести к неверному толкованию даты:

Более того, подобный код может привести к ошибке. Например, нам нужно вставить данные в таблицу. На тестовом сервере все прекрасно работает:

А у клиента, из-за разницы настройках сервера, вот такой запрос будет приводить к проблемам:

Msg 242, Level 16, State 3, Line 28
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.

Так в каком же формате задавать константы для дат? Давайте посмотрим на еще один пример:

В зависимости от установленного языка, константы также могут по-разному интерпретироваться:

И напрашивается вывод использовать последние два варианта. Сразу скажу, что задавать месяц явно — это хорошая возможность наткнуться на «же не манж па сис жур» ошибку:

Msg 241, Level 16, State 1, Line 29
Échec de la conversion de la date et/ou de l’heure à partir d’une chaîne de caractères.

Итого — остается последний вариант. Если хотите, чтобы константы с датами однозначно толковались в системе вне зависимости от настроек и фазы Луны, то указывайте их в формате ISO (yyyyMMdd) без всяких тильд, кавычек и слешей.

Еще стоит обратить внимание на различие в поведении некоторых типов данных:

Тип DATE, в отличие от DATETIME, корректно интерпретируется при различных настройках на сервере:

Но нужно ли держать этот нюанс в голове? Вряд ли. Главное помните, что задавать даты нужно в формате ISO, остальное уже от лукавого.

Date Filter

Далее рассмотрим, как фильтровать эффективно данные. Почему-то на DATETIME столбцы приходится наибольшее число костылей, так что с этого типа данных мы и начнем:

Теперь попробуем узнать, сколько строк вернет запрос за определенный день:

Запрос вернет 0. Почему? При построении плана SQL Server пытается преобразовать строковую константу к типу данных столбца, по которому идет фильтрация:

Есть правильные и неправильные варианты вывести требуемые данные. Например, обрезать время:

Или задать диапазон:

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

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

И опять же, последний вариант более приемлем, чем все остальные. Кроме того, всегда можно сделать вычисляемое поле и создать на его основе индекс:

В сравнении с прошлым запросом разница в логических чтениях будет очень существенная:

Table ‘DatabaseLog’. Scan count 1, logical reads 782, .
Table ‘DatabaseLog’. Scan count 1, logical reads 7, .

Но суть не в этом. Как я уже говорил, любые вычисления на индексных полях снижают производительность и приводят к увеличению логических чтений:

Table ‘Person’. Scan count 1, logical reads 67, .
Table ‘Person’. Scan count 0, logical reads 3, .

Если взглянуть на планы выполнения, то в первом случае SQL Server приходится выполнить IndexScan:

Во втором же случае мы увидим IndexSeek:

Convert Implicit

Теперь поговорим про такую редиску, как convert implicit, но для начала пример:

Смотрим на планы выполнения:

В первом случае — предупреждение и IndexScan, во втором — все хорошо. Что произошло? Столбец NationalIDNumber имеет тип данных NVARCHAR(15). Константу, по значению которой необходимо отфильтровать данные, мы передаем как INT. В итоге получаем неявное преобразование типов, которые может снижать производительность.

Решение достаточно простое — нужно контролировать, чтобы типы данных при сравнении совпадали. Особенно это актуально при использовании EntityFramework.

Что еще нужно знать? Даже когда у вас есть покрывающий индекс, еще не факт что он будет эффективно использоваться. Классический пример: вывести все строки, которые начинаются с .

Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 4, .

Планы выполнения, по которым можно быстро найти победителя:

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

Но что если нужно найти все вхождения подстроки в строку? Это задачка уже явно интереснее:

Но сначала нам нужно узнать много чего занимательного про строки и их свойства.

Первое, что нужно помнить — строки бывают UNICODE и ANSI. Для первых предусмотрены типы данных NVARCHAR/NCHAR (по 2 байта на символ). Для хранения ANSI строк — VARCHAR/CHAR (1 байт — 1 символ). Есть еще TEXT/NTEXT, но про них лучше забыть изначально. И вроде бы на этом можно было закончить, но нет.

Если в запросе задается юникодная константа, то перед ней нужно обязательно ставить символ N. Чтобы показать разницу, достаточно простого запроса:

Если не указывать N перед константой, то SQL Server будет пытаться искать подходящий символ в ANSI кодировке. Если не найдет, то подставит знак вопроса.

COLLATE

Вспомнился один очень интересный пример, который любят спрашивать при собеседовании на позицию Middle/Senior DB Developer. Вернет ли данные следующий запрос?

И да. и нет. Тут как повезет. Обычно я так отвечаю.

Почему такой неоднозначный ответ? Во-первых, перед строковым константами не стоит N, поэтому они будут толковаться как ANSI. Второе — очень многое зависит от текущего COLLATE, который является набором правил при сортировки и сравнении строковых данных.

При таком COLLATE вместо кириллицы мы получим знаки вопросов, потому что символы знака вопроса равны между собой:

Стоит нам поменять COLLATE на какой-нибудь другой:

И запрос уже не вернет ничего, потому что кириллица будет правильно интерпретироваться. Поэтому мораль тут простая: если строковая константа должна принимать UNICODE, то не надо лениться ставить N перед ней. Есть еще и обратная сторона медали, когда N лепиться везде, где можно, и оптимизатору приходится выполнять преобразования типов, которые, как я уже говорил, приводят к неоптимальным планам выполнения.

Что еще я забыл упомянуть про строки? Еще один хороший вопрос из цикла «давайте проведем собеседование»:

Эти строки равны? И да. и нет. Опять ответил бы я.

Если мы хотим однозначного сравнения, то нужно явно указывать COLLATE:

Потому что COLLATE могут быть как регистрозависимыми (CS), так и не учитывать регистр (CI) при сравнении и сортировке строк. Разные COLLATE у клиента и на тестовой базе — это потенциальный источник не только логических ошибок в бизнес-логике. Еще веселее, когда COLLATE между целевой базой и tempdb не совпадают.

Создадим базу с COLLATE, отличным от дефолтного:

При создании таблицы COLLATE наследуется от базы данных. Единственное отличие — для первой временной таблицы, для которой мы явно определяем структуру без указания COLLATE. В этом случае она наследует COLLATE от базы tempdb.

Сейчас остановимся на нашем примере, потому что если COLLATE не совпадают — это может привести к потенциальным проблемам. Например, данные не будут правильно фильтроваться из-за того, что COLLATE может не учитывать регистр:

Либо SQL Server будет ругаться на невозможность соединения таблиц из-за различающихся COLLATE:

Последний пример очень часто встречается. На тестовом сервере все идеально, а когда развернули бэкап на сервере клиента, то получаем ошибку:

Msg 468, Level 16, State 9, Line 93
Cannot resolve the collation conflict between «Albanian_100_CS_AS» and «Cyrillic_General_CI_AS» in the equal to operation.

После чего приходится везде делать костыли:

BINARY COLLATE

Теперь, когда «ложка дегтя» пройдена, посмотрим, как можно использовать COLLATE с пользой для себя. Помните пример про поиск подстроки в строке?

Данный запрос можно оптимизировать и сократить его выполнения. Но для того, чтобы была видна разница, нам нужно сгенерировать большую таблицу:

Создадим вычисляемые столбцы с бинарными COLLATE, не забыв при этом создать индексы:

Далее выполняем фильтрацию:

И можем увидеть результаты выполнения, которые приятно удивят:

SQL Server Execution Times:
CPU time = 250 ms, elapsed time = 254 ms.

SQL Server Execution Times:
CPU time = 235 ms, elapsed time = 255 ms.

SQL Server Execution Times:
CPU time = 15 ms, elapsed time = 17 ms.

SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 17 ms.

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

ISNULL и COALESCE

Идем дальше. Что еще потенциально интересного? Есть две функции: ISNULL и COALESCE. С одной стороны все просто — если первый оператор NULL, то вернуть второй оператор или следующий, если мы говорим про COALESCE. С другой стороны, есть коварное различие между ними. Что вернут эти функции?

Ответ и вправду не очень очевидный:

Почему? Функция ISNULL преобразует к наименьшему типу из двух операндов. COALESCE преобразует к наибольшему типу. Вот мы и получаем такую радость, над которой я в первый раз очень долго просидел в попытках понять, «что не так».

Еще интереснее, когда сталкиваешься с математикой на SQL Server. Вроде бы разницы не должно быть:

Но по факту оказывается, что она есть — все зависит от того, какие данные участвуют в запросе. Если целочисленные, то и результат будет целочисленным:

Еще интересный пример, который часто встречается на собеседованиях в том или ином виде:

Что вернет запрос? COUNT(*)/COUNT(1) вернет общее число строк. COUNT по столбцу вернет количество не NULL строк. Если добавить DISTINCT, то количество уникальных значений, которые не NULL.

Интереснее с подсчетом среднего. Операция AVG раскладывается оптимизатором на SUM и COUNT. И тут мы вспомним про пример выше. Если значения целочисленные, то какой будет результат? Целочисленный. Об этом часто забывают.

UNION ALL

Еще стоит упомянуть про UNION с приставкой ALL. Тут все просто: если мы знаем, что данные не пересекаются, и нас не волнуют дубликаты, то, с точки зрения производительности, предпочтительнее использовать UNION ALL. Если нужно убрать дублирование, то смело используем UNION.

Но тут еще важно знать об интересном различии между этими двумя конструкциями: UNION выполняется параллельно, а UNION ALL — последовательно. И это не относится к параллельным планам, просто это такая особенность доступа к данным, которая может помочь при оптимизации.

Предположим, нам нужно вернуть 1 строку, исходя из разного набора условий:

Тогда за счет использования OR в условии у нас будет IndexScan:

Table ‘Address’. Scan count 1, logical reads 6, .

Перепишем запрос с использованием UNION ALL:

И посмотрим на план выполнения:

После выполнения первого подзапроса, SQL Server смотрит, что вернулась одна строка, которой достаточно, чтобы вернуть результат, и далее не продолжает искать по второму условию.

Table ‘Worktable’. Scan count 0, logical reads 0, .
Table ‘Address’. Scan count 1, logical reads 3, .

Scalar func

О чем я еще забыл упомянуть? Специально для любителей ООП. Не используйте скалярные функции в запросах на T-SQL, которые оперируют большим числом строк.

Вот пример из жизни, которым я когда-то страдал, когда еще не знал о потенциальных минусах скалярных функций:

Запросы возвращают идентичные данные:

Но за счет, того что каждый вызов функции ресурсоемкий, получаем вот такую разницу:

SQL Server Execution Times:
CPU time = 62 ms, elapsed time = 58 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1 ms.

Кроме того, использование скалярных функций в запросе мешает SQL Server строить параллельные планы выполнения, что при больших объёмах данных может существенно подкосить производительность.

CURSORs

И самое важное, что нужно понимать при работе с SQL Server. Не используйте курсоры для модификации данных. Или используйте по минимуму их, когда других вариантов уже не остается. Часто можно встретить такой вот код:

Который лучше переписать вот так:

Приводить время выполнения и число логических чтений не стоит, но поверьте, разница действительно есть. Как вариант, просто расскажу про недавний пример из жизни. Встретил скрипт, в котором было два вложенных курсора. При выполнении данный код приводил к таймауту на клиенте, а всего он выполнялся примерно 38 секунд. Переписал запрос без использования курсоров — 600 мс.

Что можно сказать для послесловия? Это мой первый опыт подготовки материала для DOU, и, наверное, вышло немного сумбурно. Однако, надеюсь, все, что здесь написано, будет кому-то полезным при написании запросов на T-SQL.

Что может подстерегать новичков при работе с SQL Server

В свое время я зачитывался Рихтером и усиленно штудировал Шилдта. Думал, что буду заниматься разработкой под .NET, но судьба на первом месяце работы распорядилась иначе. Один из сотрудников неожиданно покинул проект, и во вновь образовавшуюся дыру докинули свежего людского материала. Именно тогда и началось мое знакомство с SQL Server.

С тех пор прошло чуть меньше 6 лет, и вспомнить можно многое. Про бывшего клиента Джозефа из Англии, который переосмыслил жизнь за время отпуска в Таиланде, и в моем скайпе стал подписываться Жозефиной. Про веселых соседей по офису, с которыми приходилось сидеть в одной комнате: один страдал от аллергии на свежий воздух, а другой маялся от неразделенной любви к С++, дополняя это аллергией на солнечный свет. Чего только не было. Один раз по команде свыше пришлось на время стать Александром, отцом двух детей, и изображать из себя обросшего скилами сениора по JS. Но самый лютый треш, наверное, связан с историей про резиновую утку-пищалку. Один коллега снимал ею стресс и, однажды, в порыве эмоций, отгрыз ей голову. С тех пор уточка потеряла прежний лоск и вскоре была заменена на мячик, который он пытался иногда грызть. увы, уже безуспешно.

К чему это было рассказано? Если хотите посвятить свою жизнь работе с базами данных, то первое чему нужно научиться — это стрессоустойчивости. Второе — взять на вооружение несколько правил при написании запросов на T-SQL, которые многие из начинающих разработчиков не знают или игнорируют, а потом сидят и ломают голову: «Почему что-то не работает?»

Каждая невеста должна знать:  Синие акценты декора. Фотоидеи

NOT IN vs NULL

Долго думал, с какого примера стоило бы начать. Бесспорный лидер среди вопросов на собеседовании Junior DB Developer — конструкция NOT IN.

Например, нужно написать запрос, который вернет всем записи из первой таблицы, которых нет во второй. Очень часто начинающие разработчики не заморачиваются и используют NOT IN:

Запрос вернул нам двойку. Давайте теперь во вторую таблицу добавим еще одно значение — NULL:

При выполнении мы не получим никаких результатов. Поменяем NOT IN на IN и сможем увидеть какую-то магию — IN работает, а NOT IN отказывается. Это первое, что нужно «понять и простить» при работе с SQL Server, который при операции сравнения руководствуется третичной логикой: TRUE, FALSE, UNKNOWN.

При выполнении SQL Server интерпретирует условие IN:

a IN (1, NULL) === a=1 OR a=NULL

a NOT IN (1, NULL) === a<>1 AND a<>NULL

При сравнении любого значения с NULL возвращается UNKNOWN. 1=NULL, NULL=NULL. Результат будет один — UNKNOWN. А поскольку у нас в условии используется оператор AND, то все выражение вернет неопределенное значение.

Скажу честно, написано реально скучно. Но важно понимать, что такая ситуация встречается достаточно часто. Хороший пример из жизни: раньше колонка была NOT NULL, потом какой-то добрый человек разрешил записывать в нее NULL значение. Итог: у клиента перестает работать отчет после того, как в таблицу попадет хотя бы одно NULL значение.

Что делать? Можно явно отбрасывать NULL значения:

Можно использовать EXCEPT:

Если нет желания много думать, то проще использовать NOT EXISTS:

Какой вариант запроса более оптимальный? Чуточку предпочтительнее выглядит последний вариант с NOT EXISTS, который генерирует более оптимальный predicate pushdown оператор при доступе к данным из второй таблицы.

Date Format

Еще часто спотыкаются на различных нюансах с типами данных. Например, нужно получить текущее время. Выполнили функцию GETDATE. Скопировали результат и вставили его в запрос. Корректно ли так делать? Дата задается строковой константой, и в некоторой степени SQL Server позволяет вольности при ее написании:

Все значения однозначно интерпретируются:

И это не будет приводить к проблемам до тех пор, пока бизнес-логику не начнут выполнять на другом сервере, на котором настройки могут отличаться:

Первый вариант может привести к неверному толкованию даты:

Более того, подобный код может привести к ошибке. Например, нам нужно вставить данные в таблицу. На тестовом сервере все прекрасно работает:

А у клиента, из-за разницы настройках сервера, вот такой запрос будет приводить к проблемам:

Msg 242, Level 16, State 3, Line 28
The conversion of a varchar data type to a datetime data type resulted in an out-of-range value.

Так в каком же формате задавать константы для дат? Давайте посмотрим на еще один пример:

В зависимости от установленного языка, константы также могут по-разному интерпретироваться:

И напрашивается вывод использовать последние два варианта. Сразу скажу, что задавать месяц явно — это хорошая возможность наткнуться на «же не манж па сис жур» ошибку:

Msg 241, Level 16, State 1, Line 29
Échec de la conversion de la date et/ou de l’heure à partir d’une chaîne de caractères.

Итого — остается последний вариант. Если хотите, чтобы константы с датами однозначно толковались в системе вне зависимости от настроек и фазы Луны, то указывайте их в формате ISO (yyyyMMdd) без всяких тильд, кавычек и слешей.

Еще стоит обратить внимание на различие в поведении некоторых типов данных:

Тип DATE, в отличие от DATETIME, корректно интерпретируется при различных настройках на сервере:

Но нужно ли держать этот нюанс в голове? Вряд ли. Главное помните, что задавать даты нужно в формате ISO, остальное уже от лукавого.

Date Filter

Далее рассмотрим, как фильтровать эффективно данные. Почему-то на DATETIME столбцы приходится наибольшее число костылей, так что с этого типа данных мы и начнем:

Теперь попробуем узнать, сколько строк вернет запрос за определенный день:

Запрос вернет 0. Почему? При построении плана SQL Server пытается преобразовать строковую константу к типу данных столбца, по которому идет фильтрация:

Есть правильные и неправильные варианты вывести требуемые данные. Например, обрезать время:

Или задать диапазон:

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

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

И опять же, последний вариант более приемлем, чем все остальные. Кроме того, всегда можно сделать вычисляемое поле и создать на его основе индекс:

В сравнении с прошлым запросом разница в логических чтениях будет очень существенная:

Table ‘DatabaseLog’. Scan count 1, logical reads 782, .
Table ‘DatabaseLog’. Scan count 1, logical reads 7, .

Но суть не в этом. Как я уже говорил, любые вычисления на индексных полях снижают производительность и приводят к увеличению логических чтений:

Table ‘Person’. Scan count 1, logical reads 67, .
Table ‘Person’. Scan count 0, logical reads 3, .

Если взглянуть на планы выполнения, то в первом случае SQL Server приходится выполнить IndexScan:

Во втором же случае мы увидим IndexSeek:

Convert Implicit

Теперь поговорим про такую редиску, как convert implicit, но для начала пример:

Смотрим на планы выполнения:

В первом случае — предупреждение и IndexScan, во втором — все хорошо. Что произошло? Столбец NationalIDNumber имеет тип данных NVARCHAR(15). Константу, по значению которой необходимо отфильтровать данные, мы передаем как INT. В итоге получаем неявное преобразование типов, которые может снижать производительность.

Решение достаточно простое — нужно контролировать, чтобы типы данных при сравнении совпадали. Особенно это актуально при использовании EntityFramework.

Что еще нужно знать? Даже когда у вас есть покрывающий индекс, еще не факт что он будет эффективно использоваться. Классический пример: вывести все строки, которые начинаются с .

Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 216, .
Table ‘Address’. Scan count 1, logical reads 4, .

Планы выполнения, по которым можно быстро найти победителя:

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

Но что если нужно найти все вхождения подстроки в строку? Это задачка уже явно интереснее:

Но сначала нам нужно узнать много чего занимательного про строки и их свойства.

Первое, что нужно помнить — строки бывают UNICODE и ANSI. Для первых предусмотрены типы данных NVARCHAR/NCHAR (по 2 байта на символ). Для хранения ANSI строк — VARCHAR/CHAR (1 байт — 1 символ). Есть еще TEXT/NTEXT, но про них лучше забыть изначально. И вроде бы на этом можно было закончить, но нет.

Если в запросе задается юникодная константа, то перед ней нужно обязательно ставить символ N. Чтобы показать разницу, достаточно простого запроса:

Если не указывать N перед константой, то SQL Server будет пытаться искать подходящий символ в ANSI кодировке. Если не найдет, то подставит знак вопроса.

COLLATE

Вспомнился один очень интересный пример, который любят спрашивать при собеседовании на позицию Middle/Senior DB Developer. Вернет ли данные следующий запрос?

И да. и нет. Тут как повезет. Обычно я так отвечаю.

Почему такой неоднозначный ответ? Во-первых, перед строковым константами не стоит N, поэтому они будут толковаться как ANSI. Второе — очень многое зависит от текущего COLLATE, который является набором правил при сортировки и сравнении строковых данных.

При таком COLLATE вместо кириллицы мы получим знаки вопросов, потому что символы знака вопроса равны между собой:

Стоит нам поменять COLLATE на какой-нибудь другой:

И запрос уже не вернет ничего, потому что кириллица будет правильно интерпретироваться. Поэтому мораль тут простая: если строковая константа должна принимать UNICODE, то не надо лениться ставить N перед ней. Есть еще и обратная сторона медали, когда N лепиться везде, где можно, и оптимизатору приходится выполнять преобразования типов, которые, как я уже говорил, приводят к неоптимальным планам выполнения.

Что еще я забыл упомянуть про строки? Еще один хороший вопрос из цикла «давайте проведем собеседование»:

Эти строки равны? И да. и нет. Опять ответил бы я.

Если мы хотим однозначного сравнения, то нужно явно указывать COLLATE:

Потому что COLLATE могут быть как регистрозависимыми (CS), так и не учитывать регистр (CI) при сравнении и сортировке строк. Разные COLLATE у клиента и на тестовой базе — это потенциальный источник не только логических ошибок в бизнес-логике. Еще веселее, когда COLLATE между целевой базой и tempdb не совпадают.

Создадим базу с COLLATE, отличным от дефолтного:

При создании таблицы COLLATE наследуется от базы данных. Единственное отличие — для первой временной таблицы, для которой мы явно определяем структуру без указания COLLATE. В этом случае она наследует COLLATE от базы tempdb.

Сейчас остановимся на нашем примере, потому что если COLLATE не совпадают — это может привести к потенциальным проблемам. Например, данные не будут правильно фильтроваться из-за того, что COLLATE может не учитывать регистр:

Либо SQL Server будет ругаться на невозможность соединения таблиц из-за различающихся COLLATE:

Последний пример очень часто встречается. На тестовом сервере все идеально, а когда развернули бэкап на сервере клиента, то получаем ошибку:

Msg 468, Level 16, State 9, Line 93
Cannot resolve the collation conflict between «Albanian_100_CS_AS» and «Cyrillic_General_CI_AS» in the equal to operation.

После чего приходится везде делать костыли:

BINARY COLLATE

Теперь, когда «ложка дегтя» пройдена, посмотрим, как можно использовать COLLATE с пользой для себя. Помните пример про поиск подстроки в строке?

Данный запрос можно оптимизировать и сократить его выполнения. Но для того, чтобы была видна разница, нам нужно сгенерировать большую таблицу:

Создадим вычисляемые столбцы с бинарными COLLATE, не забыв при этом создать индексы:

Далее выполняем фильтрацию:

И можем увидеть результаты выполнения, которые приятно удивят:

SQL Server Execution Times:
CPU time = 250 ms, elapsed time = 254 ms.

SQL Server Execution Times:
CPU time = 235 ms, elapsed time = 255 ms.

SQL Server Execution Times:
CPU time = 15 ms, elapsed time = 17 ms.

SQL Server Execution Times:
CPU time = 16 ms, elapsed time = 17 ms.

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

ISNULL и COALESCE

Идем дальше. Что еще потенциально интересного? Есть две функции: ISNULL и COALESCE. С одной стороны все просто — если первый оператор NULL, то вернуть второй оператор или следующий, если мы говорим про COALESCE. С другой стороны, есть коварное различие между ними. Что вернут эти функции?

Ответ и вправду не очень очевидный:

Почему? Функция ISNULL преобразует к наименьшему типу из двух операндов. COALESCE преобразует к наибольшему типу. Вот мы и получаем такую радость, над которой я в первый раз очень долго просидел в попытках понять, «что не так».

Еще интереснее, когда сталкиваешься с математикой на SQL Server. Вроде бы разницы не должно быть:

Но по факту оказывается, что она есть — все зависит от того, какие данные участвуют в запросе. Если целочисленные, то и результат будет целочисленным:

Еще интересный пример, который часто встречается на собеседованиях в том или ином виде:

Что вернет запрос? COUNT(*)/COUNT(1) вернет общее число строк. COUNT по столбцу вернет количество не NULL строк. Если добавить DISTINCT, то количество уникальных значений, которые не NULL.

Интереснее с подсчетом среднего. Операция AVG раскладывается оптимизатором на SUM и COUNT. И тут мы вспомним про пример выше. Если значения целочисленные, то какой будет результат? Целочисленный. Об этом часто забывают.

UNION ALL

Еще стоит упомянуть про UNION с приставкой ALL. Тут все просто: если мы знаем, что данные не пересекаются, и нас не волнуют дубликаты, то, с точки зрения производительности, предпочтительнее использовать UNION ALL. Если нужно убрать дублирование, то смело используем UNION.

Но тут еще важно знать об интересном различии между этими двумя конструкциями: UNION выполняется параллельно, а UNION ALL — последовательно. И это не относится к параллельным планам, просто это такая особенность доступа к данным, которая может помочь при оптимизации.

Предположим, нам нужно вернуть 1 строку, исходя из разного набора условий:

Тогда за счет использования OR в условии у нас будет IndexScan:

Table ‘Address’. Scan count 1, logical reads 6, .

Перепишем запрос с использованием UNION ALL:

И посмотрим на план выполнения:

После выполнения первого подзапроса, SQL Server смотрит, что вернулась одна строка, которой достаточно, чтобы вернуть результат, и далее не продолжает искать по второму условию.

Table ‘Worktable’. Scan count 0, logical reads 0, .
Table ‘Address’. Scan count 1, logical reads 3, .

Scalar func

О чем я еще забыл упомянуть? Специально для любителей ООП. Не используйте скалярные функции в запросах на T-SQL, которые оперируют большим числом строк.

Вот пример из жизни, которым я когда-то страдал, когда еще не знал о потенциальных минусах скалярных функций:

Запросы возвращают идентичные данные:

Но за счет, того что каждый вызов функции ресурсоемкий, получаем вот такую разницу:

SQL Server Execution Times:
CPU time = 62 ms, elapsed time = 58 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 1 ms.

Кроме того, использование скалярных функций в запросе мешает SQL Server строить параллельные планы выполнения, что при больших объёмах данных может существенно подкосить производительность.

CURSORs

И самое важное, что нужно понимать при работе с SQL Server. Не используйте курсоры для модификации данных. Или используйте по минимуму их, когда других вариантов уже не остается. Часто можно встретить такой вот код:

Который лучше переписать вот так:

Приводить время выполнения и число логических чтений не стоит, но поверьте, разница действительно есть. Как вариант, просто расскажу про недавний пример из жизни. Встретил скрипт, в котором было два вложенных курсора. При выполнении данный код приводил к таймауту на клиенте, а всего он выполнялся примерно 38 секунд. Переписал запрос без использования курсоров — 600 мс.

Что можно сказать для послесловия? Это мой первый опыт подготовки материала для DOU, и, наверное, вышло немного сумбурно. Однако, надеюсь, все, что здесь написано, будет кому-то полезным при написании запросов на T-SQL.

Понравилась статья? Поделиться с друзьями:
Организация и планирование свадьбы самостоятельно