Как избежать ошибки «делить на ноль» в SQL?


363

У меня есть это сообщение об ошибке:

Msg 8134, Уровень 16, Состояние 1, Строка 1 При обнаружении ошибки деления на ноль.

Каков наилучший способ написания кода SQL, чтобы я больше никогда не видел это сообщение об ошибке?

Я мог бы сделать одно из следующего:

  • Добавьте предложение where, чтобы мой делитель никогда не был равен нулю

Или

  • Я мог бы добавить описание случая, чтобы был специальный режим для нуля.

Это лучший способ использовать NULLIFпредложение?

Есть ли лучший способ, или как это можно осуществить?


7
Возможно, некоторые проверки данных в порядке.
Энтони

Ответы:


646

Чтобы избежать ошибки «Деление на ноль», мы запрограммировали ее так:

Select Case when divisor=0 then null
Else dividend / divisor
End ,,,

Но вот гораздо лучший способ сделать это:

Select dividend / NULLIF(divisor, 0) ...

Теперь единственная проблема - вспомнить бит NullIf, если я использую клавишу "/".


13
Гораздо более приятный способ сделать это «Выбрать делимое / ноль, если делитель равен NULL.
Андерсон

8
@ Андерсон Это совсем не так. Вы уверены, что случайно не использовали IsNullвместо NullIf? Попробуй сам! SELECT Value,1/NullIf(Value,0)FROM(VALUES(0),(5.0),(NULL))x(Value);Если вы не имеете в виду "breaks", вы подразумеваете, что возвращает NULL? Вы можете преобразовать это в то, что вы хотите с помощью IsNullили Coalesce.
ErikE

1
@ErikE, это правда ... попробуйте запустить ... select 1 / nullif (null, 0) ... you get "Тип первого аргумента для NULLIF не может быть константой NULL, поскольку тип первого аргумента имеет быть известным. " Обработайте это с помощью "coalesce (FieldName, 0)" ... например, выберите 1 / nullif (coalesce (null, 0), 0)
Джон Джозеф

1
@JohnJoseph Я не могу сказать, согласны ли вы со мной или спорите со мной.
ErikE

2
@JohnJoseph Посмотрите ближе на ошибку, которую вы получили. Да, SELECT 1 / NULLIF(NULL, 0)не удается, но это потому, что NULLIF()нужно знать тип данных первого аргумента. Это измененный пример работает нормально: SELECT 1 / NULLIF(CAST(NULL AS INT), 0). В реальной жизни вы собираетесь указывать столбец таблицы, NULLIF()а не NULLконстанту. Поскольку столбцы таблицы имеют известные типы данных, это также работает отлично: SELECT 1 / NULLIF(SomeNullableColumn, 0) FROM SomeTable.
MarredCheese

180

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

SELECT COALESCE(dividend / NULLIF(divisor,0), 0) FROM sometable

Для каждого делителя, равного нулю, вы получите ноль в наборе результатов.


9
Некоторые тесты показывают, что COALESCE немного медленнее, чем ISNULL. Тем не менее, COALESCE в стандартах, поэтому является более портативным.
Павел Чернох

37
Если кто-то еще не сразу понял, почему это работает, NULLIF (d, 0) вернет NULL, если d равно 0. В SQL деление на NULL возвращает NULL. Объединение заменяет полученное значение NULL на 0.
GuiSim

16
@SQLGeorge Хотя я согласен с вашим аргументом, обратите внимание, что есть случаи, когда нас интересует, что статистически правильно, а не математически правильно. В некоторых случаях при использовании статистических функций 0 или даже 1 является приемлемым результатом, когда делитель равен нулю.
Атафуд

10
Может кто-нибудь объяснить мне, почему это плохо? Если я пытаюсь вычислить процент, а делитель равен нулю, я, безусловно, хочу, чтобы результат был равен нулю.
Тодд Шарп

10
Я думаю, что @ Джордж и @ Джеймс / Уилсон в корне неправильно понимают задаваемый вопрос. Конечно, существуют бизнес-приложения, в которых возвращение «0» является уместным, даже если это технически неверно с математической точки зрения.
Шон Бранчо,

66

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

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

SELECT club_id, males, females, males/females AS ratio
  FROM school_clubs;

Вы можете использовать функцию, NULLIFчтобы избежать деления на ноль. NULLIFсравнивает два выражения и возвращает ноль, если они равны, или первое выражение в противном случае.

Перепишите запрос как:

SELECT club_id, males, females, males/NULLIF(females, 0) AS ratio
  FROM school_clubs;

Любое число, разделенное на число, NULLдает NULLошибку.


6
Да, действительно, это ПУТЬ ЛУЧШЕ, чем тот другой ответ, который получил так много голосов. В вашем решении у вас есть по крайней мере NULL, что означает, что вы не можете предоставить правильный результат. Но если вы конвертируете результат из NULL в ноль, то вы просто получите неправильные и вводящие в заблуждение результаты.
Полиция SQL

8
Кстати, если вы хотите , чтобы вычислить / женское соотношение мужчин, то я предлагаю , чтобы лучше сравнить его с общим, как это: select males/(males+females), females/(males+females). Это даст вам процентное соотношение мужчин и женщин в клубе, например, 31% мужчин, 69% женщин.
Полиция SQL

44

Вы также можете сделать это в начале запроса:

SET ARITHABORT OFF 
SET ANSI_WARNINGS OFF

Так что если у вас есть что-то подобное, 100/0он вернет NULL. Я сделал это только для простых запросов, поэтому я не знаю, как это повлияет на более длинные / сложные запросы.


1
Работает для меня. В моем случае я должен использовать операцию деления в предложении WHERE. Я уверен, что нет делителя нуля, потому что, когда я комментирую ГДЕ, в результатах нет нулевых значений. Но каким-то образом оптимизатор запросов делит на ноль при фильтрации. SET ARITHABORT OFF и SET ANSI_WARNINGS OFF делают это - после 2 дней борьбы с делением на ноль в предложении WHERE. Спасибо!
huhu78

2
Это "чувствует" так грязно, но я люблю это! Это было необходимо в запросе, который выполняет агрегацию и использование оператора CASE, но это не вариант, потому что тогда мне пришлось добавить этот столбец в GROUP BY, что полностью изменило результаты. Сделав начальный запрос подвыбором, а затем выполнив GROUP BY для внешнего запроса, также изменятся результаты, так как в нем задействовано деление.
Эндрю Стейтц

1
Итак, мне все еще нравится это «решение», но, как многие из вас, вероятно, чувствовали, я чувствовал, что должен быть «более чистый» способ. Что если я забыл снова включить предупреждения? Или кто-то скрыл мой код (этого никогда не происходит, верно?) И не подумал о предупреждениях? Во всяком случае, видел другие ответы о NULLIF (). Я знал о NULLIF (), но не понимал, что деление на NULL возвращает NULL (я думал, что это будет ошибкой). Итак ... Я пошел со следующим: ISNULL ((SUM (foo) / NULLIF (SUM (bar), 0)), 0) AS Avg
Эндрю Стейтц

2
Я не знал этого решения. Я не уверен, что мне это нравится, но это может быть полезно знать когда-нибудь. Большое спасибо.
Хенрик Стаун Поулсен

1
Это самое простое решение, но учтите, что оно ухудшит производительность. От docs.microsoft.com/en-us/sql/t-sql/statements/… «Установка ARITHABORT в положение OFF может отрицательно повлиять на оптимизацию запросов, что приведет к проблемам с производительностью».
моно блейн

36

Вы можете, по крайней мере, остановить прерывание запроса с ошибкой и вернуть, NULLесли есть деление на ноль:

SELECT a / NULLIF(b, 0) FROM t 

Тем не менее, я бы НИКОГДА не конвертировал это в ноль, coalesceкак показано в другом ответе, получившем много голосов. Это совершенно неверно в математическом смысле и даже опасно, поскольку ваше приложение, скорее всего, даст неверные и вводящие в заблуждение результаты.


32

РЕДАКТИРОВАТЬ: я получил много отрицательных отзывов об этом в последнее время ... так что я решил добавить примечание, что этот ответ был написан до того, как вопрос подвергся его последнему редактированию, где возвращаемый ноль был выделен как опция ... что кажется очень приемлемым. Некоторые из моих ответов были адресованы таким же проблемам, как и Эдвардо, в комментариях, которые, казалось, выступали за возвращение 0. Это случай, против которого я выступал.

ОТВЕТ: Я думаю, что здесь есть основная проблема, которая заключается в том, что деление на 0 не является законным. Это признак того, что что-то в корне неправильно. Если вы делите на ноль, вы пытаетесь сделать что-то, что не имеет смысла математически, поэтому никакой числовой ответ, который вы можете получить, не будет действительным. (Использование null в этом случае является разумным, поскольку это не значение, которое будет использоваться в последующих математических вычислениях).

Таким образом, Эдвардо спрашивает в комментариях «что если пользователь введет 0?», И он настаивает на том, что все должно быть в порядке, чтобы получить 0 взамен. Если пользователь помещает ноль в сумму, и вы хотите, чтобы 0 возвращалось, когда они это делают, то вам нужно вставить код на уровне бизнес-правил, чтобы перехватить это значение и вернуть 0 ... не должно быть какого-то особого случая, когда деление на 0 = 0.

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

Представь, что я что-то кодирую, и я облажался. Я должен был прочитать значение масштабирования измерения радиации, но в странном граничном случае я не ожидал, я прочитал в 0. Затем я опускаю свое значение в вашу функцию ... вы возвращаете мне 0! Ура, нет излучения! За исключением того, что это действительно там, и это просто, что я передал плохую ценность ... но я понятия не имею. Я хочу, чтобы подразделение выдавало ошибку, потому что это признак того, что что-то не так.


15
Я не согласен. Ваши бизнес-правила никогда не должны заканчиваться тем, что вы занимаетесь нелегальной математикой Если вы в конечном итоге делаете что-то подобное, скорее всего, ваша модель данных неверна. Всякий раз, когда вы сталкиваетесь с делением на 0, вам следует задуматься, не должны ли данные быть NULL вместо 0.
Remus Rusanu

32
Я не могу поверить, что за меня проголосовал кто-то, кто спрашивает, занимаюсь ли я когда-нибудь "реальным программированием?" потому что я говорю, чтобы сделать это правильно, а не лениться. вздох
Беска

11
Извини, я не хотел тебя обидеть. Но этот вопрос совершенно актуален во многих распространенных LOB-приложениях, и ответ на него с «делением на 0 не разрешено» не добавляет значения ИМХО.
Эдуардо Молтени

2
@JackDouglas Верно. Это тот случай, когда вы хотите, чтобы бизнес-правила обрабатывали особый случай особым образом ... но это не должно быть основной математикой, которая возвращает пробел. Это должно быть бизнес-правило. Принятый ответ возвращает ноль, что является хорошим способом справиться с этим. Бросить исключение было бы тоже хорошо. Обнаружение и обработка перед отправкой в ​​SQL было бы идеальным вариантом. Предоставление какой-либо функции, которую могли бы вызывать другие вещи, которая возвращает математически неверное значение, - это не тот путь, потому что этот особый случай может не подходить для этих других вызывающих.
Беска

4
@JackDouglas Да, это хорошее резюме, с которым я согласен. Первоначально вопрос, казалось, был сформулирован как «что я могу сделать, чтобы просто скрыть эту ошибку». С тех пор оно развивалось. Возвращая ноль, ответ, к которому он в конце концов приходит, кажется разумным ответом. (Я настоятельно рекомендовал не возвращать 0 или другое число.)
Beska

28
SELECT Dividend / ISNULL(NULLIF(Divisor,0), 1) AS Result from table

Улавливая ноль с помощью nullif (), затем полученное значение null с помощью isnull () вы можете обойти деление на ноль ошибок.


3
Из-за его длины ваш ответ был рекомендован для удаления. Обратите внимание, что всегда лучше добавить небольшое объяснение того, что вы предлагаете, - даже если это кажется очень простым;)
Trinimon

10

Замена «делить на ноль» на ноль противоречива, но это не единственный вариант. В некоторых случаях замена на 1 является (разумно) целесообразной. Я часто использую

 ISNULL(Numerator/NULLIF(Divisor,0),1)

когда я смотрю на сдвиги в счетах / счетчиках и хочу по умолчанию 1, если у меня нет данных. Например

NewScore = OldScore *  ISNULL(NewSampleScore/NULLIF(OldSampleScore,0),1) 

Чаще всего я фактически вычислял это соотношение где-то еще (не в последнюю очередь потому, что оно может генерировать очень большие поправочные коэффициенты для низких знаменателей. В этом случае я обычно контролирую для OldSampleScore больше порога, который затем исключает ноль Но иногда «взлом» уместен.


1
@N Мейсон; да, иногда 1 вариант. Но как вы помните часть ISNULL, когда набираете обратную косую черту?
Хенрик Стаун Поульсен,

1
Извините, Хенрик, я не уверен, что понимаю вопрос.
N Мейсон

6

Некоторое время назад я написал функцию для обработки хранимых процедур :

print 'Creating safeDivide Stored Proc ...'
go

if exists (select * from dbo.sysobjects where  name = 'safeDivide') drop function safeDivide;
go

create function dbo.safeDivide( @Numerator decimal(38,19), @divisor decimal(39,19))
   returns decimal(38,19)
begin
 -- **************************************************************************
 --  Procedure: safeDivide()
 --     Author: Ron Savage, Central, ex: 1282
 --       Date: 06/22/2004
 --
 --  Description:
 --  This function divides the first argument by the second argument after
 --  checking for NULL or 0 divisors to avoid "divide by zero" errors.
 -- Change History:
 --
 -- Date        Init. Description
 -- 05/14/2009  RS    Updated to handle really freaking big numbers, just in
 --                   case. :-)
 -- 05/14/2009  RS    Updated to handle negative divisors.
 -- **************************************************************************
   declare @p_product    decimal(38,19);

   select @p_product = null;

   if ( @divisor is not null and @divisor <> 0 and @Numerator is not null )
      select @p_product = @Numerator / @divisor;

   return(@p_product)
end
go

2
Привет Рон, Хорошее решение, за исключением того, что оно имеет ограниченный тип данных (4 знака после запятой), и наши @divisors также могут быть отрицательными. И как вы обеспечиваете его использование? TIA Хенрик Стаун Поулсен
Хенрик Стаун Поулсен

1
Я довольно быстро справился с конкретной проблемой в то время. Единственное приложение для разработчиков, так что принудительное применение не так сложно, за исключением моей памяти. :-)
Рон Сэвидж

5
Несмотря на оператор print, это не хранимый процесс, это скалярный UDF. Это убьет вас в MS-SQL, если это часть запроса.
Марк Совул

4
Я согласился с утверждением Марка Совула, что скалярная функция вызовет боль. Это ужасное предложение в T-SQL, не делайте этого! Скалярные функции являются разрушителями производительности! Встроенные табличные функции являются единственными хорошими пользовательскими функциями в SQL Server (возможно, за исключением функций CLR, которые могут хорошо работать).
Давос,

4
  1. Добавьте ограничение CHECK, которое заставляет Divisorбыть ненулевым
  2. Добавьте валидатор в форму, чтобы пользователь не мог вводить нулевые значения в это поле.

1
Я начинаю любить ограничения CHECK все больше и больше.
Хенрик Стаун Поульсен

4

Для обновления SQL:

update Table1 set Col1 = Col2 / ISNULL(NULLIF(Col3,0),1)

3
Привет, Виджай, да, это сработает, но ... Я был бы осторожен с частью ISNULL, где вы в конечном итоге делитесь на NULL. Я бы лучше дал пользователю понять, что результат неизвестен, потому что делитель равен нулю.
Хенрик Стаун Поульсен,

2
Это спасло меня в сложных подзапросах, спасибо.
QMaster

3

Не существует волшебной глобальной настройки «отключить деление на 0 исключений». Операция должна сгенерировать, так как математическое значение x / 0 отличается от значения NULL, поэтому оно не может возвращать NULL. Я предполагаю, что вы заботитесь об очевидном, и ваши запросы имеют условия, которые должны исключать записи с делителем 0 и никогда не оценивать деление. Обычная «ошибка» заключается в том, что большинство разработчиков ожидают, что SQL будет вести себя как процедурные языки, и предлагают логическое замыкание оператора, но это НЕ . Я рекомендую вам прочитать эту статью: http://www.sqlmag.com/Articles/ArticleID/9148/pg/2/2.html


4
Есть такая «Волшебная глобальная настройка»; ВЫКЛЮЧИТЕ ARITHABORT.
Дэвид Манхейм

3

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

Я смотрю на подсчет количества оборотов инвентаря, которые происходят за три месяца. Я подсчитал, что у меня есть стоимость товаров, проданных за трехмесячный период, в размере 1000 долларов. Годовой уровень продаж составляет 4000 долларов США (1000 долларов США / 3) * 12. Начальный инвентарь равен 0. Конечный инвентарь равен 0. Мой средний запас равен 0. У меня продажи 4000 долларов в год, и нет запасов. Это дает бесконечное количество ходов. Это означает, что весь мой инвентарь конвертируется и покупается клиентами.

Это бизнес-правило для расчета оборотов запасов.


3
Да, тогда у вас есть бесконечное количество ходов. Так что в этом случае, если у вас деление на ноль, вы должны показать что-то вроде '#INF'.
Полиция SQL

1
«Начальный инвентарь равен 0. Конечный инвентарь равен 0. Мой средний запас теперь равен 0.» Ваш расчет является приблизительным. В какой-то момент инвентарь будет положительным, или вы не сможете ничего продать или продать. Если в результате вы недовольны + ∞, используйте более точную оценку среднего запаса.
Том Блоджет

2
CREATE FUNCTION dbo.Divide(@Numerator Real, @Denominator Real)
RETURNS Real AS
/*
Purpose:      Handle Division by Zero errors
Description:  User Defined Scalar Function
Parameter(s): @Numerator and @Denominator

Test it:

SELECT 'Numerator = 0' Division, dbo.fn_CORP_Divide(0,16) Results
UNION ALL
SELECT 'Denominator = 0', dbo.fn_CORP_Divide(16,0)
UNION ALL
SELECT 'Numerator is NULL', dbo.fn_CORP_Divide(NULL,16)
UNION ALL
SELECT 'Denominator is NULL', dbo.fn_CORP_Divide(16,NULL)
UNION ALL
SELECT 'Numerator & Denominator is NULL', dbo.fn_CORP_Divide(NULL,NULL)
UNION ALL
SELECT 'Numerator & Denominator = 0', dbo.fn_CORP_Divide(0,0)
UNION ALL
SELECT '16 / 4', dbo.fn_CORP_Divide(16,4)
UNION ALL
SELECT '16 / 3', dbo.fn_CORP_Divide(16,3)

*/
BEGIN
    RETURN
        CASE WHEN @Denominator = 0 THEN
            NULL
        ELSE
            @Numerator / @Denominator
        END
END
GO

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

Для меня это решение идеально и элегантно
Payedimaunt

@Payedimaunt; да, UDF приводят к очень элегантному коду. Но это не очень хорошо. Это "курсоры на снотворное". :-) Также трудно запомнить, чтобы написать dbo.Divide вместо обычного "/"
Хенрик Стаун Поульсен

1

Отфильтруйте данные с помощью предложения where, чтобы не получить 0 значений.


1

Иногда 0 может не подходить, но иногда 1 также не подходит. Иногда скачок от 0 до 100 000 000, описываемый как 1 или 100-процентное изменение, также может вводить в заблуждение. 100 000 000 процентов могут быть уместны в этом сценарии. Это зависит от того, какие выводы вы намереваетесь сделать, основываясь на процентах или соотношениях.

Например, очень мелко продаваемый товар с 2-4 проданными и очень крупно продаваемый предмет с 1 000 000 до 2 000 000 проданных товаров может означать совершенно разные вещи для аналитика или для руководства, но оба будут иметь значение 100% или 1 изменение.

Возможно, будет проще изолировать значения NULL, чем просматривать группы из 0% или 100% строк, смешанных с допустимыми данными. Зачастую 0 в знаменателе может указывать на ошибку или пропущенное значение, и вы можете не захотеть просто заполнять произвольное значение только для того, чтобы ваш набор данных выглядел аккуратно.

CASE
     WHEN [Denominator] = 0
     THEN NULL --or any value or sub case
     ELSE [Numerator]/[Denominator]
END as DivisionProblem

1
Я считаю, что проблема заключается в том, чтобы не забыть сделать что-то, когда вы хотите сделать разделение. Если вы не помните, что добавили CASE или NULLIF, вы получите поддержку в течение x недель утром в понедельник. Я ненавижу это.
Хенрик Стаун Поульсен,

1

Вот как я это исправил:

IIF (ValueA! = 0, Total / ValueA, 0)

Это может быть включено в обновление:

SET Pct = IIF (ValueA! = 0, Total / ValueA, 0)

Или в избранном:

ВЫБЕРИТЕ IIF (ValueA! = 0, Total / ValueA, 0) AS Pct FROM Tablename;

Мысли?


Я и другие находим, что замена «неизвестного» на ноль - опасное исправление. Я предпочитаю NULL. Но самое сложное - не забыть добавить iif или nullif во все подразделения!
Хенрик Стаун Поульсен,

0

Вы можете соответствующим образом обработать ошибку, когда она распространяется обратно в вызывающую программу (или игнорировать ее, если вы этого хотите). В C # любые ошибки, возникающие в SQL, выдают исключение, которое я могу перехватить, а затем обработать в своем коде, как и любую другую ошибку.

Я согласен с Беской в ​​том, что вы не хотите скрывать ошибку. Возможно, вы не имеете дело с ядерным реактором, но в целом сокрытие ошибок - плохая практика программирования. Это одна из причин, по которой большинство современных языков программирования реализуют структурированную обработку исключений, чтобы отделить фактическое возвращаемое значение от кода ошибки / состояния. Это особенно верно, когда вы занимаетесь математикой. Самая большая проблема заключается в том, что вы не можете различить возвращаемый правильно 0 или 0 в результате ошибки. Вместо этого любое возвращаемое значение является вычисленным значением, и если что-то идет не так, генерируется исключение. Конечно, это будет отличаться в зависимости от того, как вы обращаетесь к базе данных и на каком языке вы пользуетесь, но вы всегда должны получать сообщение об ошибке, с которым вы можете иметь дело.

try
{
    Database.ComputePercentage();
}
catch (SqlException e)
{
    // now you can handle the exception or at least log that the exception was thrown if you choose not to handle it
    // Exception Details: System.Data.SqlClient.SqlException: Divide by zero error encountered.
}

1
Я думаю, что мы все согласны с тем, что скрыть ошибку с 0 не является решением. Я предлагаю написать наш код так, чтобы за каждым «/» следовал «NULLIF». Таким образом, мой отчет / ядерный реактор не остается один, но вместо этой надоедливой ошибки Msg 8134 отображается «NULL». Конечно, если мне нужен другой процесс, когда делитель равен 0, тогда мы с Беской согласны. Нам нужно закодировать это. Это просто сложно делать каждый раз, когда пишешь "/". «/ NULLIF» не невозможно сделать каждый раз.
Хенрик Стаун Поульсен

0

Используйте, NULLIF(exp,0)но таким образом -NULLIF(ISNULL(exp,0),0)

NULLIF(exp,0)ломается, если exp есть, nullно NULLIF(ISNULL(exp,0),0)не сломается


1
Я не могу получить NULLIF (exp, 0), если 0 - ноль, а не o. Пытаться; ОБЪЯВЛЯЙТЕ @i INT SELECT 1 / NULLIF (@i, 0), чтобы увидеть, можно ли его сломать.
Хенрик Стаун Поульсен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.