我有一个带有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,
...
答案 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})
)