1

I'm trying to fill the zero values of all rows with the previously given dividend data. How do fill the zero values with the previously given dividend and also do that for all given rows in MS SQL server?

How to do this using SELECT COMMAND without using the UPDATE command?

Date      | ticker| dividends|
2019/01/24| AAPL |          0| 
2019/01/23| AAPL |          0|        
2019/01/22| AAPL |          0| 
2019/01/21| AAPL |       0.77|
2019/01/20| AAPL |          0|       
2019/01/19| AAPL |          0|       
2019/01/18| AAPL |          0|       
2019/01/17| AAPL |       0.82| 
....
....

**OUTPUT**
Date      | ticker| dividends|
2019/01/24| AAPL |       0.77| 
2019/01/23| AAPL |       0.77|        
2019/01/22| AAPL |       0.77| 
2019/01/21| AAPL |       0.77|
2019/01/20| AAPL |       0.82|       
2019/01/19| AAPL |       0.82|       
2019/01/18| AAPL |       0.82|       
2019/01/17| AAPL |       0.82| 
....
....
1
  • Why are you using an unsupported version of SQL Server? Commented Feb 29, 2020 at 16:01

2 Answers 2

3
UPDATE t1
SET dividends = (
    SELECT TOP 1 dividends 
    FROM tbl t2 
    WHERE t2.date < t1.date AND t2.dividends <> 0 
         AND t2.ticker = t1.ticker
    ORDER BY date DESC)
FROM tbl t1
WHERE dividends = 0

Here's a fiddle.

Edit: Since you want to compute the value in a select statement, here's an approach:

SELECT *
FROM tbl t1
CROSS APPLY (
    SELECT TOP 1 t2.dividends AS nonzero_dividends
    FROM tbl t2 
    WHERE t2.date <= t1.date AND t2.dividends <> 0
        AND t2.ticker = t1.ticker
    ORDER BY date DESC 
)ca
ORDER BY t1.ticker, date 

Here's another fiddle.

Edit: Here's a more performant solution that uses CTEs to build a temp table of every date in the range along with the corresponding nonzero dividend value, and then joins that result back to your table on ticker and date. I'm pretty sure it should be compatible with SQL Server 2008:

;WITH cte_not_zero_dividends AS
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY ticker ORDER BY date) seq_num
    FROM tbl
    WHERE dividends <> 0
)
, cte_all_dates_with_dividends AS 
(
    SELECT c_start.ticker, c_start.date AS date, c_start.date AS start_range, 
        ISNULL
        (
            c_end.date, 
            (SELECT MAX(DATEADD(DAY, 1, date)) FROM dbo.tbl WHERE ticker = c_start.ticker)
        ) AS end_range, 
        c_start.dividends AS nonzero_dividends
    FROM cte_not_zero_dividends c_start
    LEFT JOIN cte_not_zero_dividends c_end 
        ON c_start.seq_num = c_end.seq_num - 1 AND c_end.ticker = c_start.ticker
    UNION ALL 
    SELECT ticker,
           DATEADD(DAY, 1, date),
           start_range,
           end_range,
           nonzero_dividends 
    FROM cte_all_dates_with_dividends
    WHERE DATEADD(DAY, 1, date) < cte_all_dates_with_dividends.end_range
)
SELECT tbl.date,
       tbl.ticker,
       cte_all_dates_with_dividends.nonzero_dividends
FROM dbo.tbl
LEFT JOIN cte_all_dates_with_dividends 
    ON cte_all_dates_with_dividends.ticker = tbl.ticker 
    AND cte_all_dates_with_dividends.date = tbl.date
ORDER BY ticker, date
OPTION (MAXRECURSION 0)

Hopefully the last fiddle.

Sign up to request clarification or add additional context in comments.

2 Comments

Hi. Thanks a lot for sending your query promptly. How to do this using only the select command? I don't want to Update the table. I just want to compute this column and use it for different computations.
The query is working perfectly but it is taking a lot of time to run completely. Do you have any other way to reduce the execution time?
1

In SQL Server 2012+ (or any other supported version), you would do:

select t.*,
       max(dividends) over (partition by ticker, max_date) as imputed_dividends
from (select t.*,
             max(case when dividends <> 0 then date end) over (partition by ticker order by date) as max_date
      from t
     ) t;

This is the SQL Server work-around for not supporting the lag(ignore nulls) functionality. This is much more efficient than using apply.

If your dates are sequential with no gaps, then you can use this information and treat your data as gaps-and-islands. This allows you to:

  • Get the minimum date in each sequence
  • Join back to get the dividends from the day before

This looks like:

select t.*,
       tprev.dividends as imputed_dividends
from (select t.*,
             min(date) over (partition by ticker, dividends, seqnum - seqnum_2) as min_date
      from (select t.*,
                   row_number() over (partition by ticker order by date) as seqnum,
                   row_number() over (partition by ticker, dividends order by date) as seqnum_2
            from t
           ) t
     ) t left join
     t tprev
     on tprev.ticker = t.ticker and
        tprev.date = dateadd(day, -1, t.min_date);

Actually, this latter approach can be modified to handle data where the dates have gaps as well. That simply requires a CTE to assign a sequential number to the rows.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.