Инструменты пользователя

Инструменты сайта


windows:11:batch_file:sqlite:battery_database:maintenance_script

Скрипт для работы с базой данных батареек

DB Browser for SQLite, конечно, удобная оболочка для базы данных SQLite, но выполнять рутину… не, не то. Поэтому был написан этот скрипт…

А, за одно, так сказать, хорошенько размяты мозги. 8-O

Связанные статьи

Функционал

Скрипт позволяет извлекать из базы данных основные сведения по протестированным элементам питания и сохраняет их в отчет.

Окно скрипта для работы с базой данных.

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

Код скрипта

Код достаточно прокомментирован, поэтому добавить нечего.

database.cmd
@ECHO OFF

:: Written by Nikolay Soloshin (nikolay@soloshin.su) for SQLite version 3.50.4 @ 2025.08
 
@CHCP 65001 > NUL
 
TITLE Скрипт для работы с базой данных батареек.
COLOR 02
 
SET dbName=database.db
SET dbDir=%cd%
SET outFileName=report.txt
SET tempDir=%temp%\Batteries
SET logDir=logs

REM Проверяем наличие файла базы данных.
 
DIR "%dbDir%\%dbName%" > NUL
 
IF %errorlevel% == 1 (
 
ECHO.
ECHO Отсутствует файл базы данных "%dbName%" по пути "%dbDir%"!
ECHO.
ECHO Вы ищите,.. а мы пока выходим! (:
 
TIMEOUT 10 > NUL
GOTO END
 
)

REM Проверяем наличие или доступность файла "sqlite3.exe", который может находиться в папке со скриптом или быть доступным через переменную "PATH". Удобнее всего разместить его по пути "C:\Windows".
 
sqlite3.exe "%dbDir%\%dbName%" .databases > NUL
 
IF %errorlevel% EQU 9009 (
REM https://stackoverflow.com/questions/23091906/
 
ECHO.
ECHO Для работы этого скрипта необходим sqlite3.exe который по всей видимости отсутствует...
ECHO.
ECHO Файл должен находиться в папке с этим скриптом или быть доступным через переменную PATH.

REM Исторически не использую SETLOCAL в контексте всего скрипта, поэтому пришлось в этом месте повторно его добавить для успешного разрешения переменной. Т.е. это не ошибка, так должно быть. )
 
SETLOCAL ENABLEDELAYEDEXPANSION
 
ECHO.
CHOICE /c YN /m "Открыть страницу загрузки?"
 
IF !errorlevel! == 1 START https://sqlite.org/download.html
 
GOTO END
 
)
 
ECHO ВНИМАНИЕ! Используется файл базы данных "%dbDir%\%dbName%"!

:START
 
ECHO.
ECHO | SET /p = "Введите порядковый номер батарейки: "
 
SET /p batteryNumber=
SET tableName=%batteryNumber%

REM Ищем первую и ЕДИНСТВЕННУЮ таблицу - поэтому номера должны быть уникальны - , которая начинается с указанных выше цифр.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT name FROM sqlite_master WHERE name LIKE '%batteryNumber% %%' LIMIT 1;"
 
') DO SET "tableName=%%a"

REM Механизм определения наличия нужной таблицы в базе простой - если таблица есть, ее название записывается в переменную, если таблицы нет, то значение переменной остается прежним и равно номеру батарейки.
 
IF "%tableName%" == "%batteryNumber%" (
 
ECHO.
ECHO Таблица, соответствующая этому номеру, не найдена!
ECHO.
ECHO В базе данных имеются следующие таблицы:
ECHO.
sqlite3.exe "%dbDir%\%dbName%" "SELECT group_concat(name, ', ') FROM sqlite_master WHERE type = 'table'"
ECHO.
ECHO Цифры в начале являются сквозным порядковым номером батарейки.
 
GOTO START
 
)
 
ECHO.
ECHO | SET /p = "Выбрана таблица с названием "%tableName%". "
 
CHOICE /c YN /t 15 /d N /m "Продолжаем?"
 
IF %errorlevel% == 2 GOTO START

REM Проверяем, есть ли вначале таблицы не усеченные данные - в данном контексте мы считаем, что напряжение не может быть меньше 0.5 вольта, т.е., по сути, это обозначает, что батарейка не была еще вставлена.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT `Voltage(V)` FROM `%tableName%` LIMIT 1;"
 
') DO SET "firstVolt=%%a"
 
IF "%firstVolt%" lss "0.5" (

REM Это костыль, чтобы в контексте "SETLOCAL ENABLEDELAYEDEXPANSION" можно было отображать восклицательный знак, епт.
 
SET exclamationMark=!

REM Да, выше, это костыль... https://forum.script-coding.com/viewtopic.php?pid=41425#p41425
 
SETLOCAL ENABLEDELAYEDEXPANSION
 
ECHO.
ECHO | SET /p = "ВНИМАНИЕ^! Вероятно в таблице имеются не обрезанные данные^! "
 
CHOICE /c YN /t 15 /d N /m "Усечь таблицу?"
 
IF !errorlevel! == 1 (
 
IF NOT EXIST %tempDir% MKDIR %tempDir%

REM Делаем единственную резервную копию.
 
COPY /Y "%dbDir%\%dbName%" "%tempDir%\%dbName%.bak" > NUL
 
IF !errorlevel! == 1 (
 
ECHO.
ECHO ВНИМАНИЕ!exclamationMark! При попытке сделать резервную копию произошла ошибка. Выходим.
 
TIMEOUT 5 > NUL
GOTO END
 
)
 
IF NOT EXIST %logDir% MKDIR %logDir%

REM С этого места начинаем писать лог в лог. ))

REM Для удобства... - да, мне так удобнее! - текст, если он повторяется несколько раз, я зачастую переношу в переменную.
 
SET textString=Резервная копия находится в
 
ECHO.
ECHO !textString! "%tempDir%".
ECHO !textString! "%tempDir%\%dbName%.bak" > %logDir%\%batteryNumber%-truncate.txt
 
ECHO.>> %logDir%\%batteryNumber%-truncate.txt
ECHO Время создания копии: %date%%time% >> %logDir%\%batteryNumber%-truncate.txt
ECHO.>> %logDir%\%batteryNumber%-truncate.txt
ECHO ВАЖНО!exclamationMark! Резервные копии перезаписываются!exclamationMark! >> %logDir%\%batteryNumber%-truncate.txt

REM Вычисляем середину таблицы, учитывая только время теста. Это необходимо, чтобы при обрезании сначала, не обрезать в конце и наоборот. Да, схема не надежная и, если были аномалии при тестировании батарейки, может съедаться лишнее, но ничего более другого гггг я пока не придумал и в подавляющем большинстве случаев оно работает прекрасно.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ( ( SELECT ROWID FROM `%tableName%` WHERE `Current(A)` > 0.001 ORDER BY `Timestamp(s)` DESC LIMIT 1 ) - ( SELECT ROWID FROM `%tableName%` WHERE `Current(A)` > 0.001 LIMIT 1 ) ) / 2;"
 
') DO SET "middleRowid=%%a"

REM В переменную currentLine записываются данные для вывода на экран и в лог. В данном случае из базы извлекается первая, еще не усеченная, строка.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ROWID, * FROM `%tableName%` LIMIT 1;"
 
') DO SET "currentLine=%%a"
 
ECHO.
ECHO Удаляем строки, которые меньше 1 вольта, из начала таблицы.
 
sqlite3.exe "%dbDir%\%dbName%" "DELETE FROM `%tableName%` WHERE `Voltage(V)` < 1 AND ROWID < !middleRowid!;"
 
SET textString=Изменения в начале файла.
 
ECHO.
ECHO !textString!
ECHO.>> %logDir%\%batteryNumber%-truncate.txt
ECHO !textString! >> %logDir%\%batteryNumber%-truncate.txt
 
ECHO Было: "!currentLine!"
ECHO Было: "!currentLine!" >> %logDir%\%batteryNumber%-truncate.txt

REM Теперь извлекаем новую первую строчку.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ROWID, * FROM `%tableName%` LIMIT 1;"
 
') DO SET "currentLine=%%a"
 
ECHO Стало: "!currentLine!"
ECHO Стало: "!currentLine!" >> %logDir%\%batteryNumber%-truncate.txt

REM Тут извлекаем последнюю.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ROWID, * FROM `%tableName%` ORDER BY `Timestamp(s)` DESC LIMIT 1;"
 
') DO SET "currentLine=%%a"
 
ECHO.
ECHO Удаляем строки, которые меньше 0.9 вольта, из конца таблицы.
 
sqlite3.exe "%dbDir%\%dbName%" "DELETE FROM `%tableName%` WHERE `Timestamp(s)` >= ( SELECT `Timestamp(s)` FROM `%tableName%` WHERE `Voltage(V)` < 0.9 AND ROWID > !middleRowid! LIMIT 1 );"
 
ECHO Удаляем строки, которые меньше 0.001 ампера, из конца таблицы.
 
sqlite3.exe "%dbDir%\%dbName%" "DELETE FROM `%tableName%` WHERE `Timestamp(s)` >= ( SELECT `Timestamp(s)` FROM `%tableName%` WHERE `Current(A)` < 0.001 AND ROWID > !middleRowid! LIMIT 1 );"
 
SET textString=Изменения в конце файла.
 
ECHO.
ECHO !textString!
ECHO.>> %logDir%\%batteryNumber%-truncate.txt
ECHO !textString! >> %logDir%\%batteryNumber%-truncate.txt
ECHO Было: "!currentLine!"
ECHO Было: "!currentLine!" >> %logDir%\%batteryNumber%-truncate.txt

REM И, наконец, новую последнюю! Эти строчки пойдут в отчет для косвенного визуального определения правильности усечения таблицы.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ROWID, * FROM `%tableName%` ORDER BY `Timestamp(s)` DESC LIMIT 1;"
 
') DO SET "currentLine=%%a"
 
ECHO После "!currentLine!"
ECHO Стало: "!currentLine!" >> %logDir%\%batteryNumber%-truncate.txt

REM Выполняем несколько команд в одном подключении. Во-первых, подключаем резервную копию базы данных, где эта таблица еще в первозданном виде. Во-вторых, удаляем временную таблицу "exceptionTable", если вдруг она осталась по ошибке. В-третьих, создаем таблицу заново и заполняем ее только теми строками, которые были ранее удалены.
 
sqlite3.exe "%dbDir%\%dbName%" "ATTACH DATABASE `%tempDir%\%dbName%.bak` as backup" "DROP TABLE IF EXISTS exceptionTable;" "CREATE TABLE exceptionTable AS SELECT ROWID, `Timestamp(s)`, `Voltage(V)`, `Current(A)`, mAh from backup.`%tableName%` EXCEPT SELECT ROWID, `Timestamp(s)`, `Voltage(V)`, `Current(A)`, mAh from `%tableName%`;"
 
SET textString=Итого были удалены следующие диапазоны:
 
ECHO.
ECHO !textString!
 
set tempFile=%tempDir%\tempOutput.txt

REM Вычисляем и выводим связанные диапазоны из таблицы "exceptionTable" во временный файл, т.к. нельзя передать в переменную многострочный вывод.
 
sqlite3.exe "%dbDir%\%dbName%" ".mode tabs" "SELECT MIN(ROWID) AS MIN, MAX(ROWID) AS MAX FROM ( SELECT ROWID, SUM(CASE WHEN previousValue IS NULL OR ROWID - previousValue > 1 THEN 1 ELSE 0 END) OVER (ORDER BY ROWID) AS groupId FROM ( SELECT ROWID, LAG(ROWID, 1, NULL) OVER (ORDER BY ROWID) AS previousValue FROM exceptionTable ) ) GROUP BY groupId;" > !tempFile!

REM Построчно выводим содержимое файла на экран.
 
FOR /f "delims=" %%a IN ('
 
TYPE "!tempFile!"'
 
) DO ECHO %%a
 
ECHO. >> %logDir%\%batteryNumber%-truncate.txt
ECHO !textString! >> %logDir%\%batteryNumber%-truncate.txt

REM Это костыль! Если не менять каталог, а писать, как везде, то в каталоге с логом ничего не добавляется командой copy, зато появляется дубль файла на этом уровне... 
 
CD %logDir%

REM Копируем данные по диапазонам из временного файла в отчет.
 
COPY /a %batteryNumber%-truncate.txt + !tempFile! > NUL
 
CD %dbDir%
 
DEL !tempFile!
 
SET textString=Диапазонов должно быть ВСЕГО ДВА - один сначала, другой с конца
 
ECHO !textString!!exclamationMark!
ECHO !textString!!exclamationMark! >> %logDir%\%batteryNumber%-truncate.txt
 
SET textString=Если это не так, то необходимо восстановить состояние из резервной копии и вручную обрезать таблицу.
 
ECHO !textString!
ECHO !textString! >> %logDir%\%batteryNumber%-truncate.txt
 
SET textString=Перечень удаленных строк
 
ECHO.>> %logDir%\%batteryNumber%-truncate.txt
ECHO !textString!. >> %logDir%\%batteryNumber%-truncate.txt

REM Добавляем в отчет сами удаленные строки.
 
sqlite3.exe "%dbDir%\%dbName%" ".mode column" "SELECT * FROM exceptionTable;" >> %logDir%\%batteryNumber%-truncate.txt
 
sqlite3.exe "%dbDir%\%dbName%" ".mode column" "DROP TABLE IF EXISTS exceptionTable;"
 
ECHO.
ECHO !textString! и прочие данные сохранены в файл
ECHO "%logDir%\%batteryNumber%-truncate.txt"
ECHO.
ECHO Постройте график и проверьте связность данных, чтобы не было усечено лишнего...
 
)
 
ECHO.
CHOICE /c YN /m "Извлечь данные по току, емкости и времени из таблицы?"
 
IF !errorlevel! == 2 GOTO END
 
ENDLOCAL
 
)
 
CLS
 
ECHO Результирующие данные для батарейки #%batteryNumber%.
ECHO.
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT AVG( `Current(A)` ) * 1000 FROM `%tableName%`;"
 
') DO SET "avgCurrent=%%a"
 
ECHO Средний ток (mA):	%avgCurrent:.=,%
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT mAh / 1000 AS Ah FROM `%tableName%` ORDER BY "mAh" DESC LIMIT 1;"
 
') DO SET "totalCapacity=%%a"
 
ECHO Итоговая емкость (Ah):	%totalCapacity:.=,%
 
FOR /f "delims=" %%a IN ('
 
sqlite3.exe "%dbDir%\%dbName%" "SELECT ( ( SELECT `Timestamp(s)` FROM `%tableName%` ORDER BY "mAh" DESC LIMIT 1 ) - ( SELECT `Timestamp(s)` FROM `%tableName%` WHERE `Current(A)` >= 0.1 LIMIT 1 ) ) / 60 AS totalTime;"
 
') DO SET "dischargeTime=%%a"
 
ECHO Время разряда (m):	%dischargeTime:.=,%
 
ECHO.
CHOICE /c YN /m "Сохранить данные в файл?"
 
IF %errorlevel% == 1 (
 
ECHO Результат для батарейки #%batteryNumber%.>> %outFileName%
ECHO Средний ток:		%avgCurrent:.=,%>> %outFileName%
ECHO Итоговая емкость:	%totalCapacity:.=,%>> %outFileName%
ECHO Время разряда:		%dischargeTime:.=,%>> %outFileName%
ECHO.>> %outFileName%
 
ECHO.
ECHO Готово! 
 
)
 
ECHO.
CHOICE /c YN /m "Извлечь данные по другой батарейки?"
 
IF %errorlevel% == 1 (
 
CLS
GOTO START
 
)

:END

:: With love from Vladivostok.

Примеры

Пример базы отчета, лога и базы можно загрузить по ссылке или посмотреть ниже по тексту.

Таблица

Таблица и ее название в базе данных должны соответствовать этому запросу.

CREATE TABLE "13 USB_Meter_Log_20250809_113316" (
	"Timestamp(s)"		REAL,
	"Voltage(V)"		REAL,
	"Current(A)"		REAL,
	"Power(W)"		REAL,
	"Temperature(C)"	REAL,
	"DP(V)"			REAL,
	"DN(V)"			REAL,
	"mAh"			REAL
);

Отчет

Результат для батарейки #15.
Средний ток:		104,831240369347
Итоговая емкость:	0,502114159987279
Время разряда:		286,555833333333
 
Результат для батарейки #13.
Средний ток:		102,445275133283
Итоговая емкость:	0,30312820364156
Время разряда:		176,977833333333

Лог

Резервная копия находится в "C:\Users\NSOLOS~1\AppData\Local\Temp\Batteries\database.db.bak" 
 
Время создания копии: 16.08.202512:16:07,56 
 
ВАЖНО! Резервные копии перезаписываются! 
 
Изменения в начале файла. 
Было: "1|2.26|0.19951|0.0|0.0|33.2|1.436|1.592|0.0" 
Стало: "17|18.49|1.6495|0.0|0.0|33.3|1.432|1.589|0.00041327071984609" 
 
Изменения в конце файла. 
Было: "26422|26796.99|1.29099|0.00027|0.0003485673|31.8|1.547|1.6|521.556238536586" 
Стало: "17019|17260.51|0.90056|0.10461|0.0942075816|31.6|1.549|1.598|502.114159987279" 
 
Итого были удалены следующие диапазоны: 
1	16
17020	26422
Диапазонов должно быть ВСЕГО ДВА - один сначала, другой с конца! 
Если это не так, то необходимо восстановить состояние из резервной копии и вручную обрезать таблицу. 
 
Перечень удаленных строк. 
rowid  Timestamp(s)  Voltage(V)  Current(A)  mAh                 
-----  ------------  ----------  ----------  --------------------
1      2.26          0.19951     0.0         0.0                 
2      3.54          0.19983     0.0         0.0                 
3      4.77          0.19951     0.00018     6.16201162338257e-05
4      5.3           0.19936     0.00015     8.37201476097107e-05
...
13     14.43         0.19983     0.00027     0.000371020652850469
14     15.44         0.20014     0.0         0.000371020652850469
15     16.46         0.20061     0.00015     0.00041327071984609 
16     17.47         0.19889     0.0         0.00041327071984609 
17020  17261.52      0.89994     0.10464     502.143633647875    
17021  17262.54      0.89994     0.10464     502.173107294611    
17022  17263.55      0.90119     0.10455     502.202555591307    
17023  17264.57      0.90135     0.10471     502.232048961676    
...
26419  26793.95      1.29006     0.00015     521.556162486447    
26420  26794.97      1.28959     0.0         521.556162486447    
26421  26795.98      1.29052     0.0         521.556162486447    
26422  26796.99      1.29099     0.00027     521.556238536586

Дисклеймер

  • Использование материалов данной базы знаний разрешено на условиях лицензии, указанной внизу каждой страницы! При использовании материалов активная гиперссылка на соответствующую страницу данной базы знаний обязательна!
  • Автор не несет и не может нести какую либо ответственность за последствия использования материалов, размещенных в данной базе знаний. Все материалы предоставляются по принципу «как есть». Используйте их исключительно на свой страх и риск.
  • Все высказывания, мысли или идеи автора, размещенные в материалах данной базе знаний, являются исключительно его личным субъективным мнением и могут не совпадать с мнением читателей!
  • При размещении ссылок в данной базе знаний на интернет-страницы третьих лиц автор не несет ответственности за их техническую функциональность (особенно отсутствие вирусов) и содержание! При обнаружении таких ссылок, можно и желательно сообщить о них в комментариях к соответствующей статье.

Обсуждение

Ваш комментарий:
T M N X O Q O Z Q G E X F J M L
 
Последнее изменение: 2025/08/16 15:48 — Николай Солошин