Loading, Manipulating, Recovering and Moving Long Term Statistics in Home Assistant

If you’ve had occasions when Home Assistant (HA) hasn’t been running, or you’ve had to recover from an earlier HA backup, or you’ve introduced new entities to replace existing entities, or you simply want to load historical data into Home Assistant such as for the Energy dashboard, then this thread covers the key approaches I developed to manipulate my HA entity statistics.

The headline is I have created 6 different scenarios of loading/manipulating Home Assistant history from different sources, and two for fixing data glitches.

All of these were created principally because I wanted to sort out my own Energy dashboard which had gaps, spikes and had suffered from bloat as I’d changed approach to how to record different tariff information.

Where I have ended up is an Energy dashboard that has import, export, solar and battery charge/discharge right back to when I first had the inverters installed (9 months before I started using HA), and all the spikes and glitches I’d had including trashing and re-creating the HA database about a month in, all resolved.

Note this focuses on statistics in Home Assistant and not entity history. Entity history is ephemeral and will be automatically purged by HA from the recorder database after (by default) 10 days of retention. Home Assistant statistics contain your ‘forever’ history, summarised from the HA entity history.

Firstly take a read of the HA explanation of what statistics are, this covers both short-term and long-term statistics (STS and LTS respectively) Long- and short-term statistics | Home Assistant Data Science Portal

Mostly we will focus on LTS although as explained in scenario 5 we do sometimes need to manipulate STS as well.

Its important to understand the different statistics types for STS and LTS, ‘measurement entities’ such as power readings and ‘integrated over time entities’ such as utility meters, today energy and total energy sensors.

I’m going to focus primarily on sensors that I use that underpin the HA Energy dashboard as this is where I make the most use of LTS. The Energy board displays ‘integrated over time’ entity data, but will later on show how the techniques can be used for measurement sensors.

As explained in the HA article, ‘measurement over time’ entities have a ‘state’ value and a ‘sum’ value, captured every hour in the LTS (and every 5 minutes in the STS). The Energy dashboard uses the ‘sum’ figures whereas a history graph uses the ‘state’ figures so usually both need to be updated to ensure the LTS and any graphs you want to look at can be used everywhere in HA.

To show how to adjust LTS I will use some real examples of my own, starting with from 21:00 on 19th January through to 22:00 on 20th January where HA stopped recording my Long Term Statistics.

I’ll cover different scenarios that can be applied to other similar requirements to recreate the LTS.

In the Energy dashboard you can see that the the FIT solar, import, battery charge and discharge all appear as single big lump sums at 6pm which is when I got the statistics collection working again.

Solar G and H appear correctly hour by hour (but that’s only because I had manually entered the stats for each hour of the day). The methods laid out below make this a lot easier and quicker.

I have created 6 different scenarios of loading Home Assistant history from different sources, and two for fixing data glitches:

  1. Load statistics from entity state history
  2. Load statistics from the GivEnergy portal data
  3. Bulk load (multiple days) of statistics from the GivEnergy portal data
  4. Load statistics from Octopus Energy import/export data
  5. Move statistics from one entity to another
  6. Move statistics from a ‘today’ entity to a ‘total’ entity
  7. Resolve data glitches and spikes in statistics data
  8. Resolving glitches in HA statistics data using DB Browser

I live in the UK, my Energy supplier is Octopus Energy and I have two GivEnergy inverters so unashamedly my procedures do leverage those services in Scenarios 2-4, but even if your setup is different you might get ideas from what I have done that you can use on your own setup.

Scenarios 1 and 5 through to 8 are pure Home Assistant so anybody should be able to use them.

They all have a bit of SQL commands in them, scenarios 1-4 make use of an integration to do the data loading, scenarios 5-7 are more SQL heavy but this shouldn’t put you off, I’ve detailed the steps to follow and if you take it methodically and carefully it should be OK.

I do recommend that you read the scenarios in order because whilst I tried to make them standalone, they do build one upon another and later scenarios build upon techniques from earlier scenarios.

I also strongly strong recommend that you take a backup before you start manipulating the HA data, and if possible trial the steps you are going to follow on a test HA server (that you can restore from the production backup).

With SQL it is VERY EASY to get the commands wrong and cause havoc to your database. There is NO UNDO on SQL so you must take a backup, check and double check what you are doing. Use SELECT’s first to be absolutely sure you are targeting the right records and double check everything before you execute the command.
The scenarios that use the Import Statistics integration can be run and re-run multiple times so if you get the data wrong then you can correct it and re-run the upload. Of course if you overwrite the wrong data, you’ll have to revert to a prior backup.

A few useful links (read the scenarios for details of how they fit in):

15 Likes

Scenario 1: load LTS from entity state history

In this example we have an entity with the correct state history, but the LTS are for whatever reason incorrect. The Energy dashboard exclusively uses LTS so we have to ‘copy’ the entity history into LTS.

As this relies on having the entity history it can only be followed for a short period of time (default 10 days) before the entity history is purged from HA.

  1. Firstly install the Import Statistics HACS integration GitHub - klausj1/homeassistant-statistics: This HA integration allows to import long term statistics from a file like csv or tsv

Simply search for ‘statistics’ in HACS and install the integration

Reboot HA

Then go back to the Import Statistics integration in HACS and click ‘add integration to HA’, or click the link Link to Integrations: add integration – My Home Assistant

(older versions of the integration required configuration.yaml to be edited, this is no longer required)

  1. On the History view in HA, select the date/time range you require and select the entity required. Make sure the start and end times include and overlap existing statistics data that you have correctly recorded (and appearing in the energy dashboard).

  2. Click three dots, download data. This will create a CSV file of the entity history shown onscreen with every state change for the entity, e.g.

entity_id,state,last_changed
sensor.fit_solar_energy_total,3308.0432,2025-01-20T21:01:06.047Z
sensor.fit_solar_energy_total,3308.0447,2025-01-20T21:03:05.050Z
sensor.fit_solar_energy_total,3308.0450,2025-01-20T21:05:03.391Z
...
sensor.fit_solar_energy_total,3308.0457,2025-01-20T21:57:12.498Z
sensor.fit_solar_energy_total,3308.0461,2025-01-20T21:59:10.331Z
sensor.fit_solar_energy_total,3308.0463,2025-01-20T22:01:10.359Z
sensor.fit_solar_energy_total,3308.0465,2025-01-20T22:03:12.448Z
...
sensor.fit_solar_energy_total,3308.0468,2025-01-19T22:57:08.558Z
sensor.fit_solar_energy_total,3308.0470,2025-01-19T22:59:05.564Z
sensor.fit_solar_energy_total,3308.0475,2025-01-19T23:01:05.601Z
sensor.fit_solar_energy_total,3308.0479,2025-01-19T23:03:05.612Z

Whilst this contains the data we need it is far too granular a set of state changes. LTS captures a data snapshot every hour (not every state change) but this will automatically be resolved in step 6 below.

Save the file and name it appropriately.

  1. Calculate the Unix timestamps for the date/times of the first and last hours in the file.

In my example the first record is ‘2025-01-19T21:59:15’ so the date/time we are going to convert is ‘19.01.25 21:00:00’, and the last record in the file is ‘2025-01-20T22:58:15’ so the date/time is ‘20.01.25 22:00:00’

Note: the date and time must be precisely formatted as shown, its only the hours that is required to be filled in, the minutes are always zero and the milliseconds are not required.

Developer tools/template

{% set ds="19.01.2025 21:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="20.01.2025 22:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the unix timestamps for the start and end as 1737320400 and 1737410400 respectively.

  1. Query the existing LTS for the range of records we are going to be updating.

SQLweb add-on. Paste in the following query, substituting your own entity id, start and end timestamps (from above step):

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.fit_solar_energy_total'  -- entity id
AND start_ts >= 1737320400  -- start time stamp
AND start_ts <= 1737410400  -- end time stamp
ORDER BY start_ts

You should get a result set looking something like this. In my case as recorder wasn’t running there are very few records.

We need to make a note of the ‘sum’ value corresponding to the start time (first) record. In my case is it 268.982599999.

If you want to convert the date/time of any of the records to printable format, copy the start_ts into this developer tools/template code fragment:

{% set x=1737320400.0 %}
{{x|timestamp_custom('%Y-%m-%d %H:%M')}}
  1. Reformat the CSV file to format required by the Import Statistics integration.

To make it easier and repetitive I have created a Google Sheets spreadsheet to do this.

Open the spreadsheet and click File/Make a Copy to save a copy in your own Google account. You won’t be able to edit my copy!

Navigate to the ‘Entity History CSV’ tab.

Open the CSV file created in step 4, select all and copy the entire file contents, then paste it into column B of ‘Entity History CSV’ tab on the spreadsheet.
If you have more input CSV rows than are pre-populated in the spreadsheet, simply copy cells C3 through to K3 into the matching cells of any additional rows you need in the spreadsheet.

Paste the ‘sum’ value for the first record obtained from the SQL query in step 6 into cell I2 (268.982599999 in my example).

The cells you are pasting data into are coloured pale red.

  1. The spreadsheet will split up and then reformat the CSV data pasted in column B and I2, producing the correctly formatted output data in column K (coloured green).

The state change CSV contains a record for every state change, often more than one an hour. LTS only contains a single state change for each hour so Column G reformats the state change time into the start time for each history record, and automatically populates it only on the last state change of each hour. Any state values of ‘unknown’ will be automatically excluded.

Select and copy cells K1 downwards to the end of your data, and paste the reformatted data into a new text file which will look something like this:

statistic_id|unit|start|state|sum
sensor.fit_solar_energy_total|kWh|19.01.2025 21:00|3308.0461|268.9826


sensor.fit_solar_energy_total|kWh|19.01.2025 22:00|3308.047|268.9835


sensor.fit_solar_energy_total|kWh|19.01.2025 23:00|3308.05|268.9865


sensor.fit_solar_energy_total|kWh|20.01.2025 00:00|3308.054|268.9905


sensor.fit_solar_energy_total|kWh|20.01.2025 01:00|3308.058|268.9945

There will be a number of blank lines between each hourly record, corresponding to intermediate state changes within the hour. These are fine as they are ignored on loading.

Save this file and name it appropriately.

  1. Copy the file created and saved in step 8 into the /config folder of your Home Assistant server (use Samba share, HA file editor, SSH, or whatever you prefer).

  2. Recommended that you take a backup of HA before you load the new LTS. Just in case !

  3. In HA, go to Developer Tools/ Actions and search for ‘import_statistics’

Fill in the UI as follows:

filename: name of the file copied into /config in step 9
timezone_identifier: Europe/London
delimiter: | (the vertical pipe symbol)

And click on Perform Action

  1. View the updated statistics by re-running the query from step 5 above, developer tools/statistics or viewing the energy dashboard

Scenario 2: load LTS from GivEnergy Portal data

In this example for whatever reason (e.g. GivTCP problem) the HA entity doesn’t have the correct history, but the data item is one of the inverter data elements that the GivEnergy Portal captures - PV Generation, Consumption, Export, Import, Battery Charge or Battery Discharge.

As long as your inverter is online to the internet the GivEnergy Portal captures this data every 5 minutes for every day since your inverter was commissioned.

We need to extract the data from the GivEnergy portal, reformat it and load it into LTS.

I’m going to use PV Generation Total to show the process, which is the same for Imported and Exported Energy.
On my Gen 1 GivEnergy inverter the GivEnergy portal doesn’t record Total Battery Charge/Discharge so we have to use the portal Today Charge/Discharge data, but otherwise the process below is the same (the spreadsheet adapts to the ‘today’ data).

  1. Install the Import Statistics HACS integration as described in scenario 1 above.

  2. In the GivEnergy portal, click on Inverter Data (the grid symbol), then Meter Data, select the date you require and click the download button to download a CSV file for that day. You’ll have to repeat and download each day individually, there is no mechanism (in the portal) to download a range of dates - but see scenario 3 for how to bulk load LTS from the GivEnergy Portal.

  3. Calculate the Unix timestamps for the date/times of the time range that you want to correct, selecting times that overlap by an hour or two the bad data time range.

If you are loading data into a ‘today’ entity then to get the daily totals right, you should always use 00:00 as the start time.

In my example I am loading ‘PV data total’ so I don’t have to choose a :00 time. LTS stopped being recorded at 22:00 on 19th January and was resumed at around 19:00 on 20th January so I will correct the data of a time range from 20:00 on 19th January to 21:00 on 20th January (adding a couple of hours either side).

Note: the date and time must be precisely formatted as shown, its only the hours that is required to be filled in, the minutes are always zero and the milliseconds are not required.

Developer tools/template

{% set ds="19.01.2025 20:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="20.01.2025 21:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the unix timestamps for the start and end as 1737316800 and 1737406800 respectively.

  1. Query the existing LTS for the range of records we are going to be updating.

SQLweb add-on. Paste in the following query, substituting your own entity id, start and end timestamps (from above step):

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.givtcp_INVID_pv_energy_total_kwh'  -- entity id
AND start_ts >= 1737316800 -3600  -- start time stamp minus 1 hour before
AND start_ts <= 1737406800        -- end time stamp
ORDER BY start_ts

We need to make a note of the ‘sum’ and ‘state’ values from the first record - this is the LTS record an hour immediately before the start time record. In my case they are 3371.5 and 6453.9 respectively.

If you want to convert the date/time of any of the records to printable format, copy the start_ts into this developer tools/template code fragment:

{% set x=1737320400.0 %}
{{x|timestamp_custom('%Y-%m-%d %H:%M')}}

Also, look at the entity history with the normal HA history graph and this will show the entity state jumping up, down or flatlining for the period of the statistics problem.

  1. Next job is to split the CSV file into columns and extract the column of data we want to import into LTS.
    To make it easier and repetitive I have created a Google Sheets spreadsheet to do this.

Open the spreadsheet HA LTS reformatter - Google Sheets and click File/Make a Copy to save a copy in your own Google account.

Navigate to the ‘GivEnergy Portal CSV’ tab.

Open the CSV file created in step 3 in a file editor, select all and copy the entire file contents, then paste it into column B of ‘GivEnergy Portal CSV’ tab on the spreadsheet, ensuring the header data is pasted into row 1.

If you want to include multiple days of data to correct then paste the next day of data into column B underneath the first day, and repeat for any subsequent days. You don’t need to delete the header row from the second and subsequent days as these will be sorted to the bottom of the table.

If you have more input CSV rows than are pre-populated in the spreadsheet, simply copy cells C3 through to BX3 into the matching cells of any additional rows you need in the spreadsheet.

Make sure that the last row after the data is blank and doesn’t contain ‘#VALUE!’ error.

Select column C, click Data/Sort sheet/Sort sheet by Column C (A to Z)

Column C now looks something like this:

Time
2025-01-19 00:01:50
2025-01-19 00:06:51
2025-01-19 00:11:54
2025-01-19 00:16:55
  1. Edit the spreadsheet and remove rows not required

Delete all rows from row 2 downwards before the start time of the time period you wish to correct.
An easy way to do this to delete the first row (right mouse click, delete row), then click anywhere in the next row to remove and press control Y to repeat the last command. Keep positioning the cursor and pressing control Y to repeat the delete row command for each row in turn.

Paste the following formula in cell BN2:
=if(mid(C3,12,2)<>mid(C2,12,2),mid(C2,9,2)&“.”&mid(C2,6,2)&“.”&mid(C2,1,4)&" “&mid(C2,12,2)&”:00",“”)

Then copy cell BN2 and paste it into cell BN3, BN4, etc all the way to the bottom of the data set.

The GivEnergy portal contains the data we need but it is far too granular as the GivEnergy Portal captures data every 5 minutes for each day, and LTS holds a data snapshot just once every hour.

Since the spreadsheet has been sorted into date/time order, the above formula will ensure that the date time only appears in column BN for the last state change within each hour - which is what we want.

Finally, delete any rows after the last record in the hour of the time range to be corrected and ensure that there is a blank row after the data set.

So column BN now looks something like this:

19.01.25 20:00

...
19.01.25 21:00

...
19.01.25 22:00

...
  1. Next we reformat the spreadsheet data into the precise format that the Import Statistics integration requires.

Find the column of data you want to extract (e.g. PV Generation Total which for me is column T) and copy the entire column into column BL.
For my inverter the portal doesn’t contain Battery Discharge Total or Charge Total figures, only a Battery Total Throughput figure which isn’t a lot of use. We can use the Battery Discharge/Charge Today column instead to load the revised Total figures and the spreadsheet will automatically cope with that.

The cells you are pasting data into are coloured pale red.

Before you move on, its worth visually checking that there haven’t been any glitches in the GivEnergy Portal data because if there are, this messes up the formulae. Visually scan down column BL and check that the values make sense; if its a ‘total’ column that the values keep incrementing, or if its a ‘today’ column that the values increment from 0 at midnight up to a end of day value, then reset to 0 at the next midnight.
When I was doing a bulk load of missing history from the GivEnergy portal (scenario 3 below) I found one data glitch:

date	solar today	solar total
01/09/2023 21:33:19	10.8	3423.1
01/09/2023 21:39:24	10.8	3423.1
01/09/2023 22:10:21	10.7	3423
01/09/2023 22:10:31	10.7	3423
...
01/09/2023 23:50:44	10.7	3423
01/09/2023 23:55:39	10.7	3423
02/09/2023 00:00:41	0	3423
02/09/2023 00:03:18	0	3423
...
02/09/2023 06:28:07	0	3423
02/09/2023 06:33:08	0	3423
02/09/2023 06:38:10	0.1	3423.1
02/09/2023 06:43:12	0.1	3423.1
02/09/2023 06:48:14	0.1	3423.1

As you can see solar today dropped from 10.8 at 21:39:24 to 10.7 at 22:10:21, remained there until the midnight reset when it then climbed as expected through the next day.
Similarly solar total dropped from 3423.1 to 3423 and stayed there until the start of solar generation the next day at 06:38.
This shouldn’t happen, today and total values should increment upwards not go down. The fix is to edit column BL of the spreadsheet and correct the values, e.g. to:

date	solar today	solar total
01/09/2023 21:33:19	10.8	3423.1
01/09/2023 21:39:24	10.8	3423.1
01/09/2023 22:10:21	10.8	3423.1
01/09/2023 22:10:31	10.8	3423.1
...
01/09/2023 23:50:44	10.8	3423.1
01/09/2023 23:55:39	10.8	3423.1
02/09/2023 00:00:41	0	3423.1
02/09/2023 00:03:18	0	3423.1
...
02/09/2023 06:28:07	0	3423.1
02/09/2023 06:33:08	0	3423.1
02/09/2023 06:38:10	0.1	3423.1
02/09/2023 06:43:12	0.1	3423.1
02/09/2023 06:48:14	0.1	3423.1

This does mean that the today figure for 2/9/23 was 0.1kWh higher than the change in total value for that day, but I can live with that. There were similar small backwards jumps at the same time on battery discharge and today consumption.
Glitch in the matrix.

Paste the entity id we are going to correct into cell BP1, e.g. sensor.givtcp_INVID_pv_energy_total_kwh

Type the unit of measurement for the entity in cell BR1, e.g. kWh (or Wh)

Paste the ‘state’ value for the first record obtained from the SQL query in step 3 into cells BT1 AND BT2, e.g. 6453.9
Paste the ‘sum’ value for the first record obtained from the SQL query in step 3 into cells BV1 AND BV2, e.g. 3371.49999999999

If you are loading a GivEnergy portal ‘total’ value or a portal ‘today’ value into a Home Assistant total entity, then in cell BT3 paste the following formula:
=BT2+BM3-if(BM3>=BM2,BM2)

If however you are loading a GivEnergy portal ‘today’ value into a Home Assistant ‘today’ entity, then in BT3 paste the following:
=BM3

Then (in both cases), copy cell BT3 and paste it into cell BT4, BT5, etc all the way to the bottom of the data set.

In cell BV3 paste the following formula (same formula regardless of source and destination types):
=BV2+BM3-if(BM3>=BM2,BM2)

Then copy cell BV3 and paste it into cell BV4, BV5, etc all the way to the bottom of the data set.

The spreadsheet will reformat the CSV data, producing the correctly formatted output data in column BX (coloured green). There will be blank rows in BX where the source data row was from a 5-minute change on the portal, but these will be ignored during the data load.

Select and copy cells BX1 downwards to the end of your data, and paste the reformatted data into a new text file which will look something like this:

statistic_id|unit|start|state|sum

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|19.01.2025 20:00|6453.9|3371.5

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|19.01.2025 21:00|6453.9|3371.5

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|19.01.2025 22:00|6453.9|3371.5

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|19.01.2025 23:00|6453.9|3371.5

Its worth a double-check of the file contents to make sure that data looks sensible. The first record should match the state and sum from the database record start_ts of the starting hour, state and sum should increment over time down the file (with state resetting to 0 if its a today sensor). The last record should have a state value that matches the end time in the database query but the sum value may show a jump if the entity wasn’t recording history in HA during the time period - step 12 covers resolving this jump.

If everything looks OK and there’s no gross errors then save this file and name it appropriately.

  1. Copy the file created and saved in step 7 into the /config folder of your Home Assistant server (use Samba share, HA file editor, SSH, or whatever you prefer).

  2. Recommended that you take a backup of HA before you load the new LTS. Just in case !

  3. In HA, go to Developer Tools/ Actions and search for ‘import_statistics’

Fill in the UI as follows:

filename: name of the file copied into /config in step 8
timezone_identifier: Europe/London
delimiter: | (the vertical pipe symbol)

And click on Perform Action

  1. View the updated statistics by re-running the query from step 4 above, viewing the energy dashboard and looking at the entity state history in the history graph for the selected date/time period.

  2. Dealing with additional Energy dashboard spike after the loaded history

Having followed the above process and successfully loaded the entity state and sum, it may well be that there is a new spike showing in the Energy dashboard that appears immediately after the history load. This is due to the sum value not incrementing correctly across the transition from the replaced history to the existing history records.

I had this problem with my battery charge, discharge and grid import sensors, and is best seen by looking at the data through SQL web.

Repeat the SQL query from step 4 but expand the ‘start_ts where clauses’ to include a few more records either side, i.e.:

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.grid_import_today_day'  -- entity id
AND start_ts >= (1737316800 - (3*3600)) -- start time stamp minus 3 hours
AND start_ts <= (1737406800 + (3*3600)) -- end time stamp plus 3 hours
ORDER BY start_ts

(The +/- 3*3600 adds 3 extra hours before the start time and 3 after the end time as each start_ts is 3600 apart from the next record)

This shows the LTS records before the history restore, the records loaded by the import_statistics integration, and a few afterwards.

e.g.:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
[existing LTS records immediately before the history load:]
3499332	NULL	1737313210.1869912	32	NULL	1737309600.0	NULL	NULL	NULL	NULL	NULL	45.4	11686.6	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3499633	NULL	1737316810.142873	32	NULL	1737313200.0	NULL	NULL	NULL	NULL	NULL	48.7	11689.9	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3499934	NULL	1737320410.188477	32	NULL	1737316800.0	NULL	NULL	NULL	NULL	NULL	52.2	11693.4	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL

[LTS records loaded by the integration:]
3139369	NULL	1738535943.438116	32	NULL	1737324000.0	NULL	NULL	NULL	NULL	NULL	62.0	11703.2	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3139645	NULL	1738535948.3166165	32	NULL	1737327600.0	NULL	NULL	NULL	NULL	NULL	67.5	11708.7	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
1	NULL
3139921	NULL	1738535951.3493242	32	NULL	1737331200.0	NULL	NULL	NULL	NULL	NULL	3.6	11712.3	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3140197	NULL	1738535953.4728782	32	NULL	1737334800.0	NULL	NULL	NULL	NULL	NULL	6.4	11715.1	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
...
3500860	NULL	1737403210.1605432	32	NULL	1737399600.0	NULL	NULL	NULL	NULL	NULL	35.1	11743.8	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3501160	NULL	1737406810.1452768	32	NULL	1737403200.0	NULL	NULL	NULL	NULL	NULL	35.1	11743.8	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3501460	NULL	1737410410.151534	32	NULL	1737406800.0	NULL	NULL	NULL	NULL	NULL	35.4	11744.1	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL

[existing LTS records that are immediately after the history load:] 
3501764	NULL	1737414010.1274874	32	NULL	1737410400.0	NULL	NULL	NULL	NULL	NULL	35.6	11708.0	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3502066	NULL	1737417610.1580782	32	NULL	1737414000.0	NULL	NULL	NULL	NULL	NULL	35.8	11708.2	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL
3502369	NULL	1737421210.1547968	32	NULL	1737417600.0	NULL	NULL	NULL	NULL	NULL	0.5	11708.7	32	sensor.grid_import_today_day	recorder	kWh	0	1	NULL

As explained in scenario 5 below, sum is initialised to zero when the entity is first created and increments forever upwards as state increments, whereas on a ‘today’ entity, state is reset to zero at the start of each day. Because there has been a problem with the statistics, this consistency is broken.

Looking at the records before and during the inserted history, state increments each hour, gets up to 67.5 (at midnight), then at the end of the next hour (1am) having been reset to zero at midnight, is 3.6. Sum increments each hour by the difference between the state value and the previous state value (ignoring the reset to zero).

The problem comes at the end, the last history record we inserted has state 35.4 and sum 11744.1, the first after the insertion the state is now 35.6 (i.e. 0.2 more) but sum drops down to 11708.0 whereas it should be 11744.3 (i.e. 0.2 more than the prior LTS record). Thereafter state and sum increment together properly, the next record has state 35.8 and sum is 11708.2, both 0.2 more than the prior record.

The fix we need to apply (borrowing from the techniques in scenarios 5 and 7 below) is to adjust sum to bring it into line with state by adding 11744.1 - 11708.0 + 0.2 = 36.3 to the first record after the history insertion and every record thereafter (because they will all be wrong by this delta amount):

UPDATE statistics_short_term
SET sum = sum + 36.3 -- add adjustment factor to correct for additional entity reset 
WHERE metadata_id = 32          -- metadata_id is of the entity being updated
AND start_ts >= 1737410400.0	-- start_ts is the time of the first record where the sum needs to be adjusted

and statistics:

UPDATE statistics
SET sum = sum + 36.3 -- add adjustment factor to correct for additional entity reset 
WHERE metadata_id = 32          -- metadata_id is of the entity being updated
AND start_ts >= 1737410400.0	-- start_ts is the time of the first record where the sum needs to be adjusted

As the LTS are created from the STS, make sure that both tables are updated with any corrections and statistics_short_term is updated first.

  1. Having imported one set of data from the GivEnergy portal its then quite simple to use the spreadsheet to load another data set. Re-run the SQL query in step 4 for the new entity id to determine the start and sum values for the first record, then continue the process from step 7 above; copy the next column of GivEnergy Portal data into column BL, enter the new entity id, unit of measurement, state and sum values into the appropriate cells. The copy/paste of formulae’s into cells BT3 and BV3 and the rest of those columns doesn’t need to be repeated (as the formulae are already in place), so you can simply copy the output data into a new file for loading into HA.

  2. Tidy up!

Remove the file copied into /config in step 8 above.

Scenario 3: Bulk load LTS from GivEnergy Portal data

This is a variant of scenario 2, using the GivEnergy Portal as the source for reloading LTS in Home Assistant.

One problem with using the GivEnergy portal is that as step 2 describes, the Portal only allows download of a single day of data into a CSV, so if you want to load several days of data, you have to individually download the CSV’s and then merge them in the data reformatting spreadsheet.
This is fine for one or two days of data and I used it to load 8 days of data that was missing from HA, but its laborious and slow.

This scenario uses a Powershell bulk download script to download multiple days of data from the GivEnergy Portal so we can then follow similar techniques to scenario 2 to load it into HA. You will need a Windows PC to run the script upon. There is a Python version of the script provided but I haven’t used (or enhanced) it.

In my case I wanted to load all the historical data that was in the GivEnergy portal but was before I started using Home Assistant, so had just over 9 months of history to download from the portal, reformat, and upload to HA.

The steps below do work but the spreadsheet can chug a bit with 80,000 rows of data so I’d recommend if you do similar that you break the data downloads and processing into smaller, more manageable chunks!

I’m going to use PV Generation Total to show the process, which is the same for Imported and Exported Energy.
On my Gen 1 GivEnergy inverter the GivEnergy portal doesn’t record Total Battery Charge/Discharge so we have to use the portal Today Charge/Discharge data, but otherwise the process below is the same (the spreadsheet adapts to the ‘today’ data).

  1. Install the Import Statistics HACS integration as described in scenario 1 above.

  2. Install the GivEnergy Portal bulk download script.

We are going to make use of a Powershell script to do the bulk download, the script was originally developed by Terravolt and then extended and improved by Robert Tidey.

There’s a discussion of using the script on the GivEnergy community forum:

https://community.givenergy.cloud/d/2881-report-data-download-as-excel-file/2

The scripts are on Robert’s GitHub repository:

Have a read of the readme, there are several scripts provided, but we’re only going to need the ‘GEFullDataPoints’ script

Go to GivEnergyScripts/GE-DaysFullDataPoints.ps1 at 016e05605a04bc5cc638a0d705f8a266afe65527 · roberttidey/GivEnergyScripts · GitHub

Click the download icon and save it on a Windows PC

You’ll next need to create a security API key to enable the script to retrieve data from your GivEnergy account.

In the GivEnergy portal:

  • click Account Settings
  • click Manage Account Security
  • click Manage API Tokens
  • click Generate New Token
  • Give the token a name so you can identify it, select an expiry date for the token, and ensure ‘api:inverter:data’ and ‘api:meter:data’ are ticked (you might not need both, but this is what I did and it worked)
  • Click Create Token
  • A security token will be created, click the clipboard icon to copy the token to your clipboard

Once created you can’t retrieve an API tokens details, so paste the token into a file somewhere safe. If you lose it then just delete the token from the portal and create a new one.

Edit GE-DaysFullDataPoints.ps1 that you downloaded earlier, and the top enter against $GivEnergyPortalAPI the Portal API key you’ve just created (surrounded by double quotes), and in SerialNum the serial number of your inverter.

Save the modified GE-DaysFullDataPoints.ps1

  1. Download bulk data from the GivEnergy portal

The script as supplied by Robert downloaded Today Solar, Import, Export, Consumption and Battery SoC data. I extended it to download Total Solar, Total Import, Total Export, Total Consumption, Today Battery Charge and Today Battery Discharge as these (bar Total Consumption) are data sets that are needed to be loaded into HA for the Energy dashboard.

I also fixed a bug in the script that was missing out the last record of the day, and added code to convert date/times to local time. The GivEnergy portal holds data in UTC format so any day’s data requests for data stored during the British Summer Time will retrieve records from 23:00 UTC to 23:00 UTC. My script changes include converting the time to local time so the records run from 00:00 to 00:00 to match what HA expects.

My changes have all now been merged into the base code by Robert.

Extract the data from the GivEnergy Portal by running the PowerShell script. My PC had PowerShell already installed, if yours doesn’t, just search for it.

  • Right mouse click on GE-DaysFullDataPoints.ps1 and select the second option, Run with PowerShell
  • If you get a warning about “Execution Policy Change”, enter ‘Y’ to approve the script
  • Enter the start date for the extract in YYYY-MM-DD format
  • Enter the number of days you wish to extract. I did 260 but suggest you keep it to blocks of 100 or so
  • The script will download each day of data, displaying the date as it starts the download
  • When finished it will save the downloaded data to a file named FullDataPoints_[date].txt
  1. Calculate the Unix timestamps for the date/times of the time range that you want to correct, selecting times that overlap by an hour or two the bad data time range.

If you are loading data into a ‘today’ entity then to get the daily totals right, you should always use 00:00 as the start time.

In my example I am loading ‘PV data total’ from when my inverters were installed on 07/01/23 to 23/10/23 when I started using HA. So I’ll choose a time range of 00:00 on 7th January to 23:00 on 23rd October (adding a couple of hours either side).

Note: the date and time must be precisely formatted as shown, its only the hours that is required to be filled in, the minutes are always zero and the milliseconds are not required.

Developer tools/template

{% set ds="07.01.2023 00:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="23.10.2023 23:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the Unix timestamps for the start and end as 1673049600 and 1698098400 respectively.

  1. Query the existing LTS for the range of records we are going to be updating.

SQLweb add-on. Paste in the following query, substituting your own entity id, start and end timestamps (from above step):

SELECT * FROM statistics
WHERE metadata_id = (SELECT id
                     FROM   statistics_meta
                     WHERE  statistic_id = 'sensor.givtcp_INVID_pv_energy_total_kwh') -- entity id
AND start_ts >= 1673049600 -3600  -- start time stamp minus 1 hour before
AND start_ts <= 1698098400 +3600  -- end time stamp plus one hour after
ORDER BY start_ts

Gives me the following results:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
90	NULL	1698033610.1748972	90	NULL	1698030000.0	NULL	NULL	NULL	NULL	NULL	4123.7	0.0
335	NULL	1698037210.4806314	90	NULL	1698033600.0	NULL	NULL	NULL	NULL	NULL	4123.7	0.0
578	NULL	1698040810.1879516	90	NULL	1698037200.0	NULL	NULL	NULL	NULL	NULL	4123.7	0.0
965	NULL	1698044410.220195	90	NULL	1698040800.0	NULL	NULL	NULL	NULL	NULL	4123.7	0.0
1208	NULL	1698048010.2052019	90	NULL	1698044400.0	NULL	NULL	NULL	NULL	NULL	4124.3	0.6
1451	NULL	1698051610.577809	90	NULL	1698048000.0	NULL	NULL	NULL	NULL	NULL	4125.9	2.2
...
5014	NULL	1698102010.208917	90	NULL	1698098400.0	NULL	NULL	NULL	NULL	NULL	4132.0	8.3
5260	NULL	1698105610.2539196	90	NULL	1698102000.0	NULL	NULL	NULL	NULL	NULL	4132.0	8.3

We need to make a note of the ‘sum’ and ‘state’ values from the first record - this is the LTS record an hour immediately before the start time record. In my case I am inserting before any existing history and the first record has start_ts of 1698030000.0 which is only just less than the end time, so my state and sum are 0 and 0 respectively.

If you want to convert the date/time of any of the records to printable format, copy the start_ts into this developer tools/template code fragment:

{% set x=1698030000.0 %}
{{x|timestamp_custom('%Y-%m-%d %H:%M')}}

Shows that the first record has a start_ts of 2023-10-23 04:00

  1. Next job is to split the CSV file into columns and extract the column of data we want to import into LTS.
    To make it easier and repetitive I have created a Google Sheets spreadsheet to do this.

Open the spreadsheet HA LTS reformatter - Google Sheets and click File/Make a Copy to save a copy in your own Google account.

Navigate to the ‘Bulk GivEnergy CSV’ tab.

Open the CSV file created in step 3 in a file editor, select all and copy the entire file contents, then paste it into column B of ‘Bulk GivEnergy CSV’ tab on the spreadsheet, ensuring the header data is pasted into row 1.

If you have more input CSV rows than are pre-populated in the spreadsheet, simply copy cells C3 through to AG3 into the matching cells of any additional rows you need in the spreadsheet.

Make sure that the last row after the data is blank and doesn’t contain ‘#VALUE!’ error.

Column C now looks something like this:

Date
07/01/2023 13:45:30
07/01/2023 13:45:40
07/01/2023 13:50:35
07/01/2023 13:55:52
  1. Edit the spreadsheet and remove rows not required

Delete all rows from row 2 downwards before the start time of the time period you wish to correct.
An easy way to do this to delete the first row (right mouse click, delete row), then click anywhere in the next row to remove and press control Y to repeat the last command. Keep positioning the cursor and pressing control Y to repeat the delete row command for each row in turn.

In my case there isn’t any rows to delete as I’m loading the entire days of history into HA.

Paste the following formula in cell W2:
=if(mid(C3,12,2)<>mid(C2,12,2),mid(C2,1,2)&“.”&mid(C2,4,2)&“.”&mid(C2,7,4)&" “&mid(C2,12,2)&”:00",“”)

Then copy cell W2 and paste it into cell W3, W4, etc all the way to the bottom of the data set.

The GivEnergy portal contains the data we need but it is far too granular as the GivEnergy Portal captures data every 5 minutes for each day, and LTS holds a data snapshot just once every hour.

Since the spreadsheet is created by the PowerShell script in date/time order, the above formula will ensure that the date time only appears in column W for the last state change within each hour - which is what we want.

Finally, delete any rows after the last record in the hour of the time range to be corrected and ensure that there is a blank row after the data set.

So column W now looks something like this:


...

07.01.2023 13:00

...

07.01.2023 14:00

...


07.01.2023 15:00

...
  1. Next we reformat the spreadsheet data into the precise format that the Import Statistics integration requires.

Find the column of data you want to extract (e.g. Solar Total which for me is column E) and copy the entire column into column V.
For my inverter the portal doesn’t contain Battery Discharge Total or Charge Total figures, only a Battery Total Throughput figure which isn’t a lot of use. We can use the Battery Discharge/Charge Today column instead to load the revised Total figures and the spreadsheet will automatically cope with that.

The cells you are pasting data into are coloured pale red.

Paste the entity id we are going to correct into cell Y, e.g. sensor.givtcp_INVID_pv_energy_total_kwh

Type the unit of measurement for the entity in cell AA1, e.g. kWh (or Wh)

Paste the ‘state’ value for the first record obtained from the SQL query in step 5 (i.e. the value immediately before the time period we are correcting) into cells AC1 AND AC2, e.g. 0.0
Paste the ‘sum’ value for the first record obtained from the SQL query in step 5 into cells AE1 AND AE2, e.g. 0.0

If you are loading a GivEnergy portal ‘total’ value or a portal ‘today’ value into a Home Assistant total entity, then in cell AC3 paste the following formula:
=round(AC2+V3-if(V3>=V2,V2),3)

If however you are loading a GivEnergy portal ‘today’ value into a Home Assistant ‘today’ entity, then in AC3 paste the following:
=round(V3,3)

Then (in both cases), copy cell AC3 and paste it into cell AC4, AC5, etc all the way to the bottom of the data set.

In cell AE3 paste the following formula (same formula regardless of source and destination types):
=round(AE2+V3-if(V3>=V2,V2),3)

Then copy cell AE3 and paste it into cell AE4, AE5, etc all the way to the bottom of the data set.

The spreadsheet will reformat the CSV data, producing the correctly formatted output data in column AG (coloured green). There will be blank rows in AG where the source data row was from a 5-minute change on the portal, but these will be ignored during the data load.

Select and copy cells AG1 downwards to the end of your data, and paste the reformatted data into a new text file which will look something like this:

statistic_id|unit|start|state|sum

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|07.01.2023 13:00|0|0

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|07.01.2023 14:00|0|0

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|07.01.2023 15:00|0.1|0.1

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|07.01.2023 16:00|0.1|0.1

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|07.01.2023 17:00|0.1|0.1

...
sensor.givtcp_INVID_pv_energy_total_kwh|kWh|23.10.2023 23:00|4132|4132

Its worth a double-check of the file contents to make sure that data looks sensible. The first record should match the state and sum from the database record start_ts of the starting hour, state and sum should increment over time down the file (with state resetting to 0 if its a today sensor). The last record should have a state value that matches the end time in the database query (4132) but the sum value may show a jump if the entity wasn’t recording history in HA during the time period - step 13 covers resolving this jump.

If everything looks OK and there’s no gross errors then save this file and name it appropriately.

  1. Copy the file created and saved in step 8 into the /config folder of your Home Assistant server (use Samba share, HA file editor, SSH, or whatever you prefer).

  2. Recommended that you take a backup of HA before you load the new LTS. Just in case !

  3. In HA, go to Developer Tools/ Actions and search for ‘import_statistics’

Fill in the UI as follows:

filename: name of the file copied into /config in step 9
timezone_identifier: Europe/London
delimiter: | (the vertical pipe symbol)

And click on Perform Action

  1. View the updated statistics by re-running the query from step 5 above, viewing the energy dashboard and looking at the entity state history in the history graph for the selected date/time period.

  2. As described in step 12 of scenario 2 and scenario 7, because we have loaded history into HA where it wasn’t recording history before, there will be a spike created in the sum value which is very visible in the Energy dashboard.

You can see the spike in the data as well, in step 8 the last row of data to be loaded is:

sensor.givtcp_INVID_pv_energy_total_kwh|kWh|23.10.2023 23:00|4132|4132

and in step 5 where we queried the existing LTS, we can see that the end time record and the record immediately after it were:

5014	NULL	1698102010.208917	90	NULL	1698098400.0	NULL	NULL	NULL	NULL	NULL	4132.0	8.3
5260	NULL	1698105610.2539196	90	NULL	1698102000.0	NULL	NULL	NULL	NULL	NULL	4132.0	8.3

So sum spikes down from 4132 to 8.3 immediately after the data we have loaded.

The fix for this is to adjust the sum value so it runs contiguous across the transition, the SQL commands are:

UPDATE statistics_short_term
SET sum = sum - 8.3 + 4132.0    -- add adjustment factor to correct for jump in sum value 
WHERE metadata_id = 90          -- metadata_id is the entity being corrected
AND start_ts > 1698098400.0     -- start_ts is the end time of the history load, we adjust every record after then
UPDATE statistics
SET sum = sum - 8.3 + 4132.0    -- add adjustment factor to correct for jump in sum value 
WHERE metadata_id = 90          -- metadata_id is the entity being corrected
AND start_ts > 1698098400.0     -- start_ts is the end time of the history load, we adjust every record after then

to deal with any spikes around the point of transition

  1. Having imported one set of data from the GivEnergy portal its then quite simple to use the spreadsheet to load another data set. Simply copy the appropriate column of data into column BL, then continue from step 8 above, enter the entity id, unit of measurement, state and sum values into the appropriate cells. The copy/paste of formulae’s into cells BT3 and BV3 and the rest of those columns doesn’t need to be repeated (as the formulae are already in place), so you can simply copy the output data into a new file for loading into HA.

  2. Tidy up!

Remove the file copied into /config in step 9 above.

Scenario 4: Loading import & export data from Octopus Energy

In situations where your inverter has been offline from the internet for an extended period of time then there simply isn’t the data in the GivEnergy Portal (scenarios 2 and 3) to re-load back into Home Assistant. For Octopus Energy customers they provide the ability to download import and export meter data in half-hour blocks which at least means you can recover that data into Home Assistant.

As well as the meter reading data there is estimated cost figures in the Octopus data download - data which of course isn’t in the GivEnergy Portal. Its this latter (import cost) data that I wanted to restore into Home Assistant from when my statistics went wild between about 21:00 on 19th January through to 22:00 on 20th January, but the process also covers recovering import or export readings as well.

(20th January was reporting 35kWh imported for £0.16 of cost - I wish !)

  1. Download the required data from Octopus Energy

Login to your Octopus account (in a web browser, the app doesn’t have the data download capability).

Click ‘My energy’, scroll down to ‘Get your energy geek on’

Choose the data, either Electricity or Electricity Export, select the date range required and click download.

This will produce a CSV file looking like this:

Consumption (kwh), Estimated Cost Inc. Tax (p), Start, End
0.407000, 8.79, 2025-01-18T23:30:00+00:00, 2025-01-19T00:00:00+00:00
2.408000, 47.65, 2025-01-19T00:00:00+00:00, 2025-01-19T00:30:00+00:00
0.339000, 7.97, 2025-01-19T00:30:00+00:00, 2025-01-19T01:00:00+00:00
  1. Calculate the Unix timestamps for the date/times of the first and last hours adjustments we are going to make.

As per scenario 2, I am going to update import data from 21:00 on 19/1/25 through to 22:00 on 20/1/25 - times that slightly overlap the ‘missing/bad statistics’ time period in HA.

However since cost is a ‘today’ entity whose state resets to zero at the start of each day, we will need to update the entity values (state) up to the end of the day so the end time will need to be 23:00 on 20/1/25 (the last statistics entry in the day that covers 23:00-23:59).

Likewise if retrieving energy import or energy export data from Octopus and loading it into a ‘today’ entity (such as sensor.givtcp_xxxx_import_energy_today_kwh) rather than loading it into a 'total entity (e.g. sensor.givtcp_xxx_export_energy_total_kwh) then the end time will also need to be extended to 23:00 on the impacted day.

Note: the date and time must be precisely formatted as shown, its only the hours that is required to be filled in, the minutes are always zero and the milliseconds are not required.

Developer tools/template

{% set ds="19.01.2025 21:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="20.01.2025 23:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the Unix timestamps for the start and end as 1737320400 and 1737414000 respectively.

  1. Identify the import/export/cost entities to be updated in HA.

This is not entirely straightforward.

Import and export entities being used in the Energy dashboard are easy enough, Energy Dashboard / three dots (top right) / Energy configuration

And the import entity is under ‘Grid consumption’ and the export under ‘Return to grid’

The financial entities used to track import costs and export income though are not shown in the dashboard and are auto-created behind the scenes by the Energy dashboard.

Go to Developer Tools / Statistics and search for ‘sensor Cost’ this will find the import cost entity, which will be named something like ‘sensor.[import entity friendly name with underscores]_cost’

My import entity is shown on the dashboard as ‘Grid Import Today Day’ and the cost entity id is sensor.grid_import_today_day_cost_2 - the ‘_2’ I guess because somehow in the past I changed things in the Energy dashboard configuration.

The export income entity can be similarly found by searching for ‘sensor Compensation’, my entity id is sensor.grid_export_today_day_compensation_2 (as I use today sensors for import/export in the Energy dashboard).

If you’ve used the import and export total entities in the dashboard then the cost and compensation entities will have total rather than today in the entity id.

  1. Query the existing LTS for the range of records we are going to be updating.

SQLweb add-on. Paste in the following query, substituting your own entity id, start and end timestamps (from step 2):

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.grid_import_today_day_cost_2'  -- entity id
AND start_ts >= 1737320400  -- start time stamp
AND start_ts <= 1737414000  -- end time stamp
ORDER BY start_ts

You should get a result set looking something like this:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
3500222	NULL	1737324010.166711	7	NULL	1737320400.0	NULL	NULL	NULL	NULL	1737244800.092167	14.3084	1850.3616	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3139356	NULL	1738535943.4376893	7	NULL	1737324000.0	NULL	NULL	NULL	NULL	1734048000.016701	11.3379	1851.5636	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3139632	NULL	1738535948.3159373	7	NULL	1737327600.0	NULL	NULL	NULL	NULL	1734048000.016701	11.3379	1852.7736	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3139908	NULL	1738535951.3488548	7	NULL	1737331200.0	NULL	NULL	NULL	NULL	1734048000.016701	11.3379	1852.7736	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3140184	NULL	1738535953.4722095	7	NULL	1737334800.0	NULL	NULL	NULL	NULL	1734048000.016701	11.3379	1852.7736	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3140460	NULL	1738535955.0181875	7	NULL	1737338400.0	NULL	NULL	NULL	NULL	1734048000.016701	11.3379	1852.7736	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
...
3500847	NULL	1737403210.1605432	7	NULL	1737399600.0	NULL	NULL	NULL	NULL	1737389883.640885	0.09999	1852.76999	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3501147	NULL	1737406810.1452768	7	NULL	1737403200.0	NULL	NULL	NULL	NULL	1737389883.640885	0.09999	1852.76999	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3501447	NULL	1737410410.151534	7	NULL	1737406800.0	NULL	NULL	NULL	NULL	1737389883.640885	0.17606	1852.84606	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3501751	NULL	1737414010.1274874	7	NULL	1737410400.0	NULL	NULL	NULL	NULL	1737389883.640885	0.22060	1852.89060	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3502053	NULL	1737417610.1580782	7	NULL	1737414000.0	NULL	NULL	NULL	NULL	1737389883.640885	0.26514	1852.9351	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL

What we should see is state incrementing by the £ import value each hour until it gets to 23:00, and then the next record (midnight) it starts back from zero again. Sum should increment forever from when the sensor was first created.

This doesn’t happen for my data, state increases, gets stuck at 11.3379, then resets to zero in the middle of a day. Sum looks like it is incrementing correctly.

We need to make a note of the ‘state’ and ‘sum’ values corresponding to the start time of the first record (which we know is OK). In my case they are 14.3084865 and 1850.361639000002 respectively.

If you want to convert the date/time of any of the records to printable format, copy the start_ts into this developer tools/template code fragment:

{% set x=1737320400.0 %}
{{x|timestamp_custom('%Y-%m-%d %H:%M')}}
  1. Reformat the CSV file to format required by the Import Statistics integration.

To make it easier and repetitive I have created a Google Sheets spreadsheet to do this.

Open the spreadsheet and click File/Make a Copy to save a copy in your own Google account.

Navigate to the ‘Octopus CSV’ tab.

Open the CSV file created in step 1, select all and copy the entire file contents, then paste it into column B of ‘Octopus CSV’ tab on the spreadsheet.

If you have more input CSV rows than are pre-populated in the spreadsheet, simply copy cells C3 through to Y3 into the matching cells of any additional rows you need in the spreadsheet.

Whilst this contains the data we need, it contains all the import (or export) data for every half hour of the selected date range, so column E (Start) currently looks like this:

 2025-01-19T00:00:00+00:00
 2025-01-19T00:30:00+00:00
 2025-01-19T01:00:00+00:00
 2025-01-19T01:30:00+00:00
 2025-01-19T02:00:00+00:00
  1. Edit the spreadsheet and remove the time slots we don’t need.

The Octopus download contains import/export kWh and cost/compensation £ for every half hour slot in the selected date range.

Delete all rows from row 2 downwards before the start hour of the time period you wish to correct, remember that there are two records for each hour, one from xx:00 to xx:30 and one from xx:30 to xy:00 and both need to be deleted.

An easy way to do this to delete the first row (right mouse click, delete row), then click anywhere in the next row to remove and press control Y to repeat the last command. Keep positioning the cursor and pressing control Y to repeat the delete row command for each row in turn.

Only delete any trailing records after the last (30 minute) record in the time range being updated if loading import/export kWh into a total entity. For any loading into a today entity (of either import/export kWh or cost/compensation into the day GBP entity that underpins the Energy dashboard), the file must include all records up to the end of the day.

So column E now looks something like this:

 2025-01-19T21:00:00+00:00
 2025-01-19T21:30:00+00:00
 2025-01-19T22:00:00+00:00
...
 2025-01-20T22:30:00+00:00
 2025-01-20T23:00:00+00:00
 2025-01-20T23:30:00+00:00
  1. Next we reformat the spreadsheet data into the precise format that the Import Statistics integration requires.

Find the column of data you want to extract (i.e. kWh Consumption - column C, or £ Estimated cost - column D) and copy the entire column into column M. If you are loading Consumption (import or export kWh) into HA then you’ll need to do Edit/Paste special/Values only to copy the values (not formulae) from Column C into column M.

The cells you are pasting data into are coloured pale red.

Column N is automatically populated in the spreadsheet with the hourly summation of the half-hour records, and column O contains the reformatted date/time.

Paste the entity id we are going to correct into cell Q1, e.g. sensor.grid_import_today_day_cost_2

Type the unit of measurement for the entity in cell S1, e.g. kWh (or Wh) for import/export or GBP for cost/compensation.

Paste the ‘state’ value for the first record obtained from the SQL query in step 4 into cells U1, e.g. 14.3084865

Paste the ‘sum’ value for the first record obtained from the SQL query in step 4 into cell W1, e.g. 1850.361639000002

If loading an import cost or export compensation GBP ‘today’ or ‘total’ entity:

a. Paste the following formulae in cell U2:
=round((N2/100)+if(mid(O2,12,2)=“00”,0,U1),4)

b. Paste the following formulae in cell W2:
=round(W1+(N2/100),4)

The formulae is the same for today and total cost entities because Home Assistant resets the cost entity state to zero at the start of each day, even on the total cost entity, so this formula loads the entity in a consistent manner to how HA operates.

Column D cost figures in the Octopus file include VAT for import costs and exclude VAT for export compensation.

If loading an import or export kWh ‘today’ entity:

a. Paste the following formulae in cell U2:
=round(N2+if(mid(O2,12,2)=“00”,0,U1),3)

b. Paste the following formulae in cell W2:
=round(W1+N2,3)

If loading an import or export kWh ‘total’ entity:

a. Paste the following formulae in cell U2:
=round(N2+U1,3)

b. Paste the following formulae in cell W2:
=round(W1+N2,3)

Then (for all types of entity loading), copy cells U2 and W2 and paste them into cells U3/U4/U5 and W3/W4/W5 etc all the way to the bottom of the data set.

Optional: the spreadsheet is setup to sum all half-hour Octopus meter readings into hourly values for storing in long term statistics.

If you are loading historical GBP import cost or export compensation figures into LTS then you could save a bit of database space and only load daily cost/compensation figures into LTS. The Energy dashboard only shows a single daily £ value so there’s no real value in storing the hourly breakdown.

To adapt the spreadsheet to daily rather than hourly data loads:
a. in cell N2 replace the formula:
=if(mid(O2,15,2)=“00”,M2+M3,“”)
with the new formula:
=if(mid(O2,12,2)=“00”,sum(M2:M49),“”)

b. in call O2 replace the formula:
=if(mid(E2,16,2)=“00”,mid(E2,10,2)&“.”&mid(E2,7,2)&“.”&mid(E2,2,4)&" “&mid(E2,13,2)&”:00",“”)
with the new formula:
=if(mid(E2,13,5)=“00:00”,mid(E2,10,2)&“.”&mid(E2,7,2)&“.”&mid(E2,2,4)&" “&mid(E2,13,2)&”:00",“”)

And then copy cells N2 and O2 and paste them into cells N3/N3/N5 and O3/O4/O5 etc all the way to the bottom of the data set.

(don’t forget to change the formula back when you want to load hourly kWh data)

The spreadsheet will reformat the CSV data, producing the correctly formatted output data in column Y (coloured green).

Select and copy cells Y downwards to the end of your data, and paste the reformatted data into a new text file which will look something like this:

statistic_id|unit|start|state|sum
sensor.grid_import_today_day_cost_2|GBP|19.01.2025 21:00|14.370405|1850.446311

sensor.grid_import_today_day_cost_2|GBP|19.01.2025 22:00|15.58914|1851.665046

sensor.grid_import_today_day_cost_2|GBP|19.01.2025 23:00|16.79664|1852.872546

sensor.grid_import_today_day_cost_2|GBP|20.01.2025 00:00|0.722505|1853.595051

(every alternate line will be blank, and this is OK as they are ignored on loading; this is a consequence of there being two Octopus half-hour records that have been summed into a single hourly record for loading into statistics).

Save this file and name it appropriately.

Important! Check the unit of measurement (cell S1) is set correctly for the entity being loaded. I have accidentally left the UoM set to GBP when loading entity history for a ‘total kWh’ entity.
The load proceeds as normal but an error appears in Developer Tools/statistics and any further statistics collection is stopped until the error is cleared.

  1. Copy the file created and saved in step 7 into the /config folder of your Home Assistant server (use Samba share, HA file editor, SSH, or whatever you prefer).

  2. Recommended that you take a backup of HA before you load the new LTS. Just in case !

  3. In HA, go to Developer Tools/ Actions and search for ‘import_statistics’

Fill in the UI as follows:

filename: name of the file copied into /config in step 8
timezone_identifier: Europe/London
delimiter: | (the vertical pipe symbol)

And click on Perform Action

  1. View the updated statistics by re-running the query from step 4 above, viewing the energy dashboard and looking at the entity state history in the history graph for the selected date/time period.

Can also check the import/export kWh and cost/compensation figure in the Energy dashboard against what is shown in the Octopus app for the days you loaded. Note that there is likely to be small variations in part-loaded days as the inverter import/export meter is not a perfect mirror of the physical electricity meter readings that Octopus uses.

  1. Dealing with additional Energy dashboard spike after the loaded history

After loading the missing statistics history from Octopus, its likely that there will be a new spike showing in the Energy dashboard immediately after where the LTS has been loaded. This is due to the sum value not incrementing correctly across the transition from the replaced history to the existing history records.

This is best seen by looking at the data through SQL.

Repeat the SQL query from step 4 but expand the ‘start_ts where clauses’ to include a few more records either side, i.e.:

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.grid_import_today_day_cost_2'  -- entity id
AND start_ts >= 1737320400 - (3*3600)  -- start time stamp less 3 hours
AND start_ts <= 1737414000 + (3*3600)  -- end time stamp plus 3 hours
ORDER BY start_ts

(The +/- 3*3600 adds 3 extra records before the start time and 3 after the end time as each start_ts is 3600 apart from the next record)

This shows the LTS records before the history restore, the records loaded by the import_statistics integration, and a few afterwards. We need to focus on the records at the end.

e.g.:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
[LTS records loaded by the integration:]
3501447	NULL	1737410410.151534	7	NULL	1737406800.0	NULL	NULL	NULL	NULL	NULL	11.530365	1864.318239	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3501751	NULL	1737414010.1274874	7	NULL	1737410400.0	NULL	NULL	NULL	NULL	NULL	11.580345	1864.368219	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3502053	NULL	1737417610.1580782	7	NULL	1737414000.0	NULL	NULL	NULL	NULL	NULL	11.629905	1864.417779	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL

[existing LTS records that are immediately after the history load:] 
3502356	NULL	1737421210.1547968	7	NULL	1737417600.0	NULL	NULL	NULL	NULL	1737417600.051562	0.115878	1853.051024	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3502659	NULL	1737424810.1311069	7	NULL	1737421200.0	NULL	NULL	NULL	NULL	1737417600.051562	0.246036	1853.1812	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL
3502962	NULL	1737428410.154108	7	NULL	1737424800.0	NULL	NULL	NULL	NULL	1737417600.051562	0.793947	1853.7291	7	sensor.grid_import_today_day_cost_2	recorder	GBP	0	1	NULL

As explained in scenario 5 below, sum is initialised to zero when the entity is first created and increments forever upwards as state increments, whereas on a ‘today’ entity, state is reset to zero at the start of each day. Because there has been a problem with the statistics, this consistency is broken.

Looking at the records before and during the inserted history, state increments each hour, gets up to 11.629905 (at midnight), then at the end of the next hour (1am) having been reset to zero at midnight, is 0.115878.

Sum increments each hour by the difference between the state value and the previous state value (ignoring the reset to zero) but at the point of transition sum drops from 1864.417779 (loaded by the integration) to 1853.051024 (original LTS value) whereas it should be 1864.417779 (prior record value) + 0.115878 (state change in the first hour).

This jump in value causes a spike in the Energy dashboard on the day after the history load (21/1/24). In my case, as its the import cost that spikes, showing that for 49.6kWh of import it only cost £1.78 - as if!

On subsequent records after the transition point state and sum increment together properly, the next record has state 0.246036 and sum is 1853.1812, both 0.130176 more than the prior record.

The fix we need to apply (borrowing from the techniques in scenarios 5 and 7) is to adjust sum to bring it into line with state by adding 1864.417779 - 1853.051024 + 0.115878 = 11.482633 to the first record after the history insertion and every record thereafter (because they will all be wrong by this delta amount):

You must update statistics_short_term first as the statistics table is built from aggregating statistics_short_term, and if statistics_short_term isn’t corrected then future statistics records written will have the wrong sum value.

UPDATE statistics_short_term
SET sum = sum + 11.482633    -- add adjustment factor to correct for missing sum history
WHERE metadata_id = 7        -- metadata_id is the entity being corrected
AND start_ts > 1737414000    -- start_ts is the end time of the history load, we adjust every record after then

and statistics:

UPDATE statistics
SET sum = sum + 11.482633    -- add adjustment factor to correct for missing sum history
WHERE metadata_id = 7        -- metadata_id is the entity being corrected
AND start_ts > 1737414000    -- start_ts is the end time of the history load, we adjust every record after then

Repeat step 11 to check the entity history and Energy dashboard for the day after the loaded history and it should all match without any more spikes.

Scenario 5: moving LTS from one entity to another

In this example we already have the statistics in HA but they are on the on the wrong entity and need migrating from one entity to another. There are a number of reasons how this might occur, you replace one device with another, you introduce a filter sensor to reduce storage in the HA database for ‘noisy’ sensors, etc, etc.

It could be possible to extract the existing LTS from one entity into a file, reformat it and then use import_statistics to load the data into the new entity, but instead since the data is already in the LTS database tables we are going to migrate it from one entity to another using techniques similar to as described in Migrate energy statistics from one entity to another
[NB: I haven’t tried to develop a procedure to extract LTS into a file and then use import_statistics to reload it, principally because as already noted, LTS holds dates in Unix seconds since 1970 format and import_statistics needs printable dates. Working out how to convert one to the other en-mass seemed non trivial so isn’t something I have looked further at]

You absolutely must take a backup beforehand !

The example I am going to use is my FIT Solar Energy Total sensor that I replaced with a time_throttle filter sensor that collects the data every minute instead of every state change measured by the CT clamp on my FIT inverter output.

In the Energy dashboard I added the old and new FIT total energy sensors so there was no break in the history in the dashboard:

And you can just about see the different shade of brown in the dashboard as I made the change on the 9th October:

And in the entity history, FIT solar energy total starts from 9th October:

I need to migrate ALL the LTS from sensor.fit_solar_energy_total_raw to sensor.fit_solar_energy_total. The procedures below describe the steps to be followed for an entity of state_class ‘total’ or ‘total_increasing’ and are the same regardless of whether the sensor is a ‘total’, ‘today’, ‘monthly’ or utility meter entity.

For state_class ‘measurement’ (e.g. temperature, pressure, power) the process is almost the same but some steps (as indicated) can be omitted - see variant process step 9.

  1. View the source data we need to migrate FROM the OLD entity

SQLweb add-on. Paste in the following query, substituting your own entity id:

SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.fit_solar_energy_total_raw')  -- 'old' source entity id that is being migrated from
ORDER BY start_ts

In my case I have 8082 records, the first ones are:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
10167	NULL	1698170410.6122658	282	NULL	1698166800.0	NULL	NULL	NULL	NULL	NULL	0.0044	0.0002
10421	NULL	1698174010.265277	282	NULL	1698170400.0	NULL	NULL	NULL	NULL	NULL	0.0104	0.0062
10673	NULL	1698177610.2787662	282	NULL	1698174000.0	NULL	NULL	NULL	NULL	NULL	0.0164	0.0122

and the last ones are:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
2681208	NULL	1728504010.4551399	282	NULL	1728500400.0	NULL	NULL	NULL	NULL	NULL	3039.055	3039.0508
2681512	NULL	1728507612.1271832	282	NULL	1728504000.0	NULL	NULL	NULL	NULL	NULL	3039.058	3039.0538
2681816	NULL	1728511210.7564747	282	NULL	1728507600.0	NULL	NULL	NULL	NULL	NULL	3039.061	3039.0568
2682120	NULL	1728514811.35525	282	NULL	1728511200.0	NULL	NULL	NULL	NULL	NULL	3039.0636	3039.0594

From the OLD entity data in LTS, look at the last record stored and make a note of the source entity metadata_id (282), start_ts (1728511200.0) record timestamp and sum (3039.0594) from that last record.
(For a measurement sensor the sum value will be zero, this is OK).

Of course if you only wanted to move a set of records from one entity to another you could determine the start and end date/times for the subset of records to move, convert those to Unix format using developer tools/template as described in Scenario 3 step 4, and then add an additional WHERE clause to the above SQL. Taking the same start and end date examples of 07.01.2023 00:00 and 23.10.2023 23:00, the Unix timestamps will be 1673049600 and 1698098400 respectively.

The SQL to view the first records to migrate would become:

SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.fit_solar_energy_total_raw')  -- 'old' source entity id that is being migrated from
  AND start_ts >= 1673049600  -- time stamp of the first record to migrate 
ORDER BY start_ts

and the last records to migrate becomes:

SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.fit_solar_energy_total_raw')  -- 'old' source entity id that is being migrated from
  AND start_ts <= 1698098400  -- time stamp of the last record to migrate
ORDER BY start_ts
  1. View the LTS for the NEW destination entity we are migrating TO
SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.fit_solar_energy_total')  -- 'new' destination entity id that is being migrated to
ORDER BY start_ts

(add a WHERE start_ts <= or >= if necessary to target specific date/time ranges)

The first records are:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
2682272	NULL	1728514811.3689609	619	NULL	1728511200.0	NULL	NULL	NULL	NULL	NULL	3039.064	0.0005
2682581	NULL	1728518410.4455652	619	NULL	1728514800.0	NULL	NULL	NULL	NULL	NULL	3039.067	0.0035
2682888	NULL	1728522011.1162825	619	NULL	1728518400.0	NULL	NULL	NULL	NULL	NULL	3039.07	0.0065

Again for the entity we are migrating to, note the destination metadata_id (619), the state (3039.064) and (for total or total_increasing entities) the sum (0.0005) of the first record.

Can see that the last record from the source entity and the first record for the destination entity have the same start_ts (1728511200.0) so when migrating we need to not migrate that record across as we will get a duplicate violation if we do.

For total and total_increasing entities we can see that whilst the state increments correctly from the old to the new entity, the sum started back at zero when the new (destination) entity was created so we will need to adjust the sum value of all the existing statistics for the destination entity to avoid getting a ‘jump’ in entity values (which shows up in the Energy dashboard).

Whilst looking at the new entity, page through all the records and check if there are any occasions where the state resets back to zero. If there is, there’s some additional activity that will be needed to be done (see point 8 below).

For measurement entities the process is simpler, we will be migrating the entity mean, min and max values from the LTS. State and sum for these entities will always be zero.

  1. Short term statistics

Statistics in HA firstly are stored in the short term statistics every 5 minutes, and then are summed hourly in the long term statistics. We therefore will need to adjust the short term statistics for the destination entity before we adjust the long term statistics for that entity.

Firstly view the short term statistics:

SELECT * FROM statistics_short_term 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.fit_solar_energy_total')  -- 'new' destination entity id that is being migrated to
ORDER BY start_ts

Look at the last few records and make a note of the entity sum and state. Can see a similar pattern that the state rolls on from the original entity values but the state was reset to zero when the new entity was created.

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
42125346	NULL	1739311510.1592438	619	NULL	1739311200.0	NULL	NULL	NULL	NULL	NULL	3368.2841	329.2206
42125466	NULL	1739311810.3223414	619	NULL	1739311500.0	NULL	NULL	NULL	NULL	NULL	3368.2844	329.2209
42125585	NULL	1739312110.1792114	619	NULL	1739311800.0	NULL	NULL	NULL	NULL	NULL	3368.2846	329.2211
  1. Adjust NEW destination entity ‘sum’ values for the short and long term statistics:

This step is only required for total and total_increasing entities.

The adjustment factor for the sum column is the difference in sum values from the last OLD source record to the first NEW destination record:

adj_factor = 3039.0594 - 0.0005

Execute the following two SQL commands:

UPDATE statistics_short_term
SET sum = sum + (3039.0594 - 0.0005)  -- difference between sum on the old source (migrated-from) to sum on the new (migrated-to) entity
WHERE metadata_id = 619               -- metadata_id is of the new (migrated-to) destination entity
UPDATE statistics
SET sum = sum + (3039.0594 - 0.0005)  -- difference between sum on the old source (migrated-from) to sum on the new (migrated-to) entity
WHERE metadata_id = 619               -- metadata_id is of the new (migrated-to) destination entity
  1. Copy the short and long term statistics from the source to the destination entity:

Execute the following two SQL commands:

INSERT INTO statistics_short_term (created_ts, metadata_id, start_ts, mean, min, max, state, sum) 
  SELECT created_ts, 619 as metadata_id, start_ts, mean, min, max, state, sum  -- metadata_id is of the destination entity
  FROM statistics_short_term
  WHERE metadata_id = 282    -- metadata_id is of the source entity
  AND start_ts < 1728511200  -- start_ts excludes the last record of the from data which already exists for the new entity
  ORDER BY start_ts
INSERT INTO statistics (created_ts, metadata_id, start_ts, mean, min, max, state, sum) 
  SELECT created_ts, 619 as metadata_id, start_ts, mean, min, max, state, sum  -- metadata_id is of the destination entity
  FROM statistics
  WHERE metadata_id = 282    -- metadata_id is of the source entity
  AND start_ts < 1728511200  -- start_ts excludes the last record of the from data which already exists for the new entity
  ORDER BY start_ts

Depending on when you are doing the entity statistics migration, if its more than the default history retention period (default 10 days) after the destination entity was created, then the first command to migrate the short term statistics won’t be required as there won’t be any for the source entity.

If we only wanted to migrate records between a start and end date range we’d just need to extend the inner WHERE clause to select only the required records from the source data entity, e.g.

INSERT INTO statistics (created_ts, metadata_id, start_ts, mean, min, max, state, sum) 
  SELECT created_ts, 619 as metadata_id, start_ts, mean, min, max, state, sum  -- metadata_id is of the destination entity
  FROM statistics
  WHERE metadata_id = 282    -- metadata_id is of the source entity
  AND start_ts >= 1673049600 -- select only records starting from and after the start date (e.g. 07.01.2023 00:00)
  AND start_ts < 1728511200  -- start_ts excludes the last record of the from data which already exists for the new entity
  ORDER BY start_ts
  1. Check the Energy dashboard and the target entity history:

In the Energy dashboard, up to the migration date (8th October) there are (in my case) four solar sources, my two GivEnergy arrays, the new FIT sensor and the original FIT (raw) sensor. The FIT and FIT raw (top two bars) have the same values.
After 9th October only the new FIT sensor is being populated, FIT raw stopped being populated so has no data shown on the energy dashboard:

The energy sources show the FIT Solar Energy Total (that was 109.45kWh) is now increased by 55.45kWh, the FIT raw history that has been migrated across.

The FIT solar entity history now is a smooth line going all the way back in time:

  1. Tidy up!

Remove the unwanted migrated-from (FIT solar (raw)) entity from the energy dashboard configuration if it had been included.

Go into Developer Tools / Statistics, search for the old migrated from (FIT (raw)) entity, it will say “This entity is no longer being recorded” and you can click “Fix issue” to delete all the statistics history as its no longer needed.

  1. Extra step if state or sum resets to zero

The above technique worked perfectly for migrating my FIT entity history from the source sensor to the destination. However when migrating another entity history I found a problem in a couple of sensors where the destination entity state had randomly reset itself back to zero:

And can see this in the statistics for that entity:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
3364594	NULL	1735686010.4762971	620	NULL	1735682400.0	NULL	NULL	NULL	NULL	NULL	585.4702	585.47
3364898	NULL	1735689610.446479	620	NULL	1735686000.0	NULL	NULL	NULL	NULL	NULL	585.6163	585.6161
3365202	NULL	1735693210.4731658	620	NULL	1735689600.0	NULL	NULL	NULL	NULL	NULL	585.7252	585.725
3365506	NULL	1735718410.3739939	620	NULL	1735714800.0	NULL	NULL	NULL	NULL	NULL	0.1006	585.8254
3365806	NULL	1735722010.3142457	620	NULL	1735718400.0	NULL	NULL	NULL	NULL	NULL	0.1136	585.8384
3366106	NULL	1735725610.3938053	620	NULL	1735722000.0	NULL	NULL	NULL	NULL	NULL	0.1243	585.8491

sum keeps incrementing properly but state resets back to zero.

This is a bit annoying but can’t do anything about it as the state value is provided by the source device and we can’t change that, but a jump in state doesn’t cause a problem in the Energy dashboard as that relies on the changes in sum to show energy consumption.

As long as sum keeps incrementing properly then there is nothing more needed to be done.

If however sum doesn’t increment properly then you might need to adjust it:

Make a note of start_ts of the record where the sum value jumps (e.g. 1735714800.0) and the sum of the prior record (585.7252). Subtract the difference between sum values either side of the jump.
e.g. 585.7252 - 0.0002 = 585.725

Follow the procedure exactly as laid out, then at the end, execute the following two additional SQL commands:

UPDATE statistics_short_term
SET sum = sum + 585.725 -- add adjustment factor to correct for jump in sum value
WHERE metadata_id = 620      -- metadata_id is of the destination entity
AND start_ts >= 1735714800.0 -- start_ts is the time of the record where the entity reset to zero occurred

and statistics:

UPDATE statistics
SET sum = sum + 585.725 -- add adjustment factor to correct for jump in sum value
WHERE metadata_id = 620      -- metadata_id is of the destination entity
AND start_ts >= 1735714800.0 -- start_ts is the time of the record where the entity reset to zero occurred
  1. Variant process for ‘measurement’ statistic entities

Measurement statistic entities are used for things like power meters and temperature/humidity monitors. Unlike the total entities covered earlier the STS and LTS only measure the mean, min and max values of the entity over the 5 minute or hourly time period.

e.g.:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
1232859	NULL	1713290410.391342	432	NULL	1713286800.0	9.837	6.36	17.59	NULL	NULL	NULL	NULL
1233164	NULL	1713294010.7394826	432	NULL	1713290400.0	10.717	6.63	19.55	NULL	NULL	NULL	NULL
1233467	NULL	1713297610.3014894	432	NULL	1713294000.0	9.650	7.08	13.62	NULL	NULL	NULL	NULL

state and sum columns are not populated and since each record is a standalone snapshot of mean/min/max over the relevant time period and there is no running totals, it is much easier to migrate these STS/LTS records from one entity to another.

All that is required is to determine the start_ts at the end of the source entity records/start of the destination entity records (steps 1 and 2).
No adjustment to the destination entity will be required (step 4) for measurement entities, and so with the start_ts value you can then copy the appropriate statistics history from one entity to another.

A slightly different set of SQL commands are required to do the copying as we are migrating the mean/min/max values, not state/sum:

Execute the following two SQL commands:

INSERT INTO statistics_short_term (created_ts, metadata_id, start_ts, mean, min, max) 
  SELECT created_ts, 621 as metadata_id, start_ts, mean, min, max  -- metadata_id is of the destination entity
  FROM statistics_short_term
  WHERE metadata_id = 432    -- metadata_id is of the source entity
  AND start_ts < 1728511200  -- start_ts excludes the last record of the from data which already exists for the new entity
  ORDER BY start_ts
INSERT INTO statistics (created_ts, metadata_id, start_ts, mean, min, max) 
  SELECT created_ts, 621 as metadata_id, start_ts, mean, min, max  -- metadata_id is of the destination entity
  FROM statistics
  WHERE metadata_id = 432    -- metadata_id is of the source entity
  AND start_ts < 1728511200  -- start_ts excludes the last record of the from data which already exists for the new entity
  ORDER BY start_ts

Then check the target entity history (step 6) and remove the statistics history from the source entity (step 7).

  1. Dealing with different units of measurement on source and target

An issue I found with migrating the LTS history of one sensor was that the unit of measurement must have been setup differently for the source and destination:

Although the source and destinatione entities have the same kW unit of measurement, at some point the source entity (sensor.xxx_raw) must have been setup as W and so the statistics are all being stored in W.

Can see this in the underlying data.

The source records all have mean/min/max values greater than zero as they are in Watts:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
2681605	NULL	1728507612.130479	432	NULL	1728504000.0	13.146	6.15	22.0	NULL	NULL	NULL	NULL
2681909	NULL	1728511210.7619662	432	NULL	1728507600.0	11.113	7.84	18.1	NULL	NULL	NULL	NULL
2682214	NULL	1728514811.362695	432	NULL	1728511200.0	8.743	6.2	13.01	NULL	NULL	NULL	NULL

Whereas the destination records all have mean/min/max values less than zero as they are in kW:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
2682274	NULL	1728514811.3690288	621	NULL	1728511200.0	0.00857	0.00798	0.00898	NULL	NULL	NULL	NULL
2682583	NULL	1728518410.445632	621	NULL	1728514800.0	0.00883	0.00799	0.01078	NULL	NULL	NULL	NULL
2682890	NULL	1728522011.1163657	621	NULL	1728518400.0	0.00832	0.00648	0.01025	NULL	NULL	NULL	NULL

Have to decide which unit of measurement we wish to keep. In my case I prefer to have all of my power and entity sensors in kW so the destination has the correct unit of measurement.

There’s three additional commands therefore required to update the source records before we migrate them:

UPDATE statistics_short_term
SET mean = mean / 1000, min = min / 1000, max = max / 1000 -- convert mean/min/max from W to kW by dividing by 1000
WHERE metadata_id = 432 -- metadata_id is of the source entity
UPDATE statistics
SET mean = mean / 1000, min = min / 1000, max = max / 1000 -- convert mean/min/max from W to kW by dividing by 1000
WHERE metadata_id = 432 -- metadata_id is of the source entity
UPDATE statistics_meta
SET unit_of_measurement = 'kW'
WHERE id = 432 -- id is of the source entity

The first two commands divide the mean, min and max of the existing source records by 1000 and the last command updates the unit of measurement for the source statistics.

If you wish to retain the source destination unit of measurement then its the same additional step, just update the destination entities.

Similarly if you need to adjust the unit of measurement for a total sensor, update the state and sum values appropriately.

Once the existing statistics have been adjusted, the entity statistics can be copied from the source to the destination entity as per step 9.

1 Like

Scenario 6: moving statistics from ‘today’ to ‘total’ entity

This is a sub-variant of scenario 5, but as there are some different steps to follow and some of it is like scenario 7 so I felt it worthwhile pulling out separately.

With GivTCP (the addon that integrates my inverters with HA) there is a problem that occurs randomly when one (or more) of the total sensors suddenly jumps to a massively bigger number, only to jump back down when HA or the GivTCP add-on is restarted at some point later on.

(I had two such spikes in this 4 day period)

We could use scenarios 2 or 3 to reload the errant total entity from the GivEnergy Portal, but actually the today entity has been working throughout and we can use that as the source to recover the battery charge total entity:

You absolutely must take a backup beforehand !

Best practice would be to copy the history into STS (statistics_short_term table) as well as LTS (statistics), but since the STS is purged after 14 days and for our purposes is only used to build the LTS, as long as a few hours have passed since the glitch occurred, we can safely only manipulate LTS and leave STS with a spike that will naturally expire.
Plus repeating the process for STS as well as LTS doubles all the SQL to execute, for limited benefit.

  1. Determine the date/time range to look at

Similar to previous scenarios, determine a window of UNIX datetimes when the glitch occurred - the procedure below relies on the start time being aligned to the start of a day (00:00), but the end time does not need to be, as long as it aligns to an exact hour.
I’m going to choose the time range as 00:00 on 8/2/25 through to 23:00 on 11/2/25 which are a bit before and a bit after the actual issue as you need to blend the corrected data in properly.

Developer tools/template

{% set ds="08.02.2025 00:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="11.02.2025 23:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the Unix timestamps for the start and end as 1738972800 and 1739314800 respectively.

  1. View the source data we need to migrate from the today entity:

SQLweb add-on. Paste in the following query, substituting your own entity id:

SELECT * FROM statistics
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.givtcp_xxxx_battery_charge_today_energy_kwh')  -- source entity id that is being migrated from
AND start_ts >= 1738976400   -- start time stamp
AND start_ts <= 1739314800   -- end time stamp
ORDER BY start_ts

My records look like this:


id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
3633037	NULL	1738980010.7166677	84	NULL	1738976400.0	NULL	NULL	NULL	NULL	NULL	0.0	-10.1
3633345	NULL	1738983610.333431	84	NULL	1738980000.0	NULL	NULL	NULL	NULL	NULL	0.0	-10.1
...
7.8	-2.3
3636750	NULL	1739023210.269987	84	NULL	1739019600.0	NULL	NULL	NULL	NULL	NULL	10.2	0.1
3637057	NULL	1739026810.2977054	84	NULL	1739023200.0	NULL	NULL	NULL	NULL	NULL	12.6	2.5
3637364	NULL	1739030410.3315246	84	NULL	1739026800.0	NULL	NULL	NULL	NULL	NULL	...
3639205	NULL	1739052010.2764275	84	NULL	1739048400.0	NULL	NULL	NULL	NULL	NULL	14.6	4.5
3639511	NULL	1739055610.265842	84	NULL	1739052000.0	NULL	NULL	NULL	NULL	NULL	17.0	6.9
3639817	NULL	1739059210.2937791	84	NULL	1739055600.0	NULL	NULL	NULL	NULL	NULL	19.4	9.3

[start of next day]
3640124	NULL	1739062810.3110845	84	NULL	1739059200.0	NULL	NULL	NULL	NULL	NULL	0.0	-10.1
3640431	NULL	1739066410.8390033	84	NULL	1739062800.0	NULL	NULL	NULL	NULL	NULL	0.0	-10.1
3640738	NULL	1739070010.2807505	84	NULL	1739066400.0	NULL	NULL	NULL	NULL	NULL	0.0	-10.1
3641045	NULL	1739073610.2697823	84	NULL	1739070000.0	NULL	NULL	NULL	NULL	NULL	
0.0	-10.1

From the source entity data in LTS, look at the first record and make a note of the source entity metadata_id (84), state (0.0) and sum (-10.1).

Can see that state increases from zero throughout the day, then resets back to zero at the start of the next day. sum follows a similar pattern of increasing through the day with state and then setting back to the start of day value, but for some reason (a glitch/corruption somewhere in the LTS history) my sum is offset by -10.1kWh.

Note that the difference between the state (0.0) and sum (-10.1) on the first record, -10.1, is the same as the difference on all the other records. The sum is always 10.1 less than the state.

Make a note of this difference figure. Take care of the signs of the state and sum when working out the difference figure, e.g. on my second inverter the state on the first record is 0.6 and the sum is -4.6, so the difference is 5.2.

(Interestingly the sum value resets at the start of each day to a negative number on both the battery charge and battery discharge today entities on both of my inverters. Will need to find out why and fix it - I use the total entities not the today entities in the Energy dashboard so this isn’t visible, but now I know its there my OCD will fester !)

  1. View the LTS for the destination total entity we are migrating to:
SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.givtcp_xxxx_battery_charge_total_energy_kwh')  -- destination entity id that is being migrated to
AND start_ts >= 1738976400 - 3600) -- start time stamp, the minus 3600 is so we can see the record immediately an hour before the migration time period
AND start_ts <= 1739314800          -- end time stamp
ORDER BY start_ts

The first records are:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
3632429	NULL	1738972810.3138041	95	NULL	1738969200.0	NULL	NULL	NULL	NULL	NULL	3076.9	3832.8
3633045	NULL	1738980010.7166677	95	NULL	1738976400.0	NULL	NULL	NULL	NULL	NULL	3076.9	3832.8
3633353	NULL	1738983610.333431	95	NULL	1738980000.0	NULL	NULL	NULL	NULL	NULL	5252.0	3832.8
3633661	NULL	1738987210.3023984	95	NULL	1738983600.0	NULL	NULL	NULL	NULL	NULL	5252.0	3832.8
3633969	NULL	1738990810.2936854	95	NULL	1738987200.0	NULL	NULL	NULL	NULL	NULL	5252.0	3835.2
3634277	NULL	1738994410.3125093	95	NULL	1738990800.0	NULL	NULL	NULL	NULL	NULL	5252.0	3837.6
3634585	NULL	1738998010.3042452	95	NULL	1738994400.0	NULL	NULL	NULL	NULL	NULL	5252.0	3840.0
3634892	NULL	1739001610.3087769	95	NULL	1738998000.0	NULL	NULL	NULL	NULL	NULL	5252.0	3840.0
...

Again for the entity we are migrating to, note the destination metadata_id (95), the state (3076.9) and sum (3832.8) from the first record (which is an hour before the time range we are restoring).

Can see that the state jumps up on the second record and remains constant thereafter. sum updates on the first few records but then stalls at 3840.0.

  1. Determine the start date/time for any intermediate days

We are going to update state and sum on the total entity from the today entity on a per-day basis (i.e. one set of SQL commands per day). So we need to determine the UNIX date/time for the start of any intermediate days.

If the correction is being applied to only one day then this step isn’t required.

In my case I am migrating history from the 8th, 9th, 10th and 11th of February. I already have the start time on 8th February (1738976400) so need to repeat the template command for the start (time 00:00) of the other days:

{% set ds2="09.02.2025 00:00" %}
{{ strptime(ds2,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set ds3="10.02.2025 00:00" %}
{{ strptime(ds3,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set ds4="11.02.2025 00:00" %}
{{ strptime(ds4,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the Unix timestamps for the start of 9th February as 1739059200, 10th as 1739145600 and 11th as 1739232000.

  1. Copy the long term statistics from the today entity to the total entity for the first day

As mentioned in step 4 we are going to copy the today history to the total history day by day, starting with the first day, 8th February for me.

Its critical to get the start and end time range correct.

start time = the start time on day 1 (i.e. 00:00 on 8/2/25 for me), 1738972800
end_time = if updating just one day then its the end time on that day, but as I am updating multiple days, its the start time of day 2 (i.e. 00:00 on 9/2/25), 1739059200

We will need the state and sum values from the last record on the prior day, i.e. the first record in the step 3 query on the total entity (3076.9 and 3832.8 respectively), and an adjustment factor of the difference between state and sum on the first today record (i.e. 10.1) as obtained in step 2.

Execute the following SQL command:

UPDATE statistics AS total
SET state = 3076.9 + today.state,     -- state value from first total record (step 3)
    sum = 3832.8 + today.sum + 10.1   -- sum value from first total record, adjustment factor for sum not being zero on today table
FROM (SELECT start_ts, state, sum FROM statistics
            WHERE metadata_id = 84        -- metadata_id is of the source, today entity
              AND start_ts >= 1738972800  -- start time stamp for 00:00 on the first day
              AND start_ts < 1739059200   -- start time stamp for 00:00 on day 2 OR if only a single day, use end time stamp
      ) AS today
WHERE metadata_id = 95   -- metadata_id is of the target, total entity
  AND total.start_ts = today.start_ts

Recommend repeating the SQL query from step 3 to check the total entity state and sum values as they have been updated for day 1. If you look at the Energy dashboard you’ll see day 1 all populated and a spike on day 2.

(if you get the sign on the adjustment factor the wrong way round, you’ll see an additional spike on day 1 in the Energy dashboard, simply correct the adjustment factor and re-run the SQL)

If you’re only updating one day of history, then can jump to step 9 to deal with any post-migration spike.

  1. Obtain the start state and sum values for day 2

Having copied the today history into total for day 1 (8th February), we need to find the values updated into the last record at 23:00 of day 1 because these will be used as the start values for day 2.

SQL command:

SELECT start_ts, state, sum
FROM statistics
WHERE metadata_id = 95               -- metadata_id is of the target, total entity
  AND start_ts = (1739059200 - 3600) -- start time of 00:00 on day 2, minus 3600 for one hour earlier

will give a result like this:

start_ts	state	sum
1739055600.0	3096.3	3852.2
  1. Copy the long term statistics from the today entity to the total entity for the second day

This is very similar to step 5, just different start and end times for day 2 and the state and sum come from the end of day 1 data obtained in step 6 above

start time = the start time on day 2 (i.e. 00:00 on 9/2/25 for me), 1739059200
end_time = if this is the last day being updated then its the end time of the affected records (obtained in step 1), but as I am updating more than 2 days, its the start time of day 3 (i.e. 00:00 on 10/2/25), 1739145600

We will need the state and sum values from the last record in day 1’s total (3096.3 and 3852.2 respectively) as obtained in step 6, and the same adjustment factor for sum as used in step 5.

SQL:

UPDATE statistics AS total
SET state = 3096.3 + today.state,     -- state value from end of day 1 total record (step 6)
    sum = 3852.2 + today.sum + 10.1   -- sum value from end of day 1 total (step 6), adjustment factor of the difference between state and sum on the today records
FROM (SELECT start_ts, state, sum FROM statistics
            WHERE metadata_id = 84        -- metadata_id is of the source, today entity
              AND start_ts >= 1739059200  -- start time stamp for 00:00 on day 2
              AND start_ts < 1739145600   -- start time stamp for 00:00 on day 3 OR if this is the last day, use end time stamp
      ) AS today
WHERE metadata_id = 95   -- metadata_id is of the target, total entity
  AND total.start_ts = today.start_ts
  1. Repeat for days 3, 4, …

Now repeat steps 6 and 7 for days 3, 4, etc

Take care over:

  • obtaining the state and sum values from the last record of the previous day (step 6) to update the next day
  • entering the correct start and end times for each day of updates (step 7)

Recommend repeating the SQL query from step 3 to check the total entity state and sum values as they are being built up.

  1. Dealing with additional Energy dashboard spike after the loaded history

After correcting the total entity statistics history from the today entity history, it likely that will be a subsequent spike showing in the Energy dashboard. This is due to the sum value not incrementing correctly across the transition from the replaced history to the existing history records.

This is very similar to step 12 of scenario 2, so for brevity, a summary of the correction steps follows:

Looking at the data through the transition by repeating the SQL query from step 3 but expand the ‘start_ts where clauses’ to include a few more records either side, i.e.:

SELECT * FROM statistics 
WHERE  metadata_id = (SELECT id
                      FROM   statistics_meta
                      WHERE  statistic_id = 'sensor.givtcp_xxxx_battery_charge_total_energy_kwh')  -- destination (total) entity id that is being migrated to
AND start_ts >= (1738976400 - 3*3600)   -- start time stamp minus 3 hours
AND start_ts <= (1739314800 + 3*3600)   -- end time stamp plus 3 hours
ORDER BY start_ts

This shows the LTS records before the history restore, the records we updated, and a few afterwards. Focussing on the records at the end:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
[LTS records updated from today entity:]
3660732	NULL	1739307610.1993396	95	NULL	1739304000.0	NULL	NULL	NULL	NULL	NULL	3148.8	3904.7
3661032	NULL	1739311210.203312	95	NULL	1739307600.0	NULL	NULL	NULL	NULL	NULL	3148.8	3904.7
3661332	NULL	1739314810.202854	95	NULL	1739311200.0	NULL	NULL	NULL	NULL	NULL	3151.2	3907.1

[existing LTS records that are immediately after the history update:] 
3661633	NULL	1739318410.204776	95	NULL	1739314800.0	NULL	NULL	NULL	NULL	NULL	3153.6	3849.2
3670016	NULL	1739322010.1814265	95	NULL	1739318400.0	NULL	NULL	NULL	NULL	NULL	3153.7	3849.3
3670318	NULL	1739325611.0024757	95	NULL	1739322000.0	NULL	NULL	NULL	NULL	NULL	3153.7	3849.3

In the records before we updated the history and in the records we updated (up to and including start_ts 1739311200.0) there is a consistent offset difference between state and sum of 755.9. As there has been a problem with the entity state and history wasn’t captured properly for a period of time, the consistency is broken for all records after our history update where the offset is now 3849.2-3153.6=695.6.

This jump in value causes a spike in the Energy dashboard immediately after the history load (23:00 on 11/2/25).

The fix we need to apply (borrowing from the techniques in scenarios 5 and 7) is to adjust sum to bring it into line with state by adding 755.9 - 695.6 = 60.3 to the first record after the history insertion and every record thereafter (because they will all be wrong by this delta amount):

UPDATE statistics_short_term
SET sum = sum + 60.3  -- add adjustment factor to correct for missing sum history
WHERE metadata_id = 95          -- metadata_id is the total entity being corrected
AND start_ts >= 1739314800.0	-- start_ts is the time of the first record after the history load

and statistics:

UPDATE statistics
SET sum = sum + 60.3  -- add adjustment factor to correct for missing sum history
WHERE metadata_id = 95          -- metadata_id is the total entity being corrected
AND start_ts >= 1739314800.0	-- start_ts is the time of the first record after the history load

Scenario 7: Dealing with spikes and glitches in HA statistics using SQL

Step 12 of scenario 2 and step 8 of scenario 5 have both already covered part of this, but for a variety of reasons you can end up with glitches or spikes in HA data.

These can manifest themselves either in big jumps in entity values as seen in entity values / entity history

e.g.

or in the Energy dashboard:

You can fix some of these with Developer tools / Statistics, but:
a) its laborious to only be able to change one entity statistics value at a time
b) you only have the option to change ‘delta value’ for each 5 minute/hourly statistics value, you can’t change the underlying state or sum values, and
c) only the sum value is changed, so the Energy dashboard is OK, but the state value in unchanged so any History graphs are stil messed up

Suggest that it can be better to directly manipulate the statistics entity values and state history in the database using SQL fixes.

For this scenario great care should be taken to ensure you know what you are updating, take a backup beforehand, and if possible practice on a test HA server first.

It is recommended that you DO NOT directly manipulate the HA database whilst HA is running. Although in theory it should work, in practice I have corrupted the HA database twice by doing this so I recommend you shutdown HA, take a copy of the HA database to your local machine, manipulate the local SQLite database, then replace the HA database - ensuring that you keep a copy of the original HA database as well in case you need to recover back to it.

  1. Determine the date/time range to look at

Similar to previous scenarios, determine a window of UNIX datetimes when the glitch occurred - choose times a bit before and a bit after the actual issue so you can see how to blend the corrected data in properly.

Developer tools/template

{% set ds="23.02.2025 13:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="23.02.2025 21:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the Unix timestamps for the start and end as 1740315600 and 1740344400 respectively.

  1. Query the existing LTS for the range of records we are going to be updating.

SQLweb add-on. Paste in the following query, substituting your own entity id, start and end timestamps (from above step):

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.givtcp_INVID_battery_charge_energy_total_kwh'  -- entity id
AND start_ts >= 1740315600  -- start time stamp
AND start_ts <= 1740344400  -- end time stamp
ORDER BY start_ts

Gives a set of records looking like this:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
3851513	NULL	1740319210.507068	176	NULL	1740315600.0	NULL	NULL	NULL	NULL	NULL	4891.8	3912.399	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3851815	NULL	1740322810.5275633	176	NULL	1740319200.0	NULL	NULL	NULL	NULL	NULL	4891.8	3912.399	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3852117	NULL	1740326410.5294979	176	NULL	1740322800.0	NULL	NULL	NULL	NULL	NULL	5171.8	4192.4	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3852421	NULL	1740330010.5471663	176	NULL	1740326400.0	NULL	NULL	NULL	NULL	NULL	5171.8	4192.4	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3852725	NULL	1740333610.5423589	176	NULL	1740330000.0	NULL	NULL	NULL	NULL	NULL	5171.8	4192.4	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3853029	NULL	1740337210.582989	176	NULL	1740333600.0	NULL	NULL	NULL	NULL	NULL	5171.8	4192.4	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3853333	NULL	1740340810.5545213	176	NULL	1740337200.0	NULL	NULL	NULL	NULL	NULL	5171.8	4192.4	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3853637	NULL	1740344410.5329993	176	NULL	1740340800.0	NULL	NULL	NULL	NULL	NULL	4892.3	3912.899	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL
3853941	NULL	1740348010.5478377	176	NULL	1740344400.0	NULL	NULL	NULL	NULL	NULL	4892.3	3912.899	176	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	0	1	NULL

Focussing on the state and sum values, can see that at start_ts of 1740322800.0, state jumps from 4891.8 to 5171.8 (increase of 280) and sum jumps from 3912.399 to 4192.4 (also an increase of 280).

Then state and sum don’t change until at start_ts of 1740340800.0, state jumps back from 5171.8 to 4892.3 (drop of 279.5) and sum jumps back from 4192.4 to 3912.899 (also a drop of 279.5).

NB: If the spike has occurred very recently (i.e. last 10 days) then the STS should be queried to determine what the start_ts is of the start and end affected STS records - they will not be quite the same as the LTS time range as STS is captured every 5 minutes and LTS every hour:

SELECT * FROM statistics_short_term
LEFT JOIN statistics_meta ON statistics_short_term.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.givtcp_INVID_battery_charge_energy_total_kwh'  -- entity id
AND start_ts >= 1740315600  -- start time stamp
AND start_ts <= 1740344400  -- end time stamp
ORDER BY start_ts
  1. Correcting the entity ‘spikes’

Both sensor history and long term statistics jumped up and down so we can’t follow scenario 1 to recover the statistics.
Could follow scenario 2 or 3 to obtain the correct data from the GivEnergy Portal, but its only a few hours of one day, and over the time period the underlying sensor only actually changed by 0.5kWh (seen as the difference between the jump up of 280 and the jump down of 279.5) so there’s not a lot of detail to extract.

Instead, far simpler is to just correct the statistics data directly. This won’t change the entity history, but its getting the Energy dashboard data right for long term viewing that I care more about, and the entity history will be purged after a few days anyway so limited value in correcting it.

The fix we need to apply is to adjust state and sum for the time period of the spike by simply subtracting 280 from each. Make sure that the correct start_ts time range is used for STS and LTS as they will be slightly different:

UPDATE statistics_short_term
SET state = state - 280, sum = sum -280 -- add/subtract adjustment factor to correct for the spike
WHERE metadata_id = 176      -- metadata_id is of the entity to be corrected
AND start_ts >= 1740324300.0 -- start_ts is the time of the first record affected
AND start_ts <= 1740340500.0 -- start_ts of the last record affected

and statistics:

UPDATE statistics
SET state = state - 280, sum = sum -280 -- add/subtract adjustment factor to correct for the spike
WHERE metadata_id = 176      -- metadata_id is of the entity to be corrected
AND start_ts >= 1740322800.0 -- start_ts is the time of the first record affected
AND start_ts <= 1740337200.0 -- start_ts of the last record affected

This technique of directly manipulating sum (and state) can be used to solve many LTS issues that impact history graphs (state) and the Energy dashboard (sum) in Home Assistant.

Just be careful to limit the UPDATE SQL to specific records using metadata_id, start_ts >= and start_ts <= as appropriate.

As the LTS are created from the STS, make sure that both tables are updated with any corrections and statistics_short_term is updated first.

Also recommended to test the SQL first (using a SELECT statement) or run it on a test HA instance. I trialled all of these techniques on a test HA server before repeating on my live server.

3b. Correcting just a single record/small group of records

As an alternative to step 3 above, you may just have a single record or small group of records to correct that you can visually see what the required change was and its simple to just adjust the individual state and sum values.

e.g. I had a spike of 2 hours in one of my total entities:

id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum
NULL	4955.8	4004.5
3904430	NULL	1740938410.549772	176	NULL	1740934800.0	NULL	NULL	NULL	NULL	NULL	4955.9	4004.6
3904744	NULL	1740942010.5286033	176	NULL	1740938400.0	NULL	NULL	NULL	NULL	NULL	5923.1	5002.3
3905058	NULL	1740945610.546521	176	NULL	1740942000.0	NULL	NULL	NULL	NULL	NULL	5924.4	5003.6
3905372	NULL	1740949210.5324569	176	NULL	1740945600.0	NULL	NULL	NULL	NULL	NULL	4956.3	4005.0
3905686	NULL	1740952810.5214067	176	NULL	1740949200.0	NULL	NULL	NULL	NULL	NULL	4956.3	4005.0
3906000	NULL	1740956410.5080047	176	NULL	1740952800.0	NULL	NULL	NULL	NULL	NULL	4957.6	4006.3

start_ts records 1740938400.0 and 1740942000.0 have spiked, which I could see in the Energy dashboard. I looked at the today sensor values to work out what the corrected values should be, and then individually corrected each statistics row by specifying the metadata_id and start_ts I required to update:

UPDATE statistics AS total
SET state = 4955.9,     -- corrected state value 
    sum = 4004.6        -- corrected sum value
WHERE metadata_id = 176 -- metadata_id is of the target, total entity
  AND total.start_ts = 1740938400.0

And repeat for the second row

  1. Check everything corrected OK

Check the energy dashboard, this should look OK now:

The sensor history will still show a spike as this is populated from entity history. In a few days (default 10) the state history will have been purged and entity history will be populated from LTS and will also be correct.

Scenario 8: Dealing with glitches in HA statistics data using DB Browser

Scenario 7 and in some of the steps from other scenarios I’ve shown how you can use SQL UPDATE and SQL INSERT commands to directly manipulate statistics data in the HA database. You can of course use SQL DELETE to remove records although need to take great care to ensure any DELETE or UPDATE is suitably qualified with WHERE clauses to ensure you only update the specific subset of records you intend to change.

This scenario shows how you can directly manipulate HA statistics in a spreadsheet-like way which for minor updates like changing a single value, or where you want to look at the data as you change it, can be easier.

As ever, great care should be taken to ensure you know what you are updating, take a backup beforehand, and if possible practice on a test HA server first.

It is recommended that you DO NOT directly manipulate the HA database whilst HA is running. Although in theory it should work, in practice I have corrupted the HA database twice by doing this so I recommend you shutdown HA, take a copy of the HA database to your local machine, manipulate the local SQLite database, then replace the HA database - ensuring that you keep a copy of the original HA database as well in case you need to recover back to it.

The following steps are written based on connecting to HA from a PC, but should work similarly from a Mac.

Step 1. Install DB Browser for SQLite

Go to https://sqlitebrowser.org/ to read about the tool, then https://sqlitebrowser.org/ to download a version for PC or Mac. I installed the 64 bit PC version.

Step 2. Determine what records are to be manipulated

Let’s say we want to update battery charge total for 17th September 2025

Firstly need to determine the metadata_id for that entity with the following SQL add-on command:

SELECT * FROM statistics_meta
WHERE statistics_meta.statistic_id = 'sensor.givtcp_INVID_battery_charge_energy_total_kwh'  -- entity id

gives a result like this for me:

id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name
95	sensor.givtcp_INVID_battery_charge_total_kwh	recorder	kWh	0	1	NULL

so the metadata_id number as will be used in the statistics table is 95

Determine the start and end time of records to be selected with Developer tools / template:

{% set ds="25.09.2025 00:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

{% set de="25.09.2025 23:00" %}
{{ strptime(de,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

Gives the unix timestamps for the start and end as 1757977200 and 1758060000 respectively.

We can see the spike I need to fix by running the usual SQL query:

SELECT * FROM statistics
LEFT JOIN statistics_meta ON statistics.metadata_id = statistics_meta.id
WHERE statistics_meta.statistic_id = 'sensor.givtcp_xxxx_battery_charge_energy_total_kwh'  -- entity id
AND start_ts >= 1757977200 -- start time stamp
AND start_ts <= 1758060000 -- end time stamp
ORDER BY start_ts
id	created	created_ts	metadata_id	start	start_ts	mean	min	max	last_reset	last_reset_ts	state	sum	mean_weight	id	statistic_id	source	unit_of_measurement	has_mean	has_sum	name	mean_type
...
5424195	NULL	1758027610.2397687	95	NULL	1758024000.0	NULL	NULL	NULL	NULL	NULL	5550.6	8304.900000000249	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5424500	NULL	1758031210.2336378	95	NULL	1758027600.0	NULL	NULL	NULL	NULL	NULL	5551.4	8305.700000000248	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5424805	NULL	1758034810.2207186	95	NULL	1758031200.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5425111	NULL	1758038410.2225094	95	NULL	1758034800.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5425417	NULL	1758042010.2281468	95	NULL	1758038400.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5425723	NULL	1758045610.2396145	95	NULL	1758042000.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5426029	NULL	1758049210.229907	95	NULL	1758045600.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5426335	NULL	1758052810.218247	95	NULL	1758049200.0	NULL	NULL	NULL	NULL	NULL	5999.8	8754.10000000025	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5426641	NULL	1758056410.2388473	95	NULL	1758052800.0	NULL	NULL	NULL	NULL	NULL	5552.4	8306.700000000248	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
5426947	NULL	1758060010.2436945	95	NULL	1758056400.0	NULL	NULL	NULL	NULL	NULL	5553.2	8307.500000000251	NULL	95	sensor.givtcp_INVID_battery_charge_energy_total_kw ...	recorder	kWh	NULL	1	NULL	0
...

On record 5424805, state jumps from 5551.4 to 5999.8 and sum from 8305.7 to 8754.1. Then on record 5426641 state and sum drop back down to the correct values. This jump up and down causes a big spike up and down in the Energy dashboard. I will fix this by overwriting the bad state and sum values. The precise values don’t matter too much but I’ll use state values of 5551.6, 5551.8, 5552.0, etc (with corresponding sum increments) to give a smooth data transition.

Step 3. Establish SSH connection to HA

As mentioned the safest way to manipulate the HA database is with HA shutdown. To do this we need to establish a connection to the HA Supervisor using the SSH add-on addons/ssh at master · home-assistant/addons · GitHub

Settings / Add-ons / Add-on store
Search for “ssh”
Select ‘Terminal and SSH’
Click Install

Then click Configuration, enter a password, click save
Start the add-on

You will need to install a SSH client on your PC. There are lots available for different platforms, I use PuTTY PuTTY: a free SSH and Telnet client

Install the SSH client and open a connection to homeassistant.local
When prompted the username to ‘login as’ is “root” and the password is as you set it in the SSH add-on configuration

Step 4. Establish a file connection to HA

I found the easiest way to copy files from HA is to install the Samba add-on addons/samba/DOCS.md at b10dd00691712e16714f5e5d0943c19fe5b37a56 · home-assistant/addons · GitHub

Settings / Add-ons / Add-on store
Search for “samba share”
Install

Then click Configuration, enter a username and password
Start the add-on

From your PC/Mac, in file manager, enter the path \homeassistant.local
you should be prompted to enter username and password (as entered in the Samba addon configuration)

Should now be able to browse your HA server filesystem and copy/edit files directly on it from your PC

Step 5 Shutdown Home Assistant and copy the HA database to your PC

In a SSH session, check HA is OK beforehand:

ha core check

And then shutdown HA:

ha core stop

Then using your PC file browser, navigate to the HA database:

Browse to \homeassistant.local, then the config folder, then click home-assistant_v2.db

Copy home-assistant_v2.db to a folder on your PC

Step 6. Open the HA database

DB Browser will directly read and write your HA database.

DB Browser, File/ Open browse to the local PC folder where you copied your HA database (it is recommended to not directly manipulate the database on the HA server), then click home-assistant_v2.db

The default view is the database structure, a tree structure of the tables and indexes in the database

Step 7. Browse HA statistics data

Click the Browse Data tab and choose ‘statistics’ from the dropdown table list

You’ll see all the data on the statistics table.

Using the metadata id and start time determined in step 2, enter ‘95’ in the box at the top of the metadata_id column
You’ll see a filtered list of records from the statistics table, but showing entries with metadata id’s of 95, 195, 295, etc

Change the entry in the metadata_id column to ‘=95’ to ensure only records that exactly match the value 90 are selected

In start_ts enter ‘>= 1757977200’ - this gives all records with a start_ts greater than or equal to the start time, but I haven’t worked out an obvious way of adding a second filter restriction (of <= end time)

Click the start_ts column to ensure you are looking at the records in ascending time sequence order

If you want to hide some columns from the view, right mouse click the column name and Hide column, e.g. I hid created_ts/mean/min/max/last_reset/last_reset_ts so I could focus on the start_ts, state and sum columns

Step 8. Correct statistics rows

Take care, this is going to start changing your HA data. Ideally have a backup beforehand in case you make a mistake!

Simply over-type the bad data entries with the correct values. In my case I am changing state and sum for half a dozen records.

Click the value you wish to change and type in the corrected value.

If you want to delete a statistics row, click the row number on the left hand side to select the row, right mouse click and delete record

How easy it is to create chaos !

Actually at this stage you’ve not actually changed the database, in the top row of the screen is the ‘Undo’ command and ‘Revert Changes’ which will do what they say.

If you are sure you want to delete these rows, click ‘Write Changes’

Then click ‘Close Database’

Step 9. Copy the updated database back to HA using the Samba share and restart HA

This is the reverse of step 5

Recommended to make a copy of home-assistant_v2.db on the HA server so you have a copy to easily revert to if need be.

I just use file manager on the PC to copy/paste the database to a ‘copy’ name.

Then copy the modified HA database from your PC to \homeassistant.local\config and replace the existing file

In the SSH terminal window, restart HA:

ha core start

It will take a few minutes to restart

Step 10. Check everything is OK

Have a look at the HA system log and check for no unexpected errors. You might get ‘The system could not validate that the sqlite3 database at //config/home-assistant_v2.db was shutdown cleanly’ but this should be OK.

Check the Energy dashboard, the previously bad data with the SQL addon, etc.

If HA decides the database is corrupt it will move the corrupted database to a new filename and create an empty database. The recovery is to shutdown HA (using SSH) and copy the HA database backup you took back over home_assistant_v2.db.

Step 11. Tidy Up

After you are sure that everything is working fine, delete any backup copies of the HA database that are in the /config folder. Or (for a few days) move them to a different folder.
The reason for doing this is that any such database copies in the /config folder will be backed up as part of your normal HA backup regime, thus doubling the size of your backups.

2 Likes

Admirable. I toff my hat to you sir. Having clear examples and diagrams makes it easy to follow.

I can see you have put an enormous amount of thought into this terribly important subject that most people encounter at some time. It should become part of a FAQ and the official documentation. The functionality to safely ‘tweak’ the data should be expanded to cover the scenarios outlined down the track. Devs: you can see the need exists.

Can I suggest maybe the portion I have quoted be scenario 0 - right there at the top before people dive in where angels fear to tread. If you get something wrong the consequences can be drastic. Doing it automagically, even as an addon, may also be an alternative for those of us prone to fumble fingers. Both initiating the backup, and the cleanup process after all is said and done.

Thanks for the feedback, yes this has been an awful lot of time spent on this, not just to work out for myself how to do it to solve data issues I had, but also to try to make it so others could follow.

I agree, it is not for the faint hearted, it is easy to make mistakes, particularly with SQL, and have catastrophic problems as a result. For a long time I tested all of my corrections on a test HA server and it was only after doing that a lot before I progressed to doing them on live, and with extra backups beforehand. And I still made occasional mistakes, loading a GBP sensor with a kWh unit of measure and stopping statistics working, loading the wrong sensor, etc. You have to be so careful.

I do feel this is an area where HA is sadly lacking. It really needs much better facilities for people to visualise and correct the data because things do go wrong with sensors or you change your mind about the setup and need to manipulate and migrate the history. I had high hopes for scenario 8, using the db browser tool as it was easy to visualise the data, but as I say above, I corrupted my HA database twice when using it. In my initial draft of this I didn’t share this scenario because I was scared of using it and it breaking HA. Only after returning to it was I able to work out how to use the tool reliably.

I have added some more text at the beginning and to scenario 7 to highlight the dangers of these steps and to backup and test everything.

2 Likes

Wow. Great. Thx a lot @geoffreycoan, I’ll link from my repo to your documentation here.

1 Like

That’s a great contribution, thank you very much for your work!

My addition to this: The timestamps do not have to be determined by templates first, but can also be specified directly in SQLite.

Instead of …

{% set ds="24.11.2025 03:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}
{% set ds="24.11.2025 09:00" %}
{{ strptime(ds,'%d.%m.%Y %H:%M')|as_timestamp()|int }}

SELECT FROM "statistics"
WHERE metadata_id = 
	  ( SELECT metadata_id FROM states_meta 
        WHERE entity_id = 'sun.sun' )
  AND start_ts >= 1763949600
  AND start_ts <= 1763971200

…using…

SELECT * FROM "statistics" 
WHERE metadata_id = 
	  ( SELECT metadata_id FROM states_meta 
        WHERE entity_id = 'sun.sun' )
  AND start_ts BETWEEN UNIXEPOCH('2025-11-24T03:00','utc') 
                   AND UNIXEPOCH('2025-11-24T09:00','utc')
1 Like

Thanks, that’s a useful simplification to the way I did it. Cuts a step out.

I also did find out how to convert a Unix date/time (e.g. start_ts) to a printable date/time in Excel/Google Sheets, but by the time I’d done that I already had a process that worked so didn’t seem much point in changing things.

To convert cell A2, use the formula:

=(A2/86400)+DATE(1970,1,1)

And change the cell format to date/time

Thats a lot of work there, nice one. I wiill surely try to remove my entities which i still have on “disabled” integrations just so i dont loose the long term statistics. In theory energy dashboard related items should have a virtual ‘device’ where it reports to. If then your device that reports to the energy dashboard changes it doesn’t matter as your using a virtual one. Pretty sure that is possible too, but that should be done by HA core team i would say (or something in that area) maybe you mentioned it somewhere, but it was a lot of text :smile:

Hello,
I’ve discovered this post while searching a way to restore my LTS after a very dumb move that I made, trying to fix some statistics in HA I made an error while selecting the one to be deleted and the result is I lost all my energy related LTS.
I have a docker env with mariadb as backend. I set the recorder to store 366 days of states data.
I thus have a full year of detailed info, how I’d like to recover LTS for the 4/5 entities that made up the energy dashboard. - Scenario 1 -

The problem I was faced with is rather stupid: as I need to edit a large number of data rows I have downloaded you sheet and opened it in excel, Macro/code it seems not to be working.
It may be a problem on seciruty settings on my laptop.
I edited the CSV separately and copy/pasted the results in the table.
I’ll report on my findings when I’ll find the courage to issue insert/update statements :slight_smile: