TimescaleDB:有效选择最后一行

时间:2018-07-28 20:25:24

标签: sql postgresql psycopg2 timescaledb

我有一个带有timescaledb扩展名的postgres数据库。

我的主要索引是时间戳,我想选择最新行。

如果我碰巧知道一段时间后发生了最后一行,那么我可以使用诸如以下的查询:

query = 'select * from prices where time > %(dt)s'

在这里指定日期时间,然后使用psycopg2执行查询:

# 2018-01-10 11:15:00
dt = datetime.datetime(2018,1,10,11,15,0)

with psycopg2.connect(**params) as conn:
    cur = conn.cursor()
    # start timing
    beg = datetime.datetime.now()
    # execute query
    cur.execute(query, {'dt':dt})
    rows = cur.fetchall()
    # stop timing
    end = datetime.datetime.now()

print('took {} ms'.format((end-beg).total_seconds() * 1e3))

定时输出:

took 2.296 ms

但是,如果我不知道输入上述查询的时间,可以使用以下查询:

query = 'select * from prices order by time desc limit 1'

我以类似的方式执行查询

with psycopg2.connect(**params) as conn:
    cur = conn.cursor()
    # start timing
    beg = datetime.datetime.now()
    # execute query
    cur.execute(query)
    rows = cur.fetchall()
    # stop timing
    end = datetime.datetime.now()

print('took {} ms'.format((end-beg).total_seconds() * 1e3))

定时输出:

took 19.173 ms

所以速度要慢8倍以上。

我不是SQL方面的专家,但我本以为查询计划者会发现“限制1”和“按主索引排序”等同于O(1)操作。

问题:

有没有更有效的方法来选择表格的最后一行?

如果有用,这是我的桌子的描述:

# \d+ prices

                                           Table "public.prices"
 Column |            Type             | Collation | Nullable | Default | Storage | Stats target | Description 
--------+-----------------------------+-----------+----------+---------+---------+--------------+-------------
 time   | timestamp without time zone |           | not null |         | plain   |              | 
 AAPL   | double precision            |           |          |         | plain   |              | 
 GOOG   | double precision            |           |          |         | plain   |              | 
 MSFT   | double precision            |           |          |         | plain   |              | 
Indexes:
    "prices_time_idx" btree ("time" DESC)
Child tables: _timescaledb_internal._hyper_12_100_chunk,
              _timescaledb_internal._hyper_12_101_chunk,
              _timescaledb_internal._hyper_12_102_chunk,
              ...

3 个答案:

答案 0 :(得分:2)

您的第一个查询可以排除除最后一个块以外的所有块,而您的第二个查询必须查找每个块,因为没有信息可帮助计划者排除块。因此,它不是O(1)运算,而是O(n)运算,其中n是该超表的块数。

您可以通过以下形式编写查询来将信息提供给计划者:

select * from prices WHERE time > now() - interval '1day' order by time desc limit 1

根据块时间间隔,您可能不得不选择其他间隔。

从TimescaleDB 1.2开始,如果可以在最近的块中找到条目,并且如果按时间排序并具有LIMIT,则不再需要WHERE子句中的明确时间约束,则此操作为O(1)。 / p>

答案 1 :(得分:0)

在TimescaleDB中获取最后/第一条记录的有效方法:

第一条记录:

SELECT <COLUMN>, time FROM <TABLE_NAME> ORDER BY time ASC LIMIT 1 ;

最近记录:

SELECT <COLUMN>, time FROM <TABLE_NAME> ORDER BY time DESC LIMIT 1 ;

问题已经回答,但我相信如果人们到这里来可能会很有用。 在TimescaleDB中使用first()和last()需要更长的时间。

答案 2 :(得分:0)

我尝试通过多种方式解决这个问题:使用last(),尝试创建索引以更快地获取最后一项。最后,我刚刚创建了另一个表,在其中存储插入到超级表中的第一个和最后一个项目,由 WHERE 条件键控,在我的情况下是一个关系。

  • 数据库编写器在向超表插入条目时也会更新此表

  • 我通过简单的 BTree 查找获得第一个和最后一个项目 - 根本不需要去超表

这是我的 SQLAlchemy 代码:

class PairState(Base):
    """Cache the timespan endpoints for intervals we are generating with hypertable.

    Getting the first / last row (timestamp) from hypertable is very expensive:
    https://stackoverflow.com/questions/51575004/timescaledb-efficiently-select-last-row

    Here data is denormalised per trading pair, and being updated when data is written to the database.
    Save some resources by not using true NULL values.
    """

    __tablename__ = "pair_state"

    # This table has 1-to-1 relationship with Pair
    pair_id = sa.Column(sa.ForeignKey("pair.id"), nullable=False, primary_key=True, unique=True)
    pair = orm.relationship(Pair,
                        backref=orm.backref("pair_state",
                                        lazy="dynamic",
                                        cascade="all, delete-orphan",
                                        single_parent=True, ), )

    # First raw event in data stream
    first_event_at = sa.Column(sa.TIMESTAMP(timezone=True), nullable=False, server_default=text("TO_TIMESTAMP(0)"))

    # Last raw event in data stream
    last_event_at = sa.Column(sa.TIMESTAMP(timezone=True), nullable=False, server_default=text("TO_TIMESTAMP(0)"))

    # The last hypertable entry added
    last_interval_at = sa.Column(sa.TIMESTAMP(timezone=True), nullable=False, server_default=text("TO_TIMESTAMP(0)"))

    @staticmethod
    def create_first_event_if_not_exist(dbsession: Session, pair_id: int, ts: datetime.datetime):
        """Sets the first event value if not exist yet."""
        dbsession.execute(
            insert(PairState).
            values(pair_id=pair_id, first_event_at=ts).
            on_conflict_do_nothing()
        )

    @staticmethod
    def update_last_event(dbsession: Session, pair_id: int, ts: datetime.datetime):
        """Replaces the the column last_event_at for a named pair."""
        # Based on the original example of https://stackoverflow.com/a/49917004/315168
        dbsession.execute(
            insert(PairState).
            values(pair_id=pair_id, last_event_at=ts).
            on_conflict_do_update(constraint=PairState.__table__.primary_key, set_={"last_event_at": ts})
        )

    @staticmethod
    def update_last_interval(dbsession: Session, pair_id: int, ts: datetime.datetime):
        """Replaces the the column last_interval_at for a named pair."""
        dbsession.execute(
            insert(PairState).
            values(pair_id=pair_id, last_interval_at=ts).
            on_conflict_do_update(constraint=PairState.__table__.primary_key, set_={"last_interval_at": ts})
        )