Add a faster query for get_last_state_changes when the number of states is 1 (#90211)
* Add a faster query for get_last_state_changes when the number of states is 1 related issue #90113 * Add a faster query for get_last_state_changes when the number of states is 1 related issue #90113 * coverage * Apply suggestions from code reviewpull/90236/head
parent
8149652f9f
commit
4c45c3c63b
|
@ -406,16 +406,38 @@ def _get_last_state_changes_stmt(
|
||||||
stmt, join_attributes = _lambda_stmt_and_join_attributes(
|
stmt, join_attributes = _lambda_stmt_and_join_attributes(
|
||||||
False, include_last_changed=False
|
False, include_last_changed=False
|
||||||
)
|
)
|
||||||
stmt += lambda q: q.where(
|
if number_of_states == 1:
|
||||||
States.state_id
|
stmt += lambda q: q.join(
|
||||||
== (
|
(
|
||||||
select(States.state_id)
|
lastest_state_for_metadata_id := (
|
||||||
.filter(States.metadata_id == metadata_id)
|
select(
|
||||||
.order_by(States.last_updated_ts.desc())
|
States.metadata_id.label("max_metadata_id"),
|
||||||
.limit(number_of_states)
|
# https://github.com/sqlalchemy/sqlalchemy/issues/9189
|
||||||
.subquery()
|
# pylint: disable-next=not-callable
|
||||||
).c.state_id
|
func.max(States.last_updated_ts).label("max_last_updated"),
|
||||||
)
|
)
|
||||||
|
.filter(States.metadata_id == metadata_id)
|
||||||
|
.group_by(States.metadata_id)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
and_(
|
||||||
|
States.metadata_id == lastest_state_for_metadata_id.c.max_metadata_id,
|
||||||
|
States.last_updated_ts
|
||||||
|
== lastest_state_for_metadata_id.c.max_last_updated,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
stmt += lambda q: q.where(
|
||||||
|
States.state_id
|
||||||
|
== (
|
||||||
|
select(States.state_id)
|
||||||
|
.filter(States.metadata_id == metadata_id)
|
||||||
|
.order_by(States.last_updated_ts.desc())
|
||||||
|
.limit(number_of_states)
|
||||||
|
.subquery()
|
||||||
|
).c.state_id
|
||||||
|
)
|
||||||
if join_attributes:
|
if join_attributes:
|
||||||
stmt += lambda q: q.outerjoin(
|
stmt += lambda q: q.outerjoin(
|
||||||
StateAttributes, States.attributes_id == StateAttributes.attributes_id
|
StateAttributes, States.attributes_id == StateAttributes.attributes_id
|
||||||
|
@ -432,6 +454,10 @@ def get_last_state_changes(
|
||||||
entity_id_lower = entity_id.lower()
|
entity_id_lower = entity_id.lower()
|
||||||
entity_ids = [entity_id_lower]
|
entity_ids = [entity_id_lower]
|
||||||
|
|
||||||
|
# Calling this function with number_of_states > 1 can cause instability
|
||||||
|
# because it has to scan the table to find the last number_of_states states
|
||||||
|
# because the metadata_id_last_updated_ts index is in ascending order.
|
||||||
|
|
||||||
with session_scope(hass=hass, read_only=True) as session:
|
with session_scope(hass=hass, read_only=True) as session:
|
||||||
instance = recorder.get_instance(hass)
|
instance = recorder.get_instance(hass)
|
||||||
if not (
|
if not (
|
||||||
|
|
|
@ -382,6 +382,42 @@ def test_get_last_state_changes(hass_recorder: Callable[..., HomeAssistant]) ->
|
||||||
assert_multiple_states_equal_without_context(states, hist[entity_id])
|
assert_multiple_states_equal_without_context(states, hist[entity_id])
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_last_state_change(hass_recorder: Callable[..., HomeAssistant]) -> None:
|
||||||
|
"""Test getting the last state change for an entity."""
|
||||||
|
hass = hass_recorder()
|
||||||
|
entity_id = "sensor.test"
|
||||||
|
|
||||||
|
def set_state(state):
|
||||||
|
"""Set the state."""
|
||||||
|
hass.states.set(entity_id, state)
|
||||||
|
wait_recording_done(hass)
|
||||||
|
return hass.states.get(entity_id)
|
||||||
|
|
||||||
|
start = dt_util.utcnow() - timedelta(minutes=2)
|
||||||
|
point = start + timedelta(minutes=1)
|
||||||
|
point2 = point + timedelta(minutes=1, seconds=1)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=start
|
||||||
|
):
|
||||||
|
set_state("1")
|
||||||
|
|
||||||
|
states = []
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=point
|
||||||
|
):
|
||||||
|
set_state("2")
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.recorder.core.dt_util.utcnow", return_value=point2
|
||||||
|
):
|
||||||
|
states.append(set_state("3"))
|
||||||
|
|
||||||
|
hist = history.get_last_state_changes(hass, 1, entity_id)
|
||||||
|
|
||||||
|
assert_multiple_states_equal_without_context(states, hist[entity_id])
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_state_can_be_copied(
|
def test_ensure_state_can_be_copied(
|
||||||
hass_recorder: Callable[..., HomeAssistant]
|
hass_recorder: Callable[..., HomeAssistant]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
Loading…
Reference in New Issue