Sometimes things just make more sense in SQL... Sometimes they don't. Here is a working implementation of the SHA2 algorithm written in T-SQL.

raw

-- If you are here looking for code to actually use in production, here you go, no need to keep reading:

SELECT HASHBYTES('sha2_256', 'The quick brown fox jumps over the lazy dog')

To get things started, SQL doesn't support binary shifts by default. So here are some helper functions to do bit shifting. SHA256 requires a right shift, and a right rotation. (Right rotation in turn requires a left shift). Shift are implemented by doing power of 2 multiplication/division. Every time you multiply by two, you do a single left shift. Every time you divide by two, you do a single right shift.

raw

-- 32 bit unsigned left shift.
CREATE FUNCTION SHL32
(
    @value  BIGINT,
    @amount BIGINT
)
RETURNS BIGINT
AS BEGIN
    RETURN (@value * POWER(CAST(2 AS BIGINT), @amount)) & 0xFFFFFFFF
END
GO

-- 32 bit unsigned right shift.
CREATE FUNCTION SHR32
(
    @value  BIGINT,
    @amount BIGINT
)
RETURNS BIGINT
AS BEGIN
    RETURN (@value / POWER(CAST(2 AS BIGINT), @amount)) & 0xFFFFFFFF
END
GO

-- 32 bit unsigned right rotation
CREATE FUNCTION ROTR32
(
    @value  BIGINT,
    @amount BIGINT
)
RETURNS BIGINT
AS BEGIN
    RETURN (dbo.SHR32(@value, @amount) | dbo.SHL32(@value, 32 - @amount)) & 0xFFFFFFFF
END
GO

Now that we have binary shift helpers, here is a working solution. The initial goal was to create the sproc without using any loops. It turned out to be incredibly difficult, so I did use a single loop in order to calculate the message schedule array.

raw

--
-- SHA256 implementation
--
CREATE PROCEDURE SHA256
(
    @input VARBINARY(MAX)
)
AS BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

    DECLARE @kvalues TABLE
    (
        id INT,
        val BIGINT
    )

    -- Initialize all of the K values
    INSERT INTO @kvalues
    VALUES 
     (0,  0x428a2f98),(1,  0x71374491),(2,  0xb5c0fbcf),(3,  0xe9b5dba5)
    ,(4,  0x3956c25b),(5,  0x59f111f1),(6,  0x923f82a4),(7,  0xab1c5ed5)
    ,(8,  0xd807aa98),(9,  0x12835b01),(10, 0x243185be),(11,0x550c7dc3)
    ,(12, 0x72be5d74),(13, 0x80deb1fe),(14, 0x9bdc06a7),(15, 0xc19bf174)
    ,(16, 0xe49b69c1),(17, 0xefbe4786),(18, 0x0fc19dc6),(19, 0x240ca1cc)
    ,(20, 0x2de92c6f),(21, 0x4a7484aa),(22, 0x5cb0a9dc),(23, 0x76f988da)
    ,(24, 0x983e5152),(25, 0xa831c66d),(26, 0xb00327c8),(27, 0xbf597fc7)
    ,(28, 0xc6e00bf3),(29, 0xd5a79147),(30, 0x06ca6351),(31, 0x14292967)
    ,(32, 0x27b70a85),(33, 0x2e1b2138),(34, 0x4d2c6dfc),(35, 0x53380d13)
    ,(36, 0x650a7354),(37, 0x766a0abb),(38, 0x81c2c92e),(39, 0x92722c85)
    ,(40, 0xa2bfe8a1),(41, 0xa81a664b),(42, 0xc24b8b70),(43, 0xc76c51a3)
    ,(44, 0xd192e819),(45, 0xd6990624),(46, 0xf40e3585),(47, 0x106aa070)
    ,(48, 0x19a4c116),(49, 0x1e376c08),(50, 0x2748774c),(51, 0x34b0bcb5)
    ,(52, 0x391c0cb3),(53, 0x4ed8aa4a),(54, 0x5b9cca4f),(55, 0x682e6ff3)
    ,(56, 0x748f82ee),(57, 0x78a5636f),(58, 0x84c87814),(59, 0x8cc70208)
    ,(60, 0x90befffa),(61, 0xa4506ceb),(62, 0xbef9a3f7),(63, 0xc67178f2)

    DECLARE @hvalues TABLE
    (
        a BIGINT
       ,b BIGINT
       ,c BIGINT
       ,d BIGINT
       ,e BIGINT
       ,f BIGINT
       ,g BIGINT
       ,h BIGINT
    )

    -- Initialize the initial H values
    INSERT INTO @hvalues
    VALUES
    (0x6a09e667
    ,0xbb67ae85
    ,0x3c6ef372
    ,0xa54ff53a
    ,0x510e527f
    ,0x9b05688c
    ,0x1f83d9ab
    ,0x5be0cd19)

    DECLARE @loop INT = 0

    -- Length of the input binary in bytes
    DECLARE @inputLength BIGINT = LEN(@input)

    -- Total length of the binary, including the 1 bit to append to the data, and the 8 byte length of the data
    DECLARE @totalLength BIGINT = @inputLength + 1 + 8

    -- Length that needs to padded with zeros
    DECLARE @padLength BIGINT = 64 - (@totalLength % 64)

    -- If the result is evenly divisible, than we don't need to pad
    SET @padLength = CASE WHEN @padLength = 64 THEN 0 ELSE @padLength END

    -- Declare the array of zeros we need to append, and pad with the right number of zeros.
    DECLARE @zeros VARBINARY(55) = CONVERT(VARBINARY(MAX), REPLICATE(CHAR(0), @padLength))

    -- Create the full length padded binary
    DECLARE @binary VARBINARY(MAX) = @input + 0x80 + @zeros + CONVERT(VARBINARY(8), (@inputLength * 8))

    DECLARE @messageSchedules TABLE
    (
        chunk INT
       ,id    INT
       ,val   BIGINT
    )

    -- Split the data into 4 byte words
    -- Copy 64 bytes (16 words) into each 64 word chunk
    -- The other 48 bits are all zero, they need to be calculated later.
    --
    -- Result:
    -- chunk | id | value
    ----------------------
    --   0   |  0 | 4 BYTES FROM index (0)
    --   0   |  1 | 4 BYTES FROM index (4)
    --          .
    --          .
    --   0   | 15 | 4 BYTES FROM index (60)
    --   0   | 16 | 0x00000000
    --          .
    --          .
    --   0   | 63 | 0x00000000
    --   1   |  0 | 4 BYTES FROM index (64)
    --          .
    --          .
    --   1   | 16 | 0x00000000
    --   1   | 17 | 0x00000000
   ;WITH splitter AS
    (
        SELECT
          0 AS id
         ,1 AS pos
         ,SUBSTRING(@binary, 1, 4) AS val
        UNION ALL
        SELECT
          s.id + 1 AS id
         ,CASE WHEN (((s.id + 1) % 64) < 16) THEN s.pos + 4 ELSE s.pos END
         ,CASE WHEN (((s.id + 1) % 64) < 16) THEN SUBSTRING(@binary, s.pos + 4, 4)
               ELSE 0x00000000
               END AS val
        FROM splitter s
        WHERE s.id + 1 < len(@binary)
    ) 
    INSERT INTO @messageSchedules
    SELECT
        s.id / 64 AS chunk
       ,s.id % 64 AS id
       ,CONVERT(BIGINT, s.val)
    FROM splitter s OPTION (MAXRECURSION 0)

    DECLARE @rows BIGINT = @@ROWCOUNT

    -- Sadly this bit I couldn't figure out how to make more SQL-y yet. 
    -- For every value, you need access to previously updated values in the table,
    -- Which doesn't work, since you cant see all of the values which you have updated.
    -- 
    -- We need to calculate the working values (everything we set to zero above, so anything
    --   with an id larger than 15)
    -- 
    -- for every id 16 <= i < 64
    --    @messageSchedules[i] = 0xFFFFFFFF 
    --              & (@messageSchedules[i-16] 
    --                  + (RIGHTROTATE(@messageSchedules[i-15], 7) ^ RIGHTROTATE(@messageSchedules[i-15], 18) ^ RIGHTSHIFT(@messageSchedules[i-15], 3))
    --                  + @messageSchedules[i-7] 
    --                  + (RIGHTROTATE(@messageSchedules[i-2], 17) ^ RIGHTROTATE(@messageSchedules[i-2], 19) ^ RIGHTSHIFT(@messageSchedules[i-2], 10)))
    SET @loop = 16
    WHILE @loop < @rows * 64
    BEGIN

        UPDATE ws
            SET ws.val = 0xFFFFFFFF 
                    & (ws16.val 
                    + (dbo.ROTR32(ws15.val, 7) 
                        ^ dbo.ROTR32(ws15.val,18) 
                        ^ dbo.SHR32(ws15.val, 3)) 
                    + ws7.val 
                    + (dbo.ROTR32(ws2.val, 17)
                        ^ dbo.ROTR32(ws2.val, 19)
                        ^ dbo.SHR32(ws2.val, 10)))
        FROM @messageSchedules ws
        JOIN @messageSchedules ws16
          ON ws.chunk = ws16.chunk AND ws.id - 16 = ws16.id
        JOIN @messageSchedules ws2
          ON ws.chunk = ws2.chunk AND ws.id - 2 = ws2.id
        JOIN @messageSchedules ws7
          ON ws.chunk = ws7.chunk AND ws.id - 7 = ws7.id
        JOIN @messageSchedules ws15
          ON ws.chunk = ws15.chunk AND ws.id - 15 = ws15.id
        WHERE ws.id >= 16 AND ws.id = @loop

        SET @loop = @loop + 1
    END

    -- This is the meat of it. 
    -- We start with row -1, which is made up of the h values. (ai,bi...hi) store the "initial" h value for the 64 byte set.
    -- a,b,c,d,e,g,h store the working values. They are all of the temp variables
    -- ai,bi,ci...hi store the base value for each 64 byte set of temp variables. At every new set of 64 bytes, we start with ai,bi,ci...
    --  At the end of every 64 bytes (a.id + 1) % 64 = 0, we store the new set of base values so we can add them back in at the end. (a.id + 1) % 64 = 63
   ;WITH abcdefgh AS
    (
        SELECT -1 AS id, a, b, c, d, e, f, g, h, a AS ai, b AS bi, c AS ci, d AS di, e AS ei, f AS fi, g AS gi, h AS hi
        FROM @hvalues

        UNION ALL

        SELECT
            a.id + 1 AS id,
            ((dbo.ROTR32(a.e, 6) ^ dbo.ROTR32(a.e, 11) ^ dbo.ROTR32(a.e, 25))      -- Sigma 1
                + ((a.e & a.f) ^ ((~a.e) & a.g))                                   -- Ch (choice function)
                + a.h                                                              
                + (SELECT val FROM @kvalues WHERE id = ((a.id + 1) % 64))          
                + bc.val                                                           
                + ((a.a & a.b) ^ (a.a & a.c) ^ (a.b & a.c))                        -- Maj (Majority Function)
                + (dbo.ROTR32(a.a, 2) ^ dbo.ROTR32(a.a, 13) ^ dbo.ROTR32(a.a, 22)) -- Sigma 0
                + CASE WHEN (a.id + 1) % 64 = 63 THEN a.ai ELSE 0 END)
                & 0xFFFFFFFF AS a,
            (a.a + CASE WHEN (a.id + 1) % 64 = 63 THEN a.bi ELSE 0 END) & 0xFFFFFFFF AS b,
            (a.b + CASE WHEN (a.id + 1) % 64 = 63 THEN a.ci ELSE 0 END) & 0xFFFFFFFF AS c,
            (a.c + CASE WHEN (a.id + 1) % 64 = 63 THEN a.di ELSE 0 END) & 0xFFFFFFFF AS d,
            (a.d + (dbo.ROTR32(a.e, 6) ^ dbo.ROTR32(a.e, 11) ^ dbo.ROTR32(a.e, 25)) -- Sigma 1
                + ((a.e & a.f) ^ ((~a.e) & a.g))                                    -- Ch (choice function)
                + a.h 
                + (SELECT val FROM @kvalues WHERE id = ((a.id + 1) % 64))
                + bc.val
                + CASE WHEN (a.id + 1) % 64 = 63 THEN a.ei ELSE 0 END)
                & 0xFFFFFFFF AS e,
            (a.e + CASE WHEN (a.id + 1) % 64 = 63 THEN a.fi ELSE 0 END) & 0xFFFFFFFF AS f,
            (a.f + CASE WHEN (a.id + 1) % 64 = 63 THEN a.gi ELSE 0 END) & 0xFFFFFFFF AS g,
            (a.g + CASE WHEN (a.id + 1) % 64 = 63 THEN a.hi ELSE 0 END) & 0xFFFFFFFF AS h
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.a ELSE a.ai END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.b ELSE a.bi END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.c ELSE a.ci END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.d ELSE a.di END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.e ELSE a.ei END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.f ELSE a.fi END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.g ELSE a.gi END
            ,CASE WHEN (a.id + 1) % 64 = 0 THEN a.h ELSE a.hi END
        FROM abcdefgh a
        JOIN @messageSchedules bc ON bc.id = ((a.id + 1) % 64) AND bc.chunk = ((a.id + 1) / 64)
        CROSS JOIN @hvalues hv
    )
    SELECT TOP 1
            CONVERT(VARBINARY(4), a)
           + CONVERT(VARBINARY(4), b)
           + CONVERT(VARBINARY(4), c)
           + CONVERT(VARBINARY(4), d)
           + CONVERT(VARBINARY(4), e)
           + CONVERT(VARBINARY(4), f)
           + CONVERT(VARBINARY(4), g)
           + CONVERT(VARBINARY(4), h)
    FROM abcdefgh 
    ORDER BY id DESC OPTION (MAXRECURSION 0)

  END
GO

DECLARE @input VARBINARY(MAX) = CONVERT(VARBINARY(MAX), 'The quick brown fox jumps over the lazy dog')
EXECUTE SHA256 @input = @input -- Should be: 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592'

SET @input = CONVERT(VARBINARY(MAX), '')
EXECUTE SHA256 @input = @input -- Should be: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

And there you have it! SHA2 implemented in SQL. Most of the algorithm is implemented in the giant CTE (abcdefgh) at the bottom of the file. If you do it completely procedural-y (while loops), you can just follow the regular Wikipedia (https://en.wikipedia.org/wiki/SHA-2) example and get a pretty clear solution.

raw

--
-- Procedural SHA256 implementation
--
CREATE PROCEDURE SHA256
(
    @input VARBINARY(MAX)
)
AS BEGIN
    SET NOCOUNT ON

    DECLARE @kvalues TABLE
    (
        id INT,
        val BIGINT
    )

    INSERT INTO @kvalues
    VALUES 
     (0,  0x428a2f98),(1,  0x71374491),(2,  0xb5c0fbcf),(3,  0xe9b5dba5)
    ,(4,  0x3956c25b),(5,  0x59f111f1),(6,  0x923f82a4),(7,  0xab1c5ed5)
    ,(8,  0xd807aa98),(9,  0x12835b01),(10, 0x243185be),(11,0x550c7dc3)
    ,(12, 0x72be5d74),(13, 0x80deb1fe),(14, 0x9bdc06a7),(15, 0xc19bf174)
    ,(16, 0xe49b69c1),(17, 0xefbe4786),(18, 0x0fc19dc6),(19, 0x240ca1cc)
    ,(20, 0x2de92c6f),(21, 0x4a7484aa),(22, 0x5cb0a9dc),(23, 0x76f988da)
    ,(24, 0x983e5152),(25, 0xa831c66d),(26, 0xb00327c8),(27, 0xbf597fc7)
    ,(28, 0xc6e00bf3),(29, 0xd5a79147),(30, 0x06ca6351),(31, 0x14292967)
    ,(32, 0x27b70a85),(33, 0x2e1b2138),(34, 0x4d2c6dfc),(35, 0x53380d13)
    ,(36, 0x650a7354),(37, 0x766a0abb),(38, 0x81c2c92e),(39, 0x92722c85)
    ,(40, 0xa2bfe8a1),(41, 0xa81a664b),(42, 0xc24b8b70),(43, 0xc76c51a3)
    ,(44, 0xd192e819),(45, 0xd6990624),(46, 0xf40e3585),(47, 0x106aa070)
    ,(48, 0x19a4c116),(49, 0x1e376c08),(50, 0x2748774c),(51, 0x34b0bcb5)
    ,(52, 0x391c0cb3),(53, 0x4ed8aa4a),(54, 0x5b9cca4f),(55, 0x682e6ff3)
    ,(56, 0x748f82ee),(57, 0x78a5636f),(58, 0x84c87814),(59, 0x8cc70208)
    ,(60, 0x90befffa),(61, 0xa4506ceb),(62, 0xbef9a3f7),(63, 0xc67178f2)

    DECLARE @hvalues TABLE
    (
        id INT,
        val BIGINT
    )

    INSERT INTO @hvalues
    VALUES
     (0, 0x6a09e667)
    ,(1, 0xbb67ae85)
    ,(2, 0x3c6ef372)
    ,(3, 0xa54ff53a)
    ,(4, 0x510e527f)
    ,(5, 0x9b05688c)
    ,(6, 0x1f83d9ab)
    ,(7, 0x5be0cd19)

    -- Initialize temp variables
    DECLARE @a BIGINT
    DECLARE @b BIGINT
    DECLARE @c BIGINT
    DECLARE @d BIGINT
    DECLARE @e BIGINT
    DECLARE @f BIGINT
    DECLARE @g BIGINT
    DECLARE @h BIGINT
    DECLARE @s1 BIGINT
    DECLARE @s0 BIGINT
    DECLARE @ch BIGINT
    DECLARE @maj BIGINT
    DECLARE @temp1 BIGINT
    DECLARE @temp2 BIGINT
    DECLARE @loop INT

    -- Length of the input binary in bytes
    DECLARE @inputLength BIGINT = LEN(@input)

    -- Total length of the binary, including the 1 bit to append to the data, and the 8 byte length of the data
    DECLARE @totalLength BIGINT = @inputLength + 1 + 8

    -- Length that needs to padded with zeros
    DECLARE @padLength BIGINT = 64 - (@totalLength % 64)

    -- If the result is evenly divisible, than we don't need to pad
    SET @padLength = CASE WHEN @padLength = 64 THEN 0 ELSE @padLength END

    -- Declare the array of zeros we need to append, and pad with the right number of zeros.
    DECLARE @zeros VARBINARY(55) = CONVERT(VARBINARY(MAX), REPLICATE(CHAR(0), @padLength))

    -- Create the full length padded binary
    DECLARE @binary VARBINARY(MAX) = @input + 0x80 + @zeros + CONVERT(VARBINARY(8), (@inputLength * 8))

    DECLARE @allDataLoop INT = 0
    DECLARE @binLength INT = DATALENGTH(@binary)

    DECLARE @messageSchedule TABLE
    (
        id INT,
        val BIGINT
    )

    WHILE (@allDataLoop <> @binLength)
    BEGIN
        DELETE FROM @messageSchedule

        -- Get all of the working bytes. 
       ;WITH cte AS
        (
            SELECT
                0 AS id
               ,SUBSTRING(@binary, @allDataLoop + 0 + 1, 4) AS val

            UNION ALL
        
            SELECT
                c.id + 1 AS id
               ,SUBSTRING(@binary, @allDataLoop + ((c.id + 1) * 4) + 1, 4) AS val
            FROM cte AS c
            WHERE c.id < 15
        )
        INSERT INTO @messageSchedule
        SELECT
          id,
          val
        FROM cte

        -- Calculate the values with indicies larger than 16 in the set of working bytes
        SET @loop = 16
        WHILE (@loop < 64)
        BEGIN
            INSERT INTO @messageSchedule
            SELECT
                @loop AS id
               ,0xFFFFFFFF & (ws16.val + (dbo.ROTR32(ws15.val, 7) ^ dbo.ROTR32(ws15.val,18) ^ dbo.SHR32(ws15.val, 3)) + ws7.val + (dbo.ROTR32(ws2.val, 17) ^ dbo.ROTR32(ws2.val, 19) ^ dbo.SHR32(ws2.val, 10))) AS val
            FROM @messageSchedule ws15
            JOIN @messageSchedule ws2
              ON ws2.id = @loop-2
            JOIN @messageSchedule ws16
              ON ws16.id = @loop-16
            JOIN @messageSchedule ws7
              ON ws7.id = @loop-7
            WHERE ws15.id = @loop-15

            SET @loop = @loop + 1
        END

        -- Initialize the temp variables
        SELECT @a = val FROM @hvalues WHERE id = 0
        SELECT @b = val FROM @hvalues WHERE id = 1
        SELECT @c = val FROM @hvalues WHERE id = 2
        SELECT @d = val FROM @hvalues WHERE id = 3
        SELECT @e = val FROM @hvalues WHERE id = 4
        SELECT @f = val FROM @hvalues WHERE id = 5
        SELECT @g = val FROM @hvalues WHERE id = 6
        SELECT @h = val FROM @hvalues WHERE id = 7

        SET @loop = 0
        WHILE (@loop < 64)
        BEGIN
            SET @s1 = (dbo.ROTR32(@e, 6) ^ dbo.ROTR32(@e, 11) ^ dbo.ROTR32(@e, 25)) & 0xFFFFFFFF -- Sigma 1
            SET @ch = ((@e & @f) ^ ((~@e) & @g)) & 0xFFFFFFFF                                    -- Ch (choice function)
            SET @temp1 = (@h + @s1 + @ch + (SELECT val FROM @kvalues WHERE id = @loop) + (SELECT val FROM @messageSchedule WHERE id = @loop)) & 0xFFFFFFFF

            SET @s0 = (dbo.ROTR32(@a, 2) ^ dbo.ROTR32(@a, 13) ^ dbo.ROTR32(@a, 22)) & 0xFFFFFFFF -- Sigma 0
            SET @maj = (@a & @b) ^ (@a & @c) ^ (@b & @c)                                         -- Maj (Majority Function)
            SET @temp2 = (@s0 + @maj) & 0xFFFFFFFF

            SET @h = @g
            SET @g = @f
            SET @f = @e
            SET @e = (@d + @temp1) & 0xFFFFFFFF
            SET @d = @c
            SET @c = @b
            SET @b = @a
            SET @a = (@temp1 + @temp2) & 0xFFFFFFFF

            SET @loop = @loop + 1
        END

        -- Update the base values
        UPDATE @hvalues SET val = (val + @a) & 0xFFFFFFFF WHERE id = 0 
        UPDATE @hvalues SET val = (val + @b) & 0xFFFFFFFF WHERE id = 1
        UPDATE @hvalues SET val = (val + @c) & 0xFFFFFFFF WHERE id = 2
        UPDATE @hvalues SET val = (val + @d) & 0xFFFFFFFF WHERE id = 3
        UPDATE @hvalues SET val = (val + @e) & 0xFFFFFFFF WHERE id = 4
        UPDATE @hvalues SET val = (val + @f) & 0xFFFFFFFF WHERE id = 5
        UPDATE @hvalues SET val = (val + @g) & 0xFFFFFFFF WHERE id = 6
        UPDATE @hvalues SET val = (val + @h) & 0xFFFFFFFF WHERE id = 7

        SET @allDataLoop = @allDataLoop + 64
    END

    DECLARE @result VARBINARY(MAX) = 0x
    SELECT @result += CONVERT(VARBINARY(4), val) FROM @hvalues
    SELECT @result
END
GO

DECLARE @input VARBINARY(MAX) = CONVERT(VARBINARY(MAX), 'The quick brown fox jumps over the lazy dog')
EXECUTE SHA256 @input = @input -- Should be: 'd7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592'

SET @input = CONVERT(VARBINARY(MAX), '')
EXECUTE SHA256 @input = @input -- Should be: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'

Clearly not as cool, but the procedural solution is pretty much textbook and much easier to follow.