JS-трекер
- JS-трекер
- Логика работы трекера
- Работа JS-трекера в кластерном режиме
- Создание скрипта и получение сниппета
- Подключение трекера
- Настройка трекера
- Пакетная отправка событий
- Переопределение метода отправки данных
- Кастомная очистка буфера
- Прогрессивная шкала отправки запросов при ошибках запросов
- Заголовок запроса X-Request-Id
- Обработка полученных данных
- Тестовое событие
- Сбор кастомных событий
- Настройка cors_policy для междоменного обмена данным
- Несовместимость типов при получении Null в активности
JS-трекер собирает статистику о действиях посетителей на сайте. Он позволяет отслеживать события, такие как клики, просмотры страниц, заполнение форм и другие взаимодействия с веб-страницей. Эта информация полезна для аналитики, выявления пользовательских сценариев и оптимизации работы сайта.
Полученные трекером данные автоматически передаются в систему Proceset, где объединяются с данными из других систем.
Логика работы трекера
Отслеживание пользовательского пути на сайте обеспечивают четыре компонента:
- Сниппет — небольшой фрагмент кода, в котором содержатся настройки подключения трекера
- Трекер — js-скрипт, который собирает данные и передает их на сервер
- Модуль трекинга на платформе Proceset (AutomationWebhook) — часть системы, которая принимает данные от трекера
- Модуль автоматизации (Automation) — часть системы, в которой настраивается блок-триггер Новые события для получения событий от трекера и скрипт для обработки полученных данных
Схема работы трекера:
- В код сайта встраивается сниппет.
- При открытии сайта сниппет начинает загружать скрипт трекера. В случае ошибки загрузки сниппет делает повторные попытки через заданный интервал времени. Можно указать несколько путей/адресов для загрузки скрипта, и сниппет будет обращаться к ним по очереди, пока не произойдет успешная загрузка.
- Трекер собирает информацию о событиях в буфер и передает ее на сервер либо через определенные интервалы времени, либо при достижении заданного объема. Если события происходят до загрузки трекера, сниппет перехватывает их и временно сохраняет, чтобы позже передать в трекер. В случае ошибки при отправке данных трекер повторяет попытки отправки либо с каждым новым событием, либо через установленные интервалы. Адресов серверов аналитики может быть несколько. В этом случае для каждой отправки данных выбирается случайный URL из массива. Если отправка не удалась, то сразу происходит попытка отправить данные на другой URL, при этом предыдущий адрес не учитывается. Интервал между попытками отправки на новый URL не может быть изменен. Если все адреса в массиве недоступны, попытка повторяется через интервал, заданный в параметре errorSendTimeout (по умолчанию 15 секунд). Если данные успешно отправляются на какой-то URL, он считается валидным, и в дальнейшем все данные отправляются только на него. Если хотя бы один запрос на валидный URL не удался, этот адрес удаляется из списка, и выбирается новый.
- Сервер аккумулирует полученные запросы и далее запускает скрипт автоматизации для обработки данных.
Подробное описание параметров настройки трекера представлено в разделе Настройка трекера.
Настройка кластерного режима и добавление нескольких адресов серверов аналитики недоступны в SAAS-версии.
Работа JS-трекера в кластерном режиме
Система поддерживает возможность работы с несколькими серверами сервиса сбора аналитики. Это обеспечивает отказоустойчивость и масштабируемость при большом потоке данных, а также позволяет предотвратить потерю данных.
Особенности работы трекера в кластерном режиме:
- Если одна из нод недоступна, трекер перенаправляет данные на другую рабочую ноду
- Если данные дублировались на нескольких нодах из-за нестабильности сети, все дубликаты будут удалены при отправке данных в скрипт автоматизации
- Если произошел сбой, после восстановления работы нода вновь начнет принимать данные, как и раньше, так как структура хранилища данных будет восстановлена, и все UUID в системе будут заново зарегистрированы
Для обеспечения работы JS-трекера необходимо настроить передачу данных через отдельный порт, изолированный от порта, используемого веб-интерфейсом. Для этого порта требуется использовать отдельный SSL-сертификат, который отличается от сертификата, применяемого для веб-интерфейса. Если для сбора данных трекером настроены несколько серверов, выделение работы JS-трекера на отдельный порт, на котором функции взаимодействия с сервером ограничены, улучшает безопасность и изоляцию системы.
Подробное описание настроек можно найти в разделе Настройка кластерного режима.
Создание скрипта и получение сниппета
Для сбора данных и их дальнейшей обработки необходимо создать скрипт.
В качестве триггера для запуска скрипта установите блок Новые события.
В левой панели веб-интерфейса Proceset представлены следующие параметры блока:
- Имя веб-приложения — по умолчанию в качестве имени используется текущий домен
- UUID — идентификатор данных трекера, используется в качестве публичного ID данных
- Адрес сервера сбора аналитики — по умолчанию используется текущий домен. Сервер используется для передачи событий
- Встраиваемый код (сниппет)
Если при настройке блока добавить имя веб-приложения, оно также отобразится во встраиваемом коде. Встраиваемый код можно просмотреть и скопировать, но он недоступен для редактирования.
- В системе не может быть двух одинаковых UUID: они уникальны для каждого трекера.
- Для корректной работы скрипта имя веб-приложения и UUID должны совпадать в блоке и на сайте.
При работе в кластерном режиме адреса доступных серверов сбора аналитики обычно отображаются в левой панели блока. Эти адреса можно редактировать. Если нужный адрес не отображается, вы можете добавить его вручную. Для этого:
- Нажмите Изменить в параметрах блока.
- Кликните + Добавить под полем Адрес сервера сбора аналитики и введите адрес.
- Для удаления нажмите X, для сохранения — Применить.
Блок Новые события запускает скрипт при получении новых событий с сервера.
Когда данные собираются через JS-трекер и передаются в скрипт, они могут быть преобразованы в другой формат. Если в исходных данных есть вложенные объекты, система преобразует их в плоскую структуру, в которой вложенные объекты отображаются как комбинация имен через точки. Например, если в исходных данных поле event
содержит вложенный объект element
, то в выходных данных трекера это поле будет представлено как event.element
.
Блок Новые события возвращает следующие поля:
Поле | Тип | Описание |
---|---|---|
app | Информация о сайте, подключающем трекер | |
app.name | string | Имя приложения. Указывается в конфигурационном файле, по умолчанию используется текущий домен |
campaign | UTM-метки, полученные из URL | |
campaign.source | string null | Источник перехода, рекламная площадка |
campaign.medium | string null | Тип рекламы |
campaign.name | string null | Название рекламной кампании |
campaign.term | string null | Ключевая фраза |
campaign.content | string null | Дополнительная информация по объявлению |
event | Информация о событии | |
event.name | string | Имя события |
event.props | array (string) | Словарь с пользовательскими данными |
event.time | number | Временная метка вызова метода (количество миллисекунд, прошедших с 1 января 1970 года 00:00:00 по UTC) |
event.element | Информация о DOM-элементе, на котором сработало событие: кнопка, ссылка, поле и другие | |
event.element.id | string null | Идентификатор элемента (берется только атрибут id ) |
event.element.name | string null | Имя/данные элемента. Вычисляется индивидуально для каждого типа элемента |
event.element.webctrl_selector | string null | Способ идентификации элемента на странице https://docs.uipath.com/studio/docs/about-selectors#webctrl |
id | string | Строка, разделенная на две части с помощью символа подчеркивания (_ ). Первая часть генерируется один раз при запуске трекера и остается неизменной на протяжении всей сессии. Вторая часть увеличивается на 1 с каждым новым событием |
lib | Информация о трекере | |
lib.version | string | Номер версии трекера |
navigator | Состояние и особенности (свойства) пользовательского агента | |
navigator.user_agent | string | Строка агента пользователя для данного браузера |
navigator.language | string | Предпочитаемый пользователем язык, как правило, это язык пользовательского интерфейса браузера |
person | Информация о пользователе | |
person.user_id | string null | Идентификатор зарегистрированного пользователя |
person.anonymous_id | string | Сгенерированный анонимный UUID v4 идентификатор |
person.ids | array (string) | Словарь идентификаторов, полученных из сторонних трекеров, подключенных к странице |
page | Информация о web-странице | |
page.title | string | Заголовок вкладки браузера |
page.referrer | string | URL, с которого произошел переход на текущую страницу |
page.url | string | URL текущей страницы |
page.path | string | Часть URL между хостом и параметрами |
screen | Информация об экране | |
screen.width | number | Ширина экрана в пикселях |
screen.height | number | Высота экрана в пикселях |
time_zone | string | Временная зона |
Общая информация по запросам на стороне сайта, такая как время загрузки страницы, длительность работы отдельных элементов на странице, не сохраняется.
Трекер также не собирает данные по местоположению пользователя.
Чтобы исключить сбор некоторых событий трекером, добавьте в сниппет предикат filterFn
. Подробную информацию о предикате можно найти в разделе Настройка трекера.
Если реальные данные с сайта еще не поступили, можно протестировать блок Новые события с помощью тестового события. Это позволит получить необходимые поля для маппинга и настроить скрипт до получения реальных данных.
Дальнейшая настройка скрипта описана в разделе Обработка полученных данных.
Подключение трекера
Для подключения трекера к странице сайта скопируйте код сниппета в параметрах блока-триггера Новые события и вставьте его в код страницы.
При открытии сайта сниппет начинает загружать скрипт трекера согласно заданным параметрам сниппета.
Настройка трекера
Настройка параметров трекера производится внутри сниппета.
Запуск трекера можно сконфигурировать следующими параметрами:
app
— имя web-приложения. По умолчанию в качестве имени используется текущий доменserverUrl
— адрес сервера сбора аналитики. Может быть передан в виде строки или массива строкuuid
— идентификатор данных трекера. Используется в качестве публичного ID данных. UUID используется для сопоставления конфигурации трекера и ID скрипта, в котором будут обрабатываться данныеsend
(data
,url
,headers
) — метод отправки данных трекера. Позволяет настроить кастомизацию метода отправки данных, например, зафиксировать формат передачи данных или изменить заголовки запроса. Функция должна возвращать объектPromise
для отслеживания процесса выполнения запросаbufferLifetime
— интервал времени в миллисекундах между отправками накопленных событий. По истечении указанного времени накопленные данные автоматически отправляются на сервер. Значение по умолчанию —Infinity
bufferLimit
— максимальное количество событий, которое может храниться в буфере. Когда количество событий достигает этого лимита, накопленные данные отправляются на сервер. Значение по умолчанию —1
bufferMaxSize
— максимальный размер памяти в байтах, по достижению которого буфер будет очищен. По умолчанию — 1 МбerrorSendTimeout
— интервал в миллисекундах между повторными попытками отправки событий на сервер, если предыдущая попытка завершилась с ошибкой. По умолчанию — 15000 мс (15 секунд)filterFn(event)
— предикат, позволяющий исключить из сбора ненужные события. Отфильтрованные с его помощью события не будут сохраняться в буфере на отправкуinterceptors
— пользовательские конфигурации для автоматического сбора собственных событий
Подробная информация о параметрах bufferLifetime
, bufferLimit
и bufferMaxSize
представлена в разделе Пакетная отправка событий.
Подробное описание параметра interceptors
представлено в разделе Сбор кастомных событий.
Пример настройки параметра filterFn
для исключения событий из сбора:
filterFn: (data) => {
if (data.event.name === 'Page') {
return false;
}
return true;
}
Где:
data
— событие, которое необходимо отфильтроватьdata.event.name === 'Page'
— условие, по которому происходит фильтрация
Кроме того, через сниппет возможно изменить путь к файлу трекера, указав массив строк. Путь к файлу задается в предпоследнем параметре вызова метода сниппета. Периодичность повторных запросов файла трекера в случае ошибки задается в последнем параметре в миллисекундах (по умолчанию — 15000).
Пример сниппета:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
serverUrl: [
"https://example1.ru",
"https://example2.ru",
"https://example3.ru",
"https://example4.ru"
],
uuid: "qwertyuiopasdfghjklzxcvbnm123456",
});
})(window,document,"script","tracker",["tracker1.js","tracker2.js","tracker3.js"],15000);
При успешном подключении трекера в консоли разработчика (F12) можно увидеть сообщение о дате сборки трекера.
Пакетная отправка событий
Трекер собирает события и сохраняет их в буфере до тех пор, пока не выполнится одно из условий для отправки данных на сервер.
Логика отправки событий определяется следующими параметрами, которые настраиваются в сниппете:
bufferLifetime
— интервал времени в миллисекундах между отправками накопленных событий. По истечении указанного времени накопленные данные автоматически отправляются на сервер. Значение по умолчанию —Infinity
bufferLimit
— максимальное количество событий, которое может храниться в буфере. Когда количество событий достигает этого лимита, накопленные данные отправляются на сервер. Значение по умолчанию —1
bufferMaxSize
— максимальный размер буфера в байтах. По достижении указанного значения буфер будет автоматически очищен. По умолчанию — 1 Мб
Принцип работы:
- Данные отправляются, когда выполняется одно из следующих условий:
- Достигается лимит накопленных событий, заданный параметром
bufferLimit
. - Истекает время, определенное параметром
bufferLifetime
, после последней успешной отправки данных.
- Достигается лимит накопленных событий, заданный параметром
- Отправка данных происходит в тот момент, когда наступит одно из этих условий, в зависимости от того, какое из них произойдет раньше.
- После успешной отправки данных отправленные события удаляются из буфера и таймер перезапускается.
- Если буфер достигает максимального размера, заданного параметром
bufferMaxSize
, он очищается. Все накопленные в нем данные будут утрачены
Переопределение метода отправки данных
Для реализации пользовательской логики отправки данных в трекере предусмотрена возможность переопределить метод отправки с помощью параметра send
. Метод, передаваемый в качестве значения этого параметра, принимает в себя два аргумента:
- Первый — массив событий
- Второй — UUID, переданный в конфигурацию трекера
Метод, передаваемый через параметр send
, можно использовать для реализации нестандартной логики преобразования данных трекера или очистки буфера.
Пример:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "qwertyuiopasdfghjklzxcvbnm123456",
send(data, url, headers) {
return fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
...headers,
},
body: JSON.stringify(data),
});
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Кастомная очистка буфера
Автоматическую очистку данных буфера после их успешной отправки на сервер можно реализовать с помощью переопределения метода отправки данных трекера через параметр send
.
Для очистки буфера:
- Реализуйте возврат объекта
Promise
из метода отправки данных через параметрsend
. - В колбэке
Promise
вызовите методresolve
с параметром{ ok: true }
.
Пример:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "script",
send(data, url, headers) {
if (data.length > 10) {
return Promise.resolve({ok: true})
}
return fetch(url, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
...headers
},
body: JSON.stringify(items),
});
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Прогрессивная шкала отправки запросов при ошибках запросов
Прогрессивный таймаут отправки ошибочных запросов можно реализовать с помощью переопределения метода send
.
Пример:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "script",
send(data, url, headers) => {
function fetchWithRetry(url, options = {}, delays) {
let attempt = 0;
function makeRequest() {
return fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(response.status);
}
return response;
})
.catch(error => {
if (attempt < delays.length) {
return new Promise(resolve =>
setTimeout(resolve, delays[attempt])
).then(() => {
attempt++;
return makeRequest();
});
} else {
throw error;
}
});
}
return makeRequest();
}
return fetchWithRetry(url, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
...headers
},
body: JSON.stringify(items)
}, [1000, 2000, 3000, 4000, 5000, 7000]);
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Заголовок запроса X-Request-Id
При отправке данных на сервер каждый запрос сопровождается заголовком X-Request-Id
в формате f4c86d97-a74e-401b-b8ec-87ff04d33ca7_1
. Этот заголовок обеспечивает идентификацию каждого запроса и помогает предотвратить дублирование или потерю данных в случае сетевых сбоев.
Заголовок состоит из двух частей, разделенных символом подчеркивания _
:
- Первая часть — уникальный идентификатор, который генерируется один раз при инициализации трекера и остается неизменным в рамках одной сессии.
- Вторая часть — порядковый номер запроса, увеличивающийся с каждой новой попыткой отправки данных.
Логика работы:
- В каждом запросе передается набор пакетов событий, который получает свой уникальный
X-Request-Id
. - Если запрос завершился с ошибкой, трекер повторяет отправку того же набора данных с тем же
X-Request-Id
. Новые события, поступившие в буфер за это время, не включаются в повторный запрос. - Как только запрос успешно отправляется, накопленные данные будут отправлены с новым
X-Request-Id
. - Если данные отправляются на несколько адресов (например, при использовании массива
serverUrl
), идентификаторX-Request-Id
для одного набора данных не меняется.
Обработка полученных данных
Для обработки событий, полученных с помощью трекера Новые события добавьте в скрипт другие блоки автоматизации. Например, вы можете настроить автоматическую загрузку собранных данных в таблицу, чтобы затем использовать их для анализа.
Подробное описание блоков для работы с полученными данными представлено в разделе Редактирование скрипта.
Тестовое событие
Если реальные данные с сайта еще не поступили, вы можете протестировать блок Новые события с помощью тестового события.
Тестовое событие — это проверка работы блока с тестовыми данными, заданными в формате JSON. После обработки тестового события система создает поля, которые затем можно использовать для маппинга в последующих блоках скрипта. Это дает возможность работать с полями блока Новые события и настроить весь скрипт до получения реальных данных с сайта.
Чтобы использовать тестовое событие:
- Перейдите во вкладку Тест и нажмите Тестовое событие.
- В открывшейся панели введите пример тела запроса в JSON-формате.
- Нажмите Применить, чтобы получить маппинг введенного примера.
Сбор кастомных событий
Трекер имеет 2 метода — trackEvent
и identify
. Оба доступны разработчикам приложения через глобальный объект window.tracker
или экземпляр tracker
.
Чтобы отследить произвольное событие, необходимо добавить ручной вызов метода trackEvent
трекера при возникновении нужного события. Метод позволяет передать на сервер событие с произвольным именем event.name
и необязательным объектом со свойствами типа ключ-значение
. Вызов метода осуществляется в момент совершения события, например, в обработчике onClick элемента.
Пример использования при подключении через сниппет:
tracker("trackEvent", "product catalog is opened");
tracker("trackEvent", "purchase", { product: "test item", price: "1$" });
Если в необязательный объект со свойствами типа ключ-значение (event.props
в структуре события) передать ключ element
со значением типа Element
и/или ключ elementName
со значением типа String
, то эти ключи удалятся из передаваемых данных, сформировав elementData
. Остальные поля остаются в передаваемых данных.
Пример использования при подключении через сниппет:
tracker("trackEvent", "some event", {
element: document.getElementById("container"),
elementName: "name",
otherProp: "value",
});
Для идентификации пользователя у трекера есть событие identify
. identify
позволяет связать с анонимным пользователем произвольный числовой или строковый идентификатор — логин, email, ID в системе и другие. Предполагается, что он будет предоставлен приложением при авторизации пользователя.
Необходимо делать вызов события в момент уточнения идентификатора.
При попытке вызова identify
с идентификатором, который уже сохранен в браузере, событие на сервер отправлено не будет.
Пример использования при подключении через сниппет:
tracker("identify", 1, { name: "Dmitry" });
Данный вызов добавляет в контекст пользователя идентификатор «1», который записывается в localStorage
и будет отправляться на сервер аналитики с каждым последующим запросом.
Далее происходит автоматическое делегирование вызова методу trackEvent
с именем события identify
с отправкой события на сервер. Для примера выше вызов будет следующим:
tracker("trackEvent", "identify", { name: "Dmitry" });
В контекст будет сохранен только идентификатор пользователя, но не объект. Например, такой как { name: "Dmitry" }
.
Кастомные события автоматически собираются с помощью параметра interceptor. В качестве значения параметра передается массив объектов.
Чтобы JS-трекер собирал кастомные события, дополните встраиваемый код пользовательской конфигурации interceptors
:
interceptors: [
{
action: "Click" | "FieldChange",
interceptor: (target: EventTarget) => {
eventName: string,
elementName: string,
element: Element,
eventProps: object
} | null
}
]
Где:
action
— тип события, по которому будет вызванinterceptor
. Возможные значения:Click
— подписка на событиеclick
FieldChange
— подписка на событиеfocusin
,focusout
interceptor
— обработчик события. Принимает элемент, по которому произошло событие, и возвращает структуру, описывающую кастомное событие. В качестве возвращаемого значения в коде обработчика можно указатьnull
, а в реализации — элементы, с которыми стоит фиксировать действия. Фиксируются только кастомные события с прописанными в сниппете элементами. Если кастомное событие будет связано с другими элементами, будет возвращатьсяnull
.
В зависимости от того, какие данные необходимо получать, у параметра могут быть указаны следующие поля:
eventName
elementName
element
eventProps
Сбор информации осуществляется по указанным полям.
Настройка cors_policy для междоменного обмена данным
В случаях, когда JS-трекер находится в домене отличном от того, на котором располагается Proceset, необходимо настроить политику cors, предоставляющую доступ к этому домену. Для этого необходимо в конце конфигурационного файла Proceset com.infomaximum.subsystem.frontend.json добавить перед последней фигурной скобкой:
"cors_policy": "*"
Файл расположен на сервере Proceset в каталоге: C:\ProgramData\Infomaximum\config\com.infomaximum.subsystem.frontend.json.
Если система установлена на OC Linux, при запуске службы в переменной FE_CORS_POLICY
укажите *
. По умолчанию cors-политика выключена.
Несовместимость типов при получении Null в активности
При возникновении несовместимости типов при получении Null в активности, собираемой JS-трекером, необходимо привести колонки целевой таблицы в скрипте к типу Nullable. Если таблица была создана ранее, то можно воспользоваться следующим способом устранения данной проблемы:
- Добавьте блок SQL-запрос в целевой скрипт.
- Введите SQL-запрос
ALTER TABLE NAME_TABLE(название таблицы) MODIFY COLUMN person_user_id Nullable(String)
. - Измените тип целевой колонки.
- Удалите блок SQL-запрос, созданный в п.1 и п.2.
- Используйте обновленную таблицу с типом колонок Nullable и ранее созданным скриптом.
Способ подходит для решения любой подобной ситуации, связанной с несовместимостью типов.
Была ли статья полезна?