Импорт csv в PostgreSQL с проверкой полей

Всем привет
Хочу поделиться своим решением, которое немного требует доработки, но будет полезным в нашем комьюнити

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

Задача

Реализовать импорт файла в базу данных

Архитектура

Так как было согласовано не делать всякие морды, а в итоге у нас считаются деньги - надо сделать максимально точно.
Было выбрано решение: CSV файл загружается через FTP, cron на регламенте запускает bash скрипт который импортирует файл в PostgreSQL
CRON-> .sh -> .sql

Наш bash скрипт

#!/bin/sh
cd /home/admin/ftp/jeventpayments/

f=`find -maxdepth 1 -name \*.csv -or -name \*.CSV`

if [[ ${f[*]} == '' ]]; then
	exit 1
fi

for file in $f
do
	declare -A A=([ref]=ref [action_id]=action_id [order_id]=order_id [payment]=payment [paid]=paid [status]=status [partner]=partner)

 # Имя промежуточного файла
 date=$(date '+%Y%m%d%H%M%S')
 logfile=`echo ${file} | sed -e 's/.csv$//' | sed -e 's/.CSV$//'`
 logfile=${logfile}_${date}.log
 errfile=`echo ${file} | sed -e 's/.csv$//' | sed -e 's/.CSV$//'`
 errfile=${errfile}_${date}.csv
 ERR=errors/${errfile##*/}
 LOG=logs/${logfile##*/}
 ERRLOG=logs/ERROR_${logfile##*/}
 touch ${LOG}
 echo $(date '+%Y-%m-%d %H:%M:%S')	Processing	${file} >> ${LOG}

unset i
unset IFS
unset arr
unset col

 for i in `cat ${file}`; do
		headers=${i}
		IFS=','
		arr=($i)
		echo $(date '+%Y-%m-%d %H:%M:%S')	Проверка атрибутов >> ${LOG}
 		echo $(date '+%Y-%m-%d %H:%M:%S')	Обязательные атрибуты ${A[*]} >> ${LOG}
	for col in "${arr[@]}"; do
    	
		#[[ ${A[*]} =~ ${col} ]] && echo 'yes' ${col} ${file} || echo 'no' ${col} ${file}


		if [[ ${A[*]} =~ ${col} ]]; then
			echo $(date '+%Y-%m-%d %H:%M:%S')	Aтрибут ${col} найден >> ${LOG}
			unset A[${col}]
		fi

	done
	unset IFS
	break;
 done

	
	if [[ ${#A[*]} >0 ]]; then
			echo $(date '+%Y-%m-%d %H:%M:%S')	ОШИБКА ОСТАЛИСЬ НЕ ЗАПОЛНЕННЫЕ АТРИБУТЫ >> ${LOG}
			echo $(date '+%Y-%m-%d %H:%M:%S')	Не хватает атрибутов: ${A[*]} >> ${LOG}
			echo $(date '+%Y-%m-%d %H:%M:%S')	Перемещение файла в папку ${ERR} >> ${LOG}
			mv ${file} ${ERR}
			echo $(date '+%Y-%m-%d %H:%M:%S')	End ${file} >> ${LOG}
			mv ${LOG} ${ERRLOG}
			break;
	fi

	echo $(date '+%Y-%m-%d %H:%M:%S')	Проверка атрибутов прошла успешно. Переходим к импорту файла >> ${LOG}

	#IMPORT CSV TO DATABASE
	filepath=`pwd`/${file}
	headers=${headers// /,}
	resultfile=/tmp/importfile2jeventpayments_$(date +%s)
	psql -v v1="'$filepath'" -v v2="$headers" -d crm -U admin -f /home/admin/cron/bash/sql/importfile2jeventpayments.sql > ${resultfile} 2>&1
	error=`cat ${resultfile} | grep 'ERROR'`

	if [ ! -z "$error" ]
		#ОШИБКА ИМПОРТА
		then 
			echo $(date '+%Y-%m-%d %H:%M:%S')	ОШИБКА ИМПОРТА ФАЙЛА В БАЗУ ДАННЫХ >> ${LOG}
			echo \################################### >> ${LOG}
			echo `cat ${resultfile}` >> ${LOG}
			echo \################################### >> ${LOG}
			echo $(date '+%Y-%m-%d %H:%M:%S')	Перемещение файла в папку ${ERR} >> ${LOG}
			mv ${file} ${ERR}
			echo $(date '+%Y-%m-%d %H:%M:%S')	End ${file} >> ${LOG}
			mv ${LOG} ${ERRLOG}
	else
		echo $(date '+%Y-%m-%d %H:%M:%S')	ФАЙЛ УСПЕШНО ИМПОРТИРОВАН В БАЗУ ДАННЫХ >> ${LOG}
		echo $(date '+%Y-%m-%d %H:%M:%S')	Удаляем файл ${file} >> ${LOG}
		echo $(date '+%Y-%m-%d %H:%M:%S')	End ${file} >> ${LOG}
		rm -f ${file}
	fi

	rm -f ${resultfile}
done 

Наш sql файл

importfile2jeventpayments.sql

create temporary table t1 as 
select :v2
from contact.jeventpayments
where 1=2
;

COPY t1
FROM :v1 DELIMITER ',' CSV HEADER FORCE NOT NULL ref, action_id, order_id
;


insert into contact.jeventpayments
	(:v2)
select :v2
from t1
ON CONFLICT (ref,action_id,order_id)
DO UPDATE SET
status =EXCLUDED.status,
payment = EXCLUDED.payment,
paid = EXCLUDED.paid,
date_update = current_timestamp
;

В вкратце мы проверяем необходимые атрибуты, импортируем в БД, проверяем в /tmp файле наличие ошибки, если она есть - отправляем его содержимое в Log файл, исходный csv отправляем в errors/
Если ошибок нет - csv просто удаляем, в Log пишем успех

Проблема

  • Изначально таблицу я получаю в Excel от партнеров.
  • Правлю все поля, редактирую. Сохраняю как csv.
  • После этого в sublime text я делаю замену разделителей с ; на ,
    • Если в этот момент я сохраню файл - он не импортируется. Скрипт отругается, что не нашел последнее поле в файле
    • Если же я создам новый файл, вставлю в него данные, и сохраню с указанием расширения csv - такой файл будет обработан успешно.

Мне кажется что excel добавляет в конце полей невидимые символы-байты, которые понимает bash, но в текстовом редакторе они не видны. (Не помню как эти символы называются, какой-то дополнительный байт)

Если мы с вами поправим эту ошибку - будет готовое решение для других пользователей, с анализом полей и анализом успешности импорта в БД :slight_smile:

Привет, спасибо за кейс. Возможно это так называемый BOM или невидимые переносы строк, символы и тп.

Проверь кодировку файла:

file <file.csv>

image

Фыйл должен быть UTF-8

Удаление BOM:

Как в Sublime сохранить в UTF-8: