SQL Challenge/Puzzle: Given a stack trace - How to find the top element at each point in time?

后端 未结 5 1212
醉梦人生
醉梦人生 2021-01-06 04:40
  • My real life use-case was to merge nested ranges. I\'ve drew some sketches and then I saw the resemblance between ranges starting and ending to stack PUSH and POP oper
相关标签:
5条回答
  • 2021-01-06 05:02

    Although it does require an additional step -

    Generic solution for Hive, Teradata, Oracle, SQL Server and PostgreSQL

    select      s.i
    
               ,min (s.val) over
                (
                    partition by    s.depth
                                   ,s.depth_val_seq
                )                                   as top_of_stack_val            
    
    from       (select      s.i
                           ,s.val
                           ,s.depth
                            
                           ,count (s.val) over 
                            (
                                partition by    s.depth
                                order by        s.i
                                rows            between unbounded preceding and current row
                            )                                                                   as depth_val_seq
                             
                from       (select      s.i
                                       ,s.val
                                        
                                       ,sum (case s.op when 'I' then 1 else -1 end)   over
                                        (
                                            order by        s.i
                                            rows            between unbounded preceding and current row
                                        )                                                                   as depth
                 
                            from        stack_trace s
                            )
                            s
                )
                s
     
    order by    i
    ;
    
    0 讨论(0)
  • 2021-01-06 05:04

    Teradata

    select      s.i
    
               ,first_value (s.val) over 
                (
                    partition by    s.depth
                    order by        s.i
                    reset when      s.op = 'I'
                )                                as top_of_stack_val
    
    from       (select      s.i
                           ,s.val
                           ,s.op
    
                           ,sum (case s.op when 'I' then 1 else -1 end)   over
                            (
                                order by        s.i
                                rows            unbounded preceding
                            )                                                   as depth
    
                from        stack_trace s
                )
                s
                
    order by    i
    ;
    
    0 讨论(0)
  • 2021-01-06 05:08

    Personally I doubt that you will end up finding SQL that you can just use in all of SQL Server, Teradata, Postgres, and Oracle and that has acceptable performance if the table is at all large.

    A SQL Server solution (demo) would be as follows

    SELECT i,
           SUBSTRING(MAX(FORMAT(i, 'D10') + val) OVER (PARTITION BY Pos ORDER BY i 
                                                         ROWS UNBOUNDED PRECEDING), 11, 8000)
    FROM   (SELECT st.*,
                   sum(CASE WHEN op = 'I' THEN 1 ELSE -1 END) 
                      OVER (ORDER BY i ROWS UNBOUNDED PRECEDING) AS pos
            FROM   stack_trace st) t1
    ORDER  BY i; 
    
    0 讨论(0)
  • 2021-01-06 05:15

    This is a nice puzzle.

    As my main DBMS is Teradata I wrote a solution for it using Analytical functions (needs TD14.10+):

    SELECT dt.*,
       -- find the last item in the stack with the same position
       Last_Value(val IGNORE NULLS)
       Over (PARTITION BY pos
             ORDER BY i) AS top_of_stack_val
    FROM 
     ( 
       SELECT st.*,
          -- calculate the number of items in the stack
          Sum(CASE WHEN op = 'I' THEN 1 ELSE -1 end) 
          Over (ORDER BY i
                ROWS Unbounded Preceding) AS pos
       FROM stack_trace AS st
     ) AS dt;
    

    This solution works for Oracle, too, but PostgreSQL & SQL Server don't support the IGNORE NULLS option for LAST_VALUE and emulating it is quite complicated, e.g see Itzk Ben-Gan's The Last non NULL Puzzle

    Edit: In fact it's not that complex, I forgot Itzik's 2nd solution, the old piggyback trick ;-)

    Martin Smith's approach will work for all four DBMSes.

    0 讨论(0)
  • 2021-01-06 05:21

    This is actually an interesting problem. What I would do is keep track of each elements position in the stack. You can do this using a cumulative sum:

    select st.*,
           sum(case when op = 'I' then 1 else -1 end) over (order by i) as pos
    from stack_trace st;
    

    Alas, at this point, I think you need a rather complicated join or subquery to figure out the most recent value that pos refers to. Here is one method:

    with st as (
          select st.*,
                 sum(case when op = 'I' then 1 else -1 end) over (order by i) as pos
          from stack_trace st
         )
    select st.*,
           (select val
            from st st2
            where st2.i <= st.id and st2.pos = st.pos and
                  st2.val is not null
            order by i desc
            fetch first 1 row only
           ) as top_of_stack_val
    from st;
    
    0 讨论(0)
提交回复
热议问题