Find all items where the 2 latest statuses meet condition












2















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.










share|improve this question




















  • 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


















2















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.










share|improve this question




















  • 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
















2












2








2








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.










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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
















  • 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










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












3 Answers
3






active

oldest

votes


















1














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.






share|improve this answer



















  • 2





    I don't see how does this check that the latest status is 'pending'.

    – ypercubeᵀᴹ
    Dec 24 '18 at 5:26





















2














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






share|improve this answer
























  • 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



















1














Assuming ..




  • an item table with exactly 1 row per item of interest (as indicated by the column name item_fk (= "Item-FK").

  • that "last" is defined by the column ts(= timestamp) - which is NOT NULL. (Might alternatively be id.)

  • that (item_fk, ts) is defined UNIQUE (else you need more ORDER BY expressions 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?






share|improve this answer
























    Your Answer








    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
    });


    }
    });














    draft saved

    draft discarded


















    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









    1














    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.






    share|improve this answer



















    • 2





      I don't see how does this check that the latest status is 'pending'.

      – ypercubeᵀᴹ
      Dec 24 '18 at 5:26


















    1














    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.






    share|improve this answer



















    • 2





      I don't see how does this check that the latest status is 'pending'.

      – ypercubeᵀᴹ
      Dec 24 '18 at 5:26
















    1












    1








    1







    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.






    share|improve this answer













    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.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    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
















    • 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















    2














    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






    share|improve this answer
























    • 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
















    2














    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






    share|improve this answer
























    • 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














    2












    2








    2







    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






    share|improve this answer













    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







    share|improve this answer












    share|improve this answer



    share|improve this answer










    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



















    • 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











    1














    Assuming ..




    • an item table with exactly 1 row per item of interest (as indicated by the column name item_fk (= "Item-FK").

    • that "last" is defined by the column ts(= timestamp) - which is NOT NULL. (Might alternatively be id.)

    • that (item_fk, ts) is defined UNIQUE (else you need more ORDER BY expressions 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?






    share|improve this answer




























      1














      Assuming ..




      • an item table with exactly 1 row per item of interest (as indicated by the column name item_fk (= "Item-FK").

      • that "last" is defined by the column ts(= timestamp) - which is NOT NULL. (Might alternatively be id.)

      • that (item_fk, ts) is defined UNIQUE (else you need more ORDER BY expressions 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?






      share|improve this answer


























        1












        1








        1







        Assuming ..




        • an item table with exactly 1 row per item of interest (as indicated by the column name item_fk (= "Item-FK").

        • that "last" is defined by the column ts(= timestamp) - which is NOT NULL. (Might alternatively be id.)

        • that (item_fk, ts) is defined UNIQUE (else you need more ORDER BY expressions 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?






        share|improve this answer













        Assuming ..




        • an item table with exactly 1 row per item of interest (as indicated by the column name item_fk (= "Item-FK").

        • that "last" is defined by the column ts(= timestamp) - which is NOT NULL. (Might alternatively be id.)

        • that (item_fk, ts) is defined UNIQUE (else you need more ORDER BY expressions 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?







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Dec 24 '18 at 4:19









        Erwin BrandstetterErwin Brandstetter

        95.2k9185300




        95.2k9185300






























            draft saved

            draft discarded




















































            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.




            draft saved


            draft discarded














            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





















































            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







            Popular posts from this blog

            Bundesstraße 106

            Verónica Boquete

            Ida-Boy-Ed-Garten