Find all items where the 2 latest statuses meet condition
I have a table in a PostgreSQL database like this:
ID|Item-FK |timestamp|status
=============================
1 | 123    | ...     | EXPIRED
2 | 123    | ...     | PENDING
...
I want to query all items where the last two statuses are 'EXPIRED' & 'PENDING' like displayed in the sample above - 'PENDING' latest and 'EXPIRED' immediately before that.
postgresql postgresql-9.4 greatest-n-per-group
add a comment |
I have a table in a PostgreSQL database like this:
ID|Item-FK |timestamp|status
=============================
1 | 123    | ...     | EXPIRED
2 | 123    | ...     | PENDING
...
I want to query all items where the last two statuses are 'EXPIRED' & 'PENDING' like displayed in the sample above - 'PENDING' latest and 'EXPIRED' immediately before that.
postgresql postgresql-9.4 greatest-n-per-group
1
Is "last" defined bytimestamporID? Is there also a tableitemswith with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?
– Erwin Brandstetter
Dec 24 '18 at 3:42
add a comment |
I have a table in a PostgreSQL database like this:
ID|Item-FK |timestamp|status
=============================
1 | 123    | ...     | EXPIRED
2 | 123    | ...     | PENDING
...
I want to query all items where the last two statuses are 'EXPIRED' & 'PENDING' like displayed in the sample above - 'PENDING' latest and 'EXPIRED' immediately before that.
postgresql postgresql-9.4 greatest-n-per-group
I have a table in a PostgreSQL database like this:
ID|Item-FK |timestamp|status
=============================
1 | 123    | ...     | EXPIRED
2 | 123    | ...     | PENDING
...
I want to query all items where the last two statuses are 'EXPIRED' & 'PENDING' like displayed in the sample above - 'PENDING' latest and 'EXPIRED' immediately before that.
postgresql postgresql-9.4 greatest-n-per-group
postgresql postgresql-9.4 greatest-n-per-group
edited Dec 24 '18 at 4:24
Erwin Brandstetter
95.2k9185300
95.2k9185300
asked Dec 22 '18 at 23:40
BarcelonaBarcelona
1133
1133
1
Is "last" defined bytimestamporID? Is there also a tableitemswith with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?
– Erwin Brandstetter
Dec 24 '18 at 3:42
add a comment |
1
Is "last" defined bytimestamporID? Is there also a tableitemswith with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?
– Erwin Brandstetter
Dec 24 '18 at 3:42
1
1
Is "last" defined by
timestamp or ID? Is there also a table items with with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?– Erwin Brandstetter
Dec 24 '18 at 3:42
Is "last" defined by
timestamp or ID? Is there also a table items with with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?– Erwin Brandstetter
Dec 24 '18 at 3:42
add a comment |
                                3 Answers
                            3
                        
active
oldest
votes
You could do it like this to avoid needing to hardcode row numbers into your query:
select * 
  from t as a 
 where status = 'pending'      -- must be pending
   and exists
  (select * 
     from t as b
    where status = 'expired'   -- must have expired
      and a.itemfk = b.itemfk
      and b.id = 
         (select max(c.id)     -- must be the previous entry before pending
            from t as c
           where c.itemfk = b.itemfk
             and c.id < a.id)  -- must have expired before pending
  );
This method performs a lookup to get the maximum id of the entry prior to it being pending and then checks that it is the row marked as expired.
db<>fiddle example.
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
add a comment |
CTE add a row number in descending order, that means that row (1) must be PENDING and row (2) must be EXPIRED.
Then you can use 2 EXISTS subqueries in the WHERE clause to ensure both conditions are true.
with ct as
(
select
    id, itemfk, dt, status,
    row_number() over (partition by itemfk order by itemfk, dt desc) rn
from
    t
order by
    itemfk, dt
)
select 
    t1.itemfk
from
    ct t1
where exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 1 and status = 'pending')
      and
      exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 2 and status = 'expired')
group by t1.itemfk;
db<>fiddle here
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
add a comment |
Assuming ..
- an 
itemtable with exactly 1 row per item of interest (as indicated by the column nameitem_fk(="Item-FK"). - that "last" is defined by the column 
ts(=timestamp) - which isNOT NULL. (Might alternatively beid.) - that 
(item_fk, ts)is definedUNIQUE(else you need moreORDER BYexpressions to make it deterministic) 
Then this should be fastest and simplest:
SELECT i.*  -- or what you need from item
FROM   item i
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT status
      FROM   tbl t
      WHERE  t.itemfk = i.item_id
      ORDER  BY ts DESC
      LIMIT  2
      ) AS last2_stat
   ) t ON t.last2_stat = '{PENDING, EXPIRED}'::text;
db<>fiddle here
You can return columns from table item directly.
It is simple to adapt to any other terminal sequence of statuses.
Have this index for best performance:
CREATE INDEX ON tbl (itemfk, id DESC, status);
status as last index expression only if you get index-only scans out of it.
Related, with more explanation:
- Optimize GROUP BY query to retrieve latest record per user
 - Select first row in each GROUP BY group?
 - What is the difference between LATERAL and a subquery in PostgreSQL?
 - Why is array_agg() slower than the non-aggregate ARRAY() constructor?
 
add a comment |
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "182"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f225650%2ffind-all-items-where-the-2-latest-statuses-meet-condition%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
                                3 Answers
                            3
                        
active
oldest
votes
                                3 Answers
                            3
                        
active
oldest
votes
active
oldest
votes
active
oldest
votes
You could do it like this to avoid needing to hardcode row numbers into your query:
select * 
  from t as a 
 where status = 'pending'      -- must be pending
   and exists
  (select * 
     from t as b
    where status = 'expired'   -- must have expired
      and a.itemfk = b.itemfk
      and b.id = 
         (select max(c.id)     -- must be the previous entry before pending
            from t as c
           where c.itemfk = b.itemfk
             and c.id < a.id)  -- must have expired before pending
  );
This method performs a lookup to get the maximum id of the entry prior to it being pending and then checks that it is the row marked as expired.
db<>fiddle example.
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
add a comment |
You could do it like this to avoid needing to hardcode row numbers into your query:
select * 
  from t as a 
 where status = 'pending'      -- must be pending
   and exists
  (select * 
     from t as b
    where status = 'expired'   -- must have expired
      and a.itemfk = b.itemfk
      and b.id = 
         (select max(c.id)     -- must be the previous entry before pending
            from t as c
           where c.itemfk = b.itemfk
             and c.id < a.id)  -- must have expired before pending
  );
This method performs a lookup to get the maximum id of the entry prior to it being pending and then checks that it is the row marked as expired.
db<>fiddle example.
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
add a comment |
You could do it like this to avoid needing to hardcode row numbers into your query:
select * 
  from t as a 
 where status = 'pending'      -- must be pending
   and exists
  (select * 
     from t as b
    where status = 'expired'   -- must have expired
      and a.itemfk = b.itemfk
      and b.id = 
         (select max(c.id)     -- must be the previous entry before pending
            from t as c
           where c.itemfk = b.itemfk
             and c.id < a.id)  -- must have expired before pending
  );
This method performs a lookup to get the maximum id of the entry prior to it being pending and then checks that it is the row marked as expired.
db<>fiddle example.
You could do it like this to avoid needing to hardcode row numbers into your query:
select * 
  from t as a 
 where status = 'pending'      -- must be pending
   and exists
  (select * 
     from t as b
    where status = 'expired'   -- must have expired
      and a.itemfk = b.itemfk
      and b.id = 
         (select max(c.id)     -- must be the previous entry before pending
            from t as c
           where c.itemfk = b.itemfk
             and c.id < a.id)  -- must have expired before pending
  );
This method performs a lookup to get the maximum id of the entry prior to it being pending and then checks that it is the row marked as expired.
db<>fiddle example.
answered Dec 23 '18 at 0:44
Mr.BrownstoneMr.Brownstone
9,75232342
9,75232342
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
add a comment |
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
2
2
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
I don't see how does this check that the latest status is 'pending'.
– ypercubeᵀᴹ
Dec 24 '18 at 5:26
add a comment |
CTE add a row number in descending order, that means that row (1) must be PENDING and row (2) must be EXPIRED.
Then you can use 2 EXISTS subqueries in the WHERE clause to ensure both conditions are true.
with ct as
(
select
    id, itemfk, dt, status,
    row_number() over (partition by itemfk order by itemfk, dt desc) rn
from
    t
order by
    itemfk, dt
)
select 
    t1.itemfk
from
    ct t1
where exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 1 and status = 'pending')
      and
      exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 2 and status = 'expired')
group by t1.itemfk;
db<>fiddle here
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
add a comment |
CTE add a row number in descending order, that means that row (1) must be PENDING and row (2) must be EXPIRED.
Then you can use 2 EXISTS subqueries in the WHERE clause to ensure both conditions are true.
with ct as
(
select
    id, itemfk, dt, status,
    row_number() over (partition by itemfk order by itemfk, dt desc) rn
from
    t
order by
    itemfk, dt
)
select 
    t1.itemfk
from
    ct t1
where exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 1 and status = 'pending')
      and
      exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 2 and status = 'expired')
group by t1.itemfk;
db<>fiddle here
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
add a comment |
CTE add a row number in descending order, that means that row (1) must be PENDING and row (2) must be EXPIRED.
Then you can use 2 EXISTS subqueries in the WHERE clause to ensure both conditions are true.
with ct as
(
select
    id, itemfk, dt, status,
    row_number() over (partition by itemfk order by itemfk, dt desc) rn
from
    t
order by
    itemfk, dt
)
select 
    t1.itemfk
from
    ct t1
where exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 1 and status = 'pending')
      and
      exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 2 and status = 'expired')
group by t1.itemfk;
db<>fiddle here
CTE add a row number in descending order, that means that row (1) must be PENDING and row (2) must be EXPIRED.
Then you can use 2 EXISTS subqueries in the WHERE clause to ensure both conditions are true.
with ct as
(
select
    id, itemfk, dt, status,
    row_number() over (partition by itemfk order by itemfk, dt desc) rn
from
    t
order by
    itemfk, dt
)
select 
    t1.itemfk
from
    ct t1
where exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 1 and status = 'pending')
      and
      exists (select 1 
              from ct 
              where itemfk = t1.itemfk 
                    and rn = 2 and status = 'expired')
group by t1.itemfk;
db<>fiddle here
answered Dec 23 '18 at 0:00
McNetsMcNets
16.3k42161
16.3k42161
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
add a comment |
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
Could u give me a generic solution? I use Hibernate in java and need a simpler query to execute
– Barcelona
Dec 23 '18 at 0:34
add a comment |
Assuming ..
- an 
itemtable with exactly 1 row per item of interest (as indicated by the column nameitem_fk(="Item-FK"). - that "last" is defined by the column 
ts(=timestamp) - which isNOT NULL. (Might alternatively beid.) - that 
(item_fk, ts)is definedUNIQUE(else you need moreORDER BYexpressions to make it deterministic) 
Then this should be fastest and simplest:
SELECT i.*  -- or what you need from item
FROM   item i
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT status
      FROM   tbl t
      WHERE  t.itemfk = i.item_id
      ORDER  BY ts DESC
      LIMIT  2
      ) AS last2_stat
   ) t ON t.last2_stat = '{PENDING, EXPIRED}'::text;
db<>fiddle here
You can return columns from table item directly.
It is simple to adapt to any other terminal sequence of statuses.
Have this index for best performance:
CREATE INDEX ON tbl (itemfk, id DESC, status);
status as last index expression only if you get index-only scans out of it.
Related, with more explanation:
- Optimize GROUP BY query to retrieve latest record per user
 - Select first row in each GROUP BY group?
 - What is the difference between LATERAL and a subquery in PostgreSQL?
 - Why is array_agg() slower than the non-aggregate ARRAY() constructor?
 
add a comment |
Assuming ..
- an 
itemtable with exactly 1 row per item of interest (as indicated by the column nameitem_fk(="Item-FK"). - that "last" is defined by the column 
ts(=timestamp) - which isNOT NULL. (Might alternatively beid.) - that 
(item_fk, ts)is definedUNIQUE(else you need moreORDER BYexpressions to make it deterministic) 
Then this should be fastest and simplest:
SELECT i.*  -- or what you need from item
FROM   item i
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT status
      FROM   tbl t
      WHERE  t.itemfk = i.item_id
      ORDER  BY ts DESC
      LIMIT  2
      ) AS last2_stat
   ) t ON t.last2_stat = '{PENDING, EXPIRED}'::text;
db<>fiddle here
You can return columns from table item directly.
It is simple to adapt to any other terminal sequence of statuses.
Have this index for best performance:
CREATE INDEX ON tbl (itemfk, id DESC, status);
status as last index expression only if you get index-only scans out of it.
Related, with more explanation:
- Optimize GROUP BY query to retrieve latest record per user
 - Select first row in each GROUP BY group?
 - What is the difference between LATERAL and a subquery in PostgreSQL?
 - Why is array_agg() slower than the non-aggregate ARRAY() constructor?
 
add a comment |
Assuming ..
- an 
itemtable with exactly 1 row per item of interest (as indicated by the column nameitem_fk(="Item-FK"). - that "last" is defined by the column 
ts(=timestamp) - which isNOT NULL. (Might alternatively beid.) - that 
(item_fk, ts)is definedUNIQUE(else you need moreORDER BYexpressions to make it deterministic) 
Then this should be fastest and simplest:
SELECT i.*  -- or what you need from item
FROM   item i
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT status
      FROM   tbl t
      WHERE  t.itemfk = i.item_id
      ORDER  BY ts DESC
      LIMIT  2
      ) AS last2_stat
   ) t ON t.last2_stat = '{PENDING, EXPIRED}'::text;
db<>fiddle here
You can return columns from table item directly.
It is simple to adapt to any other terminal sequence of statuses.
Have this index for best performance:
CREATE INDEX ON tbl (itemfk, id DESC, status);
status as last index expression only if you get index-only scans out of it.
Related, with more explanation:
- Optimize GROUP BY query to retrieve latest record per user
 - Select first row in each GROUP BY group?
 - What is the difference between LATERAL and a subquery in PostgreSQL?
 - Why is array_agg() slower than the non-aggregate ARRAY() constructor?
 
Assuming ..
- an 
itemtable with exactly 1 row per item of interest (as indicated by the column nameitem_fk(="Item-FK"). - that "last" is defined by the column 
ts(=timestamp) - which isNOT NULL. (Might alternatively beid.) - that 
(item_fk, ts)is definedUNIQUE(else you need moreORDER BYexpressions to make it deterministic) 
Then this should be fastest and simplest:
SELECT i.*  -- or what you need from item
FROM   item i
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT status
      FROM   tbl t
      WHERE  t.itemfk = i.item_id
      ORDER  BY ts DESC
      LIMIT  2
      ) AS last2_stat
   ) t ON t.last2_stat = '{PENDING, EXPIRED}'::text;
db<>fiddle here
You can return columns from table item directly.
It is simple to adapt to any other terminal sequence of statuses.
Have this index for best performance:
CREATE INDEX ON tbl (itemfk, id DESC, status);
status as last index expression only if you get index-only scans out of it.
Related, with more explanation:
- Optimize GROUP BY query to retrieve latest record per user
 - Select first row in each GROUP BY group?
 - What is the difference between LATERAL and a subquery in PostgreSQL?
 - Why is array_agg() slower than the non-aggregate ARRAY() constructor?
 
answered Dec 24 '18 at 4:19
Erwin BrandstetterErwin Brandstetter
95.2k9185300
95.2k9185300
add a comment |
add a comment |
Thanks for contributing an answer to Database Administrators Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
 
But avoid …
- Asking for help, clarification, or responding to other answers.
 - Making statements based on opinion; back them up with references or personal experience.
 
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f225650%2ffind-all-items-where-the-2-latest-statuses-meet-condition%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown

1
Is "last" defined by
timestamporID? Is there also a tableitemswith with 1 row per item of interest (like "FK" seems to suggest)? And how many rows per item on average?– Erwin Brandstetter
Dec 24 '18 at 3:42