0

I have two kind of "elements". One that generates time series (lets call them generator element) and the other that based on some logic rules applied to the time series became "activated" (activable elements).

This simplification represent the time series

CREATE TABLE data.time_series
(
    generator_element_id uuid NOT NULL,
    epoch bigint NOT NULL, -- UNIX epoch
    level integer NOT NULL, -- values from 0 to 3
    -- other fields
    -- PRIMARY KEY (generator_element_id, epoch),
    -- FOREIG KEY TO the table with the generator_elements
)

The idea is that the activable elements are activated following rules like

Legend:

  • Generator element GE
  • Activable element AE
IF  (GE1 has level > 1 AND GE2 has level > 1) OR (GE3 has level > 2) for a given instant T1  THEN AE1 becames activated

I'm trying to find the best way to represent this logic rules in the database, as those will be configured by the user and may include multiple conbinations of "AND" and "OR" rules

The issue is precesily with this combination of using AND's and OR's, as if it would be only one type of those I could create another table with the relation between the different kind of elements, and the thresholds. But using a combination of ANDs and ORs i'm at loss..

My first thought was to store those relations using a plain string, similar to what I described before, but using the corresponding UUIDs for the generator elements, and then have a service that will process those rules, and decide to activate or not the elements.

I'm searching for alternatives to this approach as I don't think that my approach could scalate well enough, nor I like it personally...

For context I'm using PostgreSQL 12.0 with docker

Edit:

Clarifying about the epochs as it was not clear. The epoch value is not included in the logic rule "definition", that only includes the elements and its levels (actually it includes other ids but to keep simple just this two variables).

As for the actual calculation the records will be retrieved and separated in "bins" for each epoch. This way we apply the rule over the records for that epoch (assuming that there will always be records for all the generator elements for the given epoch)

Also adding some test data as an example. In this case I'll use one epoch value 1581638400 that corresponds to 2020-02-14 00:00:00Z

INSERT INTO data.time_series
VALUES (
    '00000000-0000-0000-0000-000000000001', -- uuid for GE1
    1, --level
    1581638400 -- epoch for 2020-02-14 00:00:00Z
)

INSERT INTO data.time_series
VALUES (
    '00000000-0000-0000-0000-000000000002', -- uuid GE2
    2, --level
    1581638400 -- epoch for 2020-02-14 00:00:00Z
)


INSERT INTO data.time_series
VALUES (
    '00000000-0000-0000-0000-000000000003', -- uuid GE3
    0, --level
    1581638400 -- epoch for 2020-02-14 00:00:00Z
)

Here the rule specified as my proposal would be for the activable element AE1:

-- Omiting the 0s for simplicity
"('000...01' > 1 AND '000...02' > 1) OR  ('000...03' > 2)"

With the dummy data, the result will be that AE1 is activated as the first part of the rule is completed

But with the way that the rule is saved in my proposal, i'll have make a specific processing system for that...

Any help is apreciated

4
  • Can you post some sample data (as insert statements)? Is the following what you mean by GE1, GE2, etc: (u1, t_0, ...), (u1, t_1, ...), (u1, t_2, ...)? I.e. GE1 and GE2 share the same uuid, and have an ordering relation defined by epoch? Feb 14, 2020 at 22:55
  • Added some test data, hope it can help
    – CRE
    Feb 14, 2020 at 23:35
  • Are the rules dynamic, or is it always the same rule? Feb 15, 2020 at 0:27
  • Yes they can be dynamic
    – CRE
    Feb 15, 2020 at 9:18

1 Answer 1

0

I'll write this as an answer, even though it's just a few ideas. Breaking down the logical rules in a relational form is probably a waste of time, I would simply store the rules using XML or JSON. A parser friendly format like:

{ OR 
    { AND 
          { GE1 has level > 1 }
          { GE2 has level > 1 }
    }
    { GE3 has level > 2 }
}   

is maybe beneficial. Note that this is just an example

This format can easily be transformed into SQL like:

WHERE ((ge=1 AND level>1) AND (ge=2 AND level=2))
   OR  (ge=3 AND level>2)

The ge attribute can be constructed as:

WITH T (uid, level, epoch, ge) AS (
    SELECT uid, level, epoch
         , row_number() over (partition by epoch
                              order by uid) as ge
    FROM data.time_series
)

All and all you would end up with a query like:

WITH T (uid, level, epoch, ge) AS (
    SELECT uid, level, epoch
         , row_number() over (partition by epoch
                              order by uid) as ge
    FROM data.time_series
)
SELECT uid, level, epoch
FROM T
WHERE ((ge=1 AND level>1) AND (ge=2 AND level=2))
   OR  (ge=3 AND level>2)

The idea is hence to read all the rules into memory, when a rule is about to be evaluated it is transformed into SQL, and the query is executed against the time_series table.

1
  • This is actually a good starting point, I'll give the question a few days to see if I get some other inputs, and if not I'll mark it as an answer
    – CRE
    Feb 16, 2020 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.