Home > Software engineering >  Find Consecutive Columns Greater Than a Number
Find Consecutive Columns Greater Than a Number

Time:12-10

I have a table that is setup saving data for hours of the day with values attached. The table looks like so (it includes all hours, but I'm only showing a subset here):

ID Start Finish HR_8 HR_9 HR_10 HR_11 HR_12 HR_13 HR_14 HR_15
1 9 15 10 22 25 30 22 24 8 6
2 12 15 15 18 20 25 15 18 2 6
3 8 13 7 12 18 25 9 12 34 19
4 12 15 8 22 25 31 13 22 24 26

If I subtract finish from start I get a number of hours. I need to return an indicator of a 1 or a 0 based on the following rule. Return 1 if 60% of the time from Start - Finish (in 24 hour format) falls into an area with at least 3 consecutive numbers greater than 17.

Example:
ID 1 has a consecutive block in HR_9, HR_10, HR_11, HR_12, and HR_13. In this example, 9 - 15 is 100% inside of that range

ID 3 has two consecutive blocks in HR_9, HR_10, and HR_11, and also in HR_13, HR_14, and HR_15. 12 - 15 is at least 60% inside of HR_13, HR_14, and HR_15. However, it could have also fell inside of the first consecutive block.

I will be updating a separate column in this same table with the 1 or 0 derived from the rule above.

I know this table is setup strangely, but it is what I've been given to work with. Any help would be greatly appreciated.

CodePudding user response:

SELECT
  ID,
  Start,
  Finish,
  HR_8,
  HR_9,
  HR_10,
  HR_11,
  HR_12,
  HR_13,
  HR_14,
  HR_15,
  -- Calculate the number of hours between the Start and Finish times
  (Finish - Start) AS num_hours,
  -- Use a CASE expression to find blocks of numbers that are greater than 17
  -- and return 1 if at least 60% of the hours between Start and Finish fall
  -- within a block
  CASE
    -- Iterate over the values in the row, starting at HR_8
    WHEN HR_8 > 17 THEN
      -- If the value is greater than 17, increment a counter variable
      -- and check if the next value is also greater than 17
      CASE
        WHEN HR_9 > 17 THEN
          -- If the next value is also greater than 17, increment the
          -- counter variable again and continue checking the next value
          -- until we reach a value that is not greater than 17
          CASE
            ...
            -- If we reach a value that is not greater than 17,
            -- check if the counter variable is greater than or equal to 3.
            -- If it is, then we have found a block of at least 3
            -- consecutive numbers that are greater than 17, so we can
            -- return 1. Otherwise, we return 0.
            ELSE
              CASE
                WHEN counter >= 3 THEN 1
                ELSE 0
              END
          END
        ELSE 0
      END
    ELSE 0
  END AS indicator
FROM your_table;

CodePudding user response:

I believe that your first step should be to unpivot your data, either with a single hour per row or progressive groups of three values per row. While a SQL UNPIVOT provides some functionality, it is sometimes more convenient to use a CROSS APPLY, since it is more flexible.

A single-value pivot would be something like:

SELECT D.ID, D.Start, D.Finish, C.*
FROM @Data D
CROSS APPLY (
    VALUES
        ( 8, HR_8),
        ( 9, HR_9),
        (10, HR_10),
        (11, HR_11),
        (12, HR_12),
        (13, HR_13),
        (14, HR_14),
        (15, HR_15)
) C(HR, Value)
ORDER BY D.ID, C.Hr
ID Start Finish HR Value
1 9 15 8 10
1 9 15 9 22
1 9 15 10 25
1 9 15 11 30
1 9 15 12 22
1 9 15 13 24
1 9 15 14 8
1 9 15 15 6
2 12 15 8 15
2 12 15 9 18
...

Given that, you can apply your over-17 criteria and then use some window function techniques to check for consecutive ranges. See [sql-server] count consecutive for similar questions that may have the answers you need.

If you can reduce your problem down a 3-consecutive over-17 value condition with two inside the start/finish range, you can perhaps use a technique that unpivots your data into groups of three and then applies those tests.

Something like:

SELECT D.ID, D.Start, D.Finish, C.*, T.*
FROM @Data D
CROSS APPLY (
    VALUES
        (9,  HR_8,  HR_9,  HR_10),
        (10, HR_9,  HR_10, HR_11),
        (11, HR_10, HR_11, HR_12),
        (12, HR_11, HR_12, HR_13),
        (13, HR_12, HR_13, HR_14),
        (14, HR_13, HR_14, HR_15)
) C (HRMid, Value1, Value2, Value3)
CROSS APPLY (
    SELECT
        CASE WHEN C.Value1 > 17 AND C.Value2 > 17 AND C.Value3 > 17 THEN 1 ELSE 0 END AS ThreeConsecutive,
        CASE WHEN HrMid BETWEEN D.Start AND D.Finish AND D.Start <> D.Finish THEN 1 ELSE 0 END AS InRange
) T
ORDER BY D.ID, C.HrMid
ID Start Finish HRMid Value1 Value2 Value3 ThreeConsecutive InRange
1 9 15 9 10 22 25 0 1
1 9 15 10 22 25 30 1 1
1 9 15 11 25 30 22 1 1
1 9 15 12 30 22 24 1 1
1 9 15 13 22 24 8 0 1
1 9 15 14 24 8 6 0 1
2 12 15 9 15 18 20 0 0
2 12 15 10 18 20 25 1 0
...

See this db<>fiddle for examples of the above.

You would then need to take these intermediate results and feed them into additional logic to GROUP BY ID and test for any satisfying rows in order to calculate your final result.

CodePudding user response:

Not sure about the 60% requirements, so I'm following the 3 consecutive over-17 rule. Please feel free to change the code as needed.

Assume that

  • table schema is (id, start, finish, hr01, hr02..., hr24)
  • all of the columns can't be NULLs.

Here's one way to do it:

Step 1. Convert the interval (start, finish) to an integer, of which there're 24 bits. Bit 1 indicate 1st hour; bit 2, 2nd hour; ...bit 24, 24th hours.

Step 2. Convert hr01, hr02...hr24 to an integer that bit n is 1 if hr_n > 17; 0, otherwise.

Step 3. Bitwise AND the above two integer and check if there are 3 consecutive 1s. If so return 1; 0, otherwise.

Here are some examples:

ID=1:
sf24bin         11111111111111000 <- start to finish: 3 - 16
hr24bin    1010001010111111000011 <- hr01 to hr24: 1 indicate > 17; 0, otherwise. 
consec_3         1010111111000000 <- bitwise and of the above two

ID=4:
sf24bin        111100000000000000
hr24bin  100011110111110100111111
consec_3       110100000000000000

Query:

-- convert (start, finish) to an integer
drop function sf2int;
create function sf2int(
    @start int,
    @finish int)
returns int
as
begin
   declare @n int = 0;
   declare @i int = @start;
   
   while @i <= @finish
   begin
       set @n = @n   power(2,@i);
       set @i = @i   1;
   end;
   return @n;
end;

-- check if @x has @n consecutive 1s
create function consecutive_n(
    @x  int,
    @n  int)
returns int
as
begin
   declare @bitn int = dbo.sf2int(0,@n-1);   
   declare @lx   int = @x;
   
   while @lx > 0 
   begin
      if (@lx & @bitn) = @bitn
         return 1;      -- there are n consecutive 1s 
      else
         set @lx = @lx / 2;  -- shift 1 bit to the right
   end;
   return 0;
end;


-- do the job
with cte as (
select *,
       dbo.sf2int(start, finish) as sf24bin,
       case when hr01 > 17 then  power(2, 0) else 0 end 
       case when hr02 > 17 then  power(2, 1) else 0 end
       case when hr03 > 17 then  power(2, 2) else 0 end
       case when hr04 > 17 then  power(2, 3) else 0 end
       case when hr05 > 17 then  power(2, 4) else 0 end
       case when hr06 > 17 then  power(2, 5) else 0 end
       case when hr07 > 17 then  power(2, 6) else 0 end
       case when hr08 > 17 then  power(2, 7) else 0 end
       case when hr09 > 17 then  power(2, 8) else 0 end
       case when hr10 > 17 then  power(2, 9) else 0 end
       case when hr11 > 17 then  power(2,10) else 0 end
       case when hr12 > 17 then  power(2,11) else 0 end
       case when hr13 > 17 then  power(2,12) else 0 end
       case when hr14 > 17 then  power(2,13) else 0 end
       case when hr15 > 17 then  power(2,14) else 0 end
       case when hr16 > 17 then  power(2,15) else 0 end
       case when hr17 > 17 then  power(2,16) else 0 end
       case when hr18 > 17 then  power(2,17) else 0 end
       case when hr19 > 17 then  power(2,18) else 0 end
       case when hr20 > 17 then  power(2,19) else 0 end
       case when hr21 > 17 then  power(2,20) else 0 end
       case when hr22 > 17 then  power(2,21) else 0 end
       case when hr23 > 17 then  power(2,22) else 0 end
       case when hr24 > 17 then  power(2,23) else 0 end as hr24bin
 from rs)
select *,
       sf24bin & hr24bin as hr_bin,
       dbo.consecutive_n(sf24bin & hr24bin,3) as consecutive_3_hrs
  from cte;

Some sample output:

id |start|finish|hr01|hr02|hr03|hr04|hr05|hr06|hr07|hr08|hr09|hr10|hr11|hr12|hr13|hr14|hr15|hr16|hr17|hr18|hr19|hr20|hr21|hr22|hr23|hr24|sf24bin |hr24bin |hr_bin  |consecutive_3_hrs|
--- ----- ------ ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -------- -------- -------- ----------------- 
  1|    3|    16|  26|  38|  11|  17|   4|   5|  26|  25|  34|  33|  44|  45|  15|  34|  15|  34|   9|  15|  12|  22|  14|  41|  13|  14|  131064| 2666435|   44992|                1|
  2|   13|    20|  40|  11|  39|  22|   1|   3|   3|  37|  10|   1|  15|   3|  24|   6|  45|  24|  43|  37|  44|   7|   4|  47|  40|  13| 2088960| 6803597|  507904|                1|
  3|    3|    20|  24|  37|   7|  35|  24|  46|  21|  16|  15|  14|  36|  11|  32|  40|  42|  28|  24|  13|  18|   5|  40|   6|  27|  40| 2097144|14021755| 1438840|                1|
  4|   14|    17|  19|  38|  34|  43|  20|  26|  12|  13|  29|  15|  30|  24|  27|  19|  32|   8|  40|  30|  37|  31|   4|   4|  15|  26|  245760| 9403711|  212992|                0|
  5|    7|    17|  44|   5|  47|  17|  10|  17|  37|  47|  26|  33|  31|  22|  44|   9|  49|  19|  11|  26|  48|  49|  36|  49|  23|  32|  262016|16703429|  188288|                1|
  6|    5|    22|   2|  15|  21|  15|  23|   8|  20|  43|  32|   7|  15|  12|   5|  13|  37|  29|  43|  49|  26|  18|  36|  27|  32|  35| 8388576|16761300| 8372672|                1|
  7|   13|    20|  28|  22|  40|  44|  32|  15|   1|  14|  14|  21|  26|  10|  10|  23|   1|   0|  27|  42|   4|  48|  21|  34|  12|   6| 2088960| 3876383| 1777664|                0|
  8|    2|    16|   5|  47|   9|  24|  33|  36|  22|  34|  29|  48|   3|  37|  25|  49|  10|  40|  48|  15|   3|  32|  38|   3|  16|  36|  131068|10075130|  113656|                1|
  9|    1|    21|  31|  44|  17|  43|  10|  34|  38|   8|  10|   3|   4|  40|  38|  40|  38|  26|  43|   3|  47|  40|  17|   4|  38|  30| 4194302|13498475|  915562|                1|
 10|   11|    23|   3|  24|  35|   1|  31|  23|  24|  43|   4|  27|  16|  36|  37|  19|  48|  16|  39|   7|  36|   8|  26|  13|  43|  12|16775168| 5602038| 5601280|                1|
  • Related