Дублирование данных нежелательно потому что

Дублирование данных нежелательно потому что

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому что

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоДублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому что

Лекция 3. Файловые системы как предшественники баз данных
Предшественниками баз данных были файловые системы. Знать принципы работы файловых систем не только полезно, но и необходимо при переходе от файловой системы к системе баз данных. Понимание проблем, присущих файловым системам, может предотвратить их повторение в СУБД.
Файловые системы были первой попыткой компьютеризировать известные всем ручные картотеки. Подобная картотека в некоторой организации могла содержать всю внешнюю и внутреннюю документацию, связанную с каким-либо проектом, продуктом, задачей, клиентом или сотрудником. Обычно таких папок очень много, они нумеруются и хранятся в одном или нескольких шкафах.
Файловые системы имеют существенные ограничения:

Разделение и изоляция данных
Когда данные изолированы в отдельных файлах, доступ к ним весьма затруднителен. Например, для создания списка всех домов, отвечающих требованиям потенциальных арендаторов, предварительно нужно создать временный файл со списком арендаторов, желающих арендовать недвижимость типа «дом». Затем в файле PropertyForRent следует осуществить поиск объектов недвижимости типа «дом» с арендной платой ниже установленного арендатором максимума. Выполнять подобную обработку данных в файловых системах достаточно сложно. Для извлечения соответствующей поставленным условиям информации программист должен организовать синхронную обработку двух файлов. Трудности существенно возрастают, когда необходимо извлечь данные из большего количества файлов.
Дублирование данных
Из-за децентрализованной работы с данными, проводимой в каждом отделе независимо от других отделов, в файловой системе фактически допускается бесконтрольное дублирование данных, и это, в принципе, неизбежно.
Бесконтрольное дублирование данных нежелательно по следующим причинам.

Зависимость от данных
Как уже упоминалось выше, физическая структура и способ хранения записей файлов данных жестко зафиксированы в коде приложений. Это значит, что изменить существующую структуру данных достаточно сложно. Например, увеличение в файле PropertyForRent длины поля адреса с 40 до 41 символа кажется совершенно незначительным изменением его структуры, но для воплощения этого изменения потребуется, как минимум, создать одноразовую программу специального назначения, преобразующую уже существующий файл PropertyForRent в новый формат. Она должна выполнять следующие действия:

Помимо этого, все обращающиеся к файлу PropertyForRent программы
должны быть изменены с целью соответствия новой структуре файла. А таких программ может быть очень много. Следовательно, программист должен прежде всего выявить все программы, нуждающиеся в доработке, а затем их перепроверить и изменить. Отметим, что многие подлежащие изменению программы могут обращаться к файлу PropertyForRent, но при этом вообще не использовать поле адреса. Ясно, что выполнение всех этих действий требует больших затрат времени и может явиться причиной появления ошибок. Данная особенность файловых систем называется зависимостью программ от данных (program-data dependence).
Несовместимость форматов файлов
Поскольку структура файлов определяется кодом приложений, она также зависит от языка программирования этого приложения. Например, структура файла, созданного программой на языке COBOL, может значительно отличаться от структуры файла, создаваемого программой на языке С. Прямая несовместимость таких файлов затрудняет процесс их совместной обработки.
Например, предположим, что сотрудникам отдела контрактов требуется найти имена и адреса всех владельцев, недвижимость которых в настоящее время сдана в аренду. К сожалению, в отделе контрактов нет сведений о владельцах недвижимости, так как они хранятся только в отделе реализации. Однако в файлах отдела контрактов имеется поле propertyNo с номерами объектов недвижимости, которые можно использовать для поиска соответствующих номеров в файле PropertyForRent отдела реализации. Этот файл также содержит поле ownerNo с номерами владельцев, которые можно использовать для поиска сведений о владельцах в файле PrivateOwner. Допустим, что программа отдела контрактов создана на языке COBOL, а программа отдела реализации — на языке С.
Тогда с целью поиска соответствующих номеров объектов недвижимости в поле propertyNo в двух файлах PropertyForRent программисту потребуется создать программное обеспечение, предназначенное для преобразования этих полей в некоторый общий формат. Не вызывает сомнения, что этот процесс может оказаться весьма длительным и дорогим.
Фиксированные запросы/быстрое увеличение количества приложений
С точки зрения пользователя возможности файловых систем намного превосходят возможности ручных картотек. Соответственно возрастают и требования к реализации новых или модифицированных запросов. Однако файловые системы требуют больших затрат труда программиста, поскольку все необходимые запросы и отчеты должны быть созданы именно им. В результате события обычно развивались по одному из следующих двух сценариев. Во-первых, во многих организациях типы применяемых запросов и отчетов имели фиксированную форму, и не было никаких инструментов создания незапланированных или произвольных запросов как к самим данным, так и к сведениям о том, какие типы данных доступны.
Во-вторых, в других организациях наблюдалось быстрое увеличение количества файлов и приложений. В конечном счете наступал момент, когда сотрудники отдела обработки данных были просто не в состоянии справиться со всей работой с помощью имеющихся ресурсов. В этом случае нагрузка на сотрудников отдела настолько возрастала, что неизбежно наступал момент, когда программное обеспечение было неспособно адекватно отвечать запросам пользователей, эффективность его падала, а недостаточность документирования имела следствием дополнительное усложнение сопровождения программ. При этом часто игнорировались вопросы поддержки функционирования системы: не предусматривались меры по обеспечению безопасности или целостности данных; средства восстановления в случае сбоя аппаратного или программного обеспечения были крайне ограничены или вообще отсутствовали. Доступ к файлам часто ограничивался узким кругом пользователей, т.е. не предусматривалось их совместное использование даже сотрудниками одного и того же отдела.

Источник

Как избавиться от дублей в базе данных (на примере MS SQL)

Всем привет! Меня зовут Евгений, я занимаюсь разработкой и проектированием в Ozon. Больше всего работаю с MS SQL и C#, но попадаются и другие СУБД и языки программирования.

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

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоВзаимодействие информационных систем

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

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

расскажу немного о специфике предметной области;

рассмотрим популярные варианты борьбы с дублированием данных;

опишу, в чём заключается наш способ;

приведу пример, как это всё работает.

Примеры будут приведены для MS SQL Server. Однако аналогичное решение можно реализовать на любой другой СУБД с учётом её особенностей.

Предметная область: логистика заказов

У нас в Ozon все заказы делятся по отправлениям:

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоСостав заказа

В отправлении может быть один или несколько типов товаров.

В базе данных существуют отдельные таблицы для заказов и для отправлений.

Заказы бывают двух типов:

Одноместный заказ, состоящий из одного отправления (Упаковка 1 — одно отправление):

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоОдноместный заказ из одного отправления (упаковки)

Многоместный заказ, состоящий из двух и более отправлений (Упаковка 1 — одно отправление, Упаковка 2 — второе отправление):

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоМногоместный заказ из нескольких отправлений (упаковок)

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

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

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

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоАсинхронная тарификация отправлений

Проблема дублирования данных возникает в отношении многоместных заказов.

Сумма тарификации по бизнес-требованиям должна проставляться целиком только на одном отправлении одного типа тарификации в рамках заказа (выдача или возврат).

И тут мы подходим к сути проблемы. Как заставить асинхронные процессы из других систем Ozon обновлять сумму только в рамках одного типа тарификации, то есть на одном отправлении? Если не делать синхронизацию изменений, то можно получить дублирование сумм для разных отправлений заказа по одному и тому же типу тарификации, то есть когда на одном или нескольких отправлениях в рамках одного заказа будут проставлены одни и те же суммы.

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоДублирование тарификаций отправлений

Выбираем метод борьбы с дублированием данных

На практике я встречал два основных способа не допустить дублирования данных:

Навесить ограничение на уникальность для нужной комбинации полей в таблице и обрабатывать исключения при нарушении этого ограничения (хорошо работает при вставках и обновлениях данных).

Поместить участок кода, где может возникнуть дублирование данных, в транзакцию с уровнем изоляции «сериализуемая» (SERIALIZABLE) (хорошо работает при добавлениях и обновлениях в монопольном режиме).

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

А что, если сделать гибридный вариант?

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

На самом деле можно делать пересчёт и постфактум (то есть жить с дублирующими суммами), но в нашем случае это плохое решение, так как разные сервисы будут считывать невалидные данные до момента пересчёта, что приведёт к некорректным счетам;

мы используем уровень изоляции транзакций снимками (SNAPSHOT).

Алгоритм предотвращения дублирования данных

Верхнеуровнево алгоритм для многоместных заказов выглядит следующим образом:

Создаём таблицу, в которой будет содержаться вся необходимая информация для предотвращения дублирования сумм при обновлениях.

Делаем хранимую процедуру, которая будет в монопольном режиме добавлять в новую таблицу запись с проверкой и возвращать статус, отражающий, удалось ли добавить запись или нет (если нет, значит, запись уже была добавлена).

Создаём хранимую процедуру для записи суммы тарифа. При каждом проставлении суммы сравниваем её с суммой из новой таблицы и сначала обновляем сумму в новой таблице, если там она была 0:

в случае успешного обновления обновляем сумму в операции;

если в новой таблице уже указана сумма, отличная от 0, и отправление в обработке отличается от отправления, на котором проставлена сумма (то есть кто-то уже обновил сумму и нам не нужны дубли), обнуляем её;

в противном случае ничего не делаем.

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоСхема алгоритма

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

Создаём таблицу с информацией для предотвращения дублирования сумм при обновлениях

Создадим таблицу dbo.LogisticOrderMultiPostingPrincipalTariff следующим образом:

Определение таблицы dbo.LogisticOrderMultiPostingPrincipalTariff

Таблица dbo.LogisticOrderMultiPostingPrincipalTariff содержит следующие поля:

LogisticOrderID — идентификатор заказа;

TariffTypeID — идентификатор типа тарификации;

ArticleID — идентификатор отправления;

OperationID — идентификатор операции;

Amount — сумма тарификации;

InsertUTCDate — дата и время создания записи в UTC (служебное поле).

Создаём хранимую процедуру для монопольного добавления записи в таблицу

Создадим хранимую процедуру dbo.AddLogisticOrderMultiPostingPrincipalTariff, которая будет в монопольном режиме добавлять в новую таблицу dbo.LogisticOrderMultiPostingPrincipalTariff запись с проверкой на существование записи с заданной парой (заказ, тип тарификации) и возвращать статус, показывающий, удалось добавить запись или нет (если нет, то запись уже была добавлена):

Определение хранимой процедуры dbo.AddLogisticOrderMultiPostingPrincipalTariff

Хранимая процедура dbo.AddLogisticOrderMultiPostingPrincipalTariff принимает следующие параметры:

LogisticOrderID — идентификатор заказа;

TariffTypeID — идентификатор типа тарификации;

ArticleID — идентификатор отправления;

OperationID — идентификатор операции;

IsResult — выходной параметр, возвращающий значение 1 (успех вставки) или 0 (неуспех — когда уже есть запись с таким же заказом и типом тарификации).

Обратите внимание, что при добавлении записи в таблицу dbo.LogisticOrderMultiPostingPrincipalTariff в хранимой процедуре dbo.AddLogisticOrderMultiPostingPrincipalTariff сумма тарификации Amount принимает значение 0, так как проставление этой суммы произойдёт позже через обновление.

Создаём хранимую процедуру для записи суммы тарифа

Создадим ещё одну хранимую процедуру. При каждой записи суммы тарифа сравниваем её с суммой из новой таблицы dbo.LogisticOrderMultiPostingPrincipalTariff и сначала обновляем в ней сумму Amount, если там она 0. В случае успешного обновления меняем сумму в операции. В противном случае (если в операции стоит иная сумма, то есть кто-то её уже обновил) — обнуляем её.

Создадим эту хранимую процедуру dbo.UpdAmountLogisticOrderMultiPostingPrincipalTariff:

Определение хранимой процедуры dbo.UpdAmountLogisticOrderMultiPostingPrincipalTariff

Хранимая процедура dbo.UpdAmountLogisticOrderMultiPostingPrincipalTariff принимает следующие параметры:

LogisticOrderID — идентификатор заказа;

TariffTypeID — идентификатор типа тарификации;

ArticleID — идентификатор отправления;

OperationID — идентификатор операции;

Amount — сумма тарификации;

IsResult — выходной параметр, возвращающий значение:

1 — успех обновления;

0 — неуспех, т е когда уже есть запись с нужной суммой

NULL — когда не нужно ничего менять, например при попытке записать сумму, которая уже указана, в то же отправление, на котором она указана.

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

Пример использования созданных хранимых процедур

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

У нас все отправления заказа создаются и переходят в статус «Сформировано» до того, как хотя бы одно из них куда-либо поедет. Напомним, что тарификация происходит при выдаче или возврате товара, когда сформированы все отправления заказа.

При тарификации у нас на входе есть следующие параметры:

LogisticOrderID — идентификатор заказа;

TariffTypeID — идентификатор типа тарификации;

ArticleID — идентификатор отправления;

OperationID — идентификатор операции.

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

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

Далее мы вычисляем сумму тарифа по определённому алгоритму и кладём её в переменную @Amount.

На следующем этапе мы записываем ненулевую сумму тарификации @Amount в новую таблицу dbo.LogisticOrderMultiPostingPrincipalTariff через вызов хранимой процедуры dbo.UpdAmountLogisticOrderMultiPostingPrincipalTariff:

Теперь смотрим на вернувшееся значение в переменной @IsResult:

если @IsResult = 1, то сумму тарифа @Amount не меняем;

если @IsResult = 0, то обнуляем сумму тарифа @Amount и после этого записываем получившуюся сумму тарификации @Amount в нужную операцию отправления заказа;

если @IsResult IS NULL, то ничего не делаем.

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоДублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоСинхронизация проставления суммы тарификации в отправлении в рамках заказа

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

Ключ к уменьшению количества дублей — уровень изоляции транзакций

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

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

Этот способ — гибридный: мы используем ограничение уникальности по набору (заказ, тип тарификации) и монопольный блок на вставку и обновление на вставку и обновление данных.

Дублирование данных нежелательно потому что. Смотреть фото Дублирование данных нежелательно потому что. Смотреть картинку Дублирование данных нежелательно потому что. Картинка про Дублирование данных нежелательно потому что. Фото Дублирование данных нежелательно потому чтоРезультат правильной тарификации

Приведённое решение можно использовать на разных СУБД в задачах, где происходят асинхронные вставки и обновления данных разными процессами и из-за этого возможно дублирование данных.

Такие задачи распространены во многих сферах:

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

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

Ссылки по теме

Транзакции MS SQL Server: как вызывать транзакции, откатывать и фиксировать их, какие существуют уровни изоляции транзакций и различия между ними.

Предложение OUTPUT: как получить то, что вставили, обновили (заодно смотрим, что было до обновления) или удалили при различных командах модификации данных.

MS SQL Server: общая документация по СУБД, которую я выбрал для реализации описанного метода предотвращения дублирования данных.

Источник

Дублирование информации

Доброе время суток,

Очень давно наблюдаю за интернетом в целом и на ум приходит только одна мысль — зачем всё это и кому это надо. Поясню. Где-то был пост о том, что сейчас в интернете очень много информации (было указано в цифрах), но полезна ли эта информация.

Дублирование

Самым ярким примером дублирования информации являются социальные сети и различные сервисы (Digg и прочие). Взять тот же Digg. Там чётко указывается цифра сколько раз данная тема была опубликована. Ну и зачем? Неужели не достаточно одной публикации? Пояснить можно так — каждый себе заносит в чемодан только то, что ему интересно. Понять можно, просто я бы тогда этот самый чемодан сделал личным пространством и никому не показывал.

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

Дублирование №2

Другая причина моих мозговых процессов — это дублирование аккаунтов в интернете в целом. Каждый сайт, каждый стартап хочет набрать свою базу пользователей и показать друзьям какой у них востребованный сайт. Я думаю, что лидеры уже более менее понятны. Это всемирно-известные сервисы и социальные сети сети (Facebook, Twitter, WordPress, Google, YouTube и т.д.) и если человек уже там был зарегистрирован, то на очередном сайте он что делает? Правильно, либо повторяет свои данные, либо просто придумывает временные, дабы посмотреть, что твориться внутри очередного, интересного и не очень, проекта.

Я обоими руками за OpenID, OAuth, XAuth и прочие технологии, которые избавляют всех от лишних телодвижений. И строя очередной проэкт надо думать об открытости и доступности, т.е. мысли вроде «пользователи уже есть, осталось их только привлечь» будут полезны всем. Регистрации, где все требовали E-Mail адреса, пользуясь старой школой, что E-Mail адреса очень важны (для последующей рассылки). Спама итак 80% на весь интернет, а то и больше. Используем OpenID и таким образом очищаем интернет.

Дублирование №3

И третья мысль, которая не даёт покоя. Зачем копировать полностью чужие проекты? Тот же Twitter скопировали n-ое колличество раз, уже и открываются сайт с содержанием: «Скачайте данный скрипт, установите и получите копию Твиттера». И зачем? В интернете ещё много свободных ниш для начала, но все рвуться повторять и копировать то, что уже сделано и хорошо работает. Тут многие начнут пояснять, что мол «конкуренция, сделал проект и получай кэш за счёт рекламы, продвинутость» и другое. Как сказал Питер Даниэелс: «Делайте лучше, чем ваши конкуренты, либо не делайте совсем». 1000 и 1 сервис ничуть не улучшили Твиттер, а просто скопировали и то, не полностью.

Не только Твиттер, но и сайты-опросники, сайты-комментаторы, сайты-шаринги и прочее. Всего уже предостаточно и даже слишком. Я просматриваю каталог FeedMyApp и каждый день там есть новая пачка сайтов и сервисов, но открутим 2-3 страницы назад и получим предыдущие копии.

Послесловие

Как никогда сейчас интернет нуждается в очистке и переработке. Это начал понимать Google и это радует. Все знают уже современную поговорку: «В интернете есть всё, но не то, что надо». Все знают и продолжают дублировать. Интернет насчитывает более 120 миллионов доменных имён (± 30 миллионов). Каждый день появляется и пропадает множество сайтов и сервисов. Но моё видение такое, что только 20% из всего интернета является уникальной информацией, остальное дубликаты.

Тема поднималась и не раз, пишу, потому что просто накипело после очередного мега-проэкта, где надо вводить 1000 и одно поле для регистрации.

Источник

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

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