historical_entity_attrs Table
This table tracks the historical attributes associated with an entity. Each row represents the value of a particular attribute of an entity at a particular point in time. New rows are written when the value of a property changes between topologies, and at regular intervals (e.g. daily) to keep a recent value available within a reasonable time range.
| Data Object | Type | Nullable | Primary | Description | Reference |
|---|---|---|---|---|---|
| time |
timestamp with time zone |
False |
False |
When this attribute was recorded. |
|
| entity_oid |
bigint |
False |
False |
The ID of the entity this attribute pertains to. |
|
| type |
enum |
False |
False |
The attribute type. |
|
| bool_value |
boolean |
True |
False |
If the attribute is boolean, the boolean value. |
|
| int_value |
int |
True |
False |
If the attribute is an integer, the integer value. If the attribute is an enum, this is the OID of the enum in the pg_enum table (to guard against name or order changes). The int_value should be castable to the enum via a custom cast (e.g. int_value::entity_state = 'POWERED_ON'). |
|
| long_value |
bigint |
True |
False |
If the attribute is a long, the long value. |
|
| double_value |
double precision |
True |
False |
If the attribute is a double, the double value. |
|
| string_value |
text |
True |
False |
If the attribute is a string, the string value. |
|
| int_arr_value |
int[] |
True |
False |
If the attribute is a list of ints, the list values. If the attribute is a list of enums, these are the OIDs of the enums in the pg_enum table (to guard against name or order changes). |
|
| long_arr_value |
bigint[] |
True |
False |
If the attribute is a list of longs, the long values. |
|
| string_arr_value |
text[] |
True |
False |
If the attribute is a list of strings, the string values. |
|
| double_arr_value |
float8[] |
True |
False |
If the attribute is a list of doubles, the double values. |
|
| json_value |
jsonb |
True |
False |
If the attribute is a JSON object, the given object. |
Sample Use Cases
Get All Powered On Entities:
This query returns all the entities in the historical_entity_attrs that do not have a powered_off state given a certain time range.
select *
from historical_entity_attrs h1
where h1.type = 'ENTITY_STATE' and h1.int_value::entity_state != 'POWERED_OFF' AND h1.time between $__timeFrom() and $__timeTo()
Gap-filling data:
Data in this table are not written at every broadcast. Because of this, if we ask for those data in a time range, we might only get few data points. Consider this query in which we get the values for one entity in a time range:
SELECT * FROM historical_entity_attrs h1
where h1.type = 'ENTITY_STATE'
and entity_oid ='73864029152384'
and time between (timestamptz '2021-03-01T01:00:00Z' - interval '24 hour') and '2021-03-03T04:59:59Z'
order by time;

In order to solve this problem we use a timescaledb function that fills the time ranges with data, as if those data were written at a constant interval. This means that if we want data every interval t but we only have data at t1 and t4, with this function we can generate data at t2 and t3 with the same value of t1. The function that we use is called time_bucket_gapfill and here’s a sample query. Notice how in the result we have data points at a constant interval of 10 minutes:
SELECT
time_bucket_gapfill('10 min', time) AS minutes,
locf(max(h1.int_value::entity_state))
FROM historical_entity_attrs h1
where h1.type = 'ENTITY_STATE'
-- AND h1.int_value::entity_state = 'POWERED_ON'
and entity_oid ='73864029152384'
and time between (timestamptz '2021-03-01T01:00:00Z' - interval '24 hour') and '2021-03-03T04:59:59Z'
GROUP BY minutes

Casting Entity States to Integers:
Entity States are internally represented as integers. This means that a property such as POWERED_OFF is stored as a number. In order to work with this the queries need to cast the integer to the corresponding entity state. This is done with the following cast, supported by Postgres: int_value::entity_state .
So for example, take a look at the two queries below, the first one without the cast, and the second query with it:
SELECT time, entity_oid, entity_state FROM historical_entity_attrs h1
where h1.type = 'ENTITY_STATE'
and entity_oid ='73864029152384'
and time between (timestamptz '2021-03-01T01:00:00Z' - interval '24 hour') and '2021-03-03T04:59:59Z'
order by time;

SELECT time, entity_oid, int_value::entity_state FROM historical_entity_attrs h1
where h1.type = 'ENTITY_STATE'
and entity_oid ='73864029152384'
and time between (timestamptz '2021-03-01T01:00:00Z' - interval '24 hour') and '2021-03-03T04:59:59Z'
order by time;
