简体   繁体   中英

Replicating SAS DO loop in Oracle PL/SQL

I'm trying to replicate a DO loop from SAS into Oracle PL/SQL. Basically, this DO loop iterates over the table and creates multiple rows for a single employee. I'm not extremely familiar with loops in PL/SQL so any help is appreciated. I can't think of any way to recreate this except for creating numerous tables then combining them. I'll explain more on my thoughts at the end; for now, please see an example of the data and what the SAS DO loop is doing.

HIST_EMPLOYEE Table:

+----------+----------+--------+
| EMPLOYEE | START_YR | END_YR |
+----------+----------+--------+
| JOHN     |     2013 |   2014 |
| WILL     |     2012 |   2016 |
| MARK     |     2012 |   2012 |
+----------+----------+--------+

DO loop in SAS:

DATA HIST_EMPLOYEE_NEW;
SET HIST_EMPLOYEE;
    DO YR = START_YR TO END_YR;
OUTPUT;
END;
RUN;

Output:

+----------+----------+--------+------+
| EMPLOYEE | START_YR | END_YR |  YR  |
+----------+----------+--------+------+
| JOHN     |     2013 |   2014 | 2013 |
| JOHN     |     2013 |   2014 | 2014 |
| WILL     |     2012 |   2016 | 2012 |
| WILL     |     2012 |   2016 | 2013 |
| WILL     |     2012 |   2016 | 2014 |
| WILL     |     2012 |   2016 | 2015 |
| WILL     |     2012 |   2016 | 2016 |
| MARK     |     2012 |   2012 | 2012 |
+----------+----------+--------+------+

The way I'd solve this (which is not efficient in any way) is to create tables filtered on END_YR < START_YR + i where i is from 0 to 10 , then create the YR column then combine all tables. I can discuss this further, but I already feel like this is the bad way of doing things.

Just make a table with one value of YR ranging from some minimum to maximum and join on that.

So something like:

with years as (
        select 2012 + rownum - 1 as YR
        from dual
        connect by rownum < (2016 - 2012)
    )
select a.*,b.YR
from HIST_EMPLOYEE a
  inner join years b
  on a.start_yr <= b.yr and b.yr <= a.end_yr
;

Just change the lower (2012) and upper (2016) limits to change the number of years you want to generate.

See this question: How to populate calendar table in Oracle?

Here's one way. The "with" clause is called a Common Table Expression (CTE) and just sets up the test data with a unique id for each entry.

The query uses a CONNECT BY which can be thought of as a looping mechanism for each row returned. It comes along with a variable called "level" which is incremented once for each iteration (it starts at 1). To define how many times to "loop" for each row is the expression (end_yr-start_yr+1). For JOHN we'll need to loop 2 times as we need 2 rows, WILL 5 rows, etc. The "PRIOR ID" clauses help to handle the multiple rows for each original row.

with hist_employee(id, employee, start_yr, end_yr) as (
  select 1, 'JOHN', 2013, 2014 from dual union all
  select 2, 'WILL', 2012, 2016 from dual union all
  select 3, 'MARK', 2012, 2012 from dual
)
select employee, start_yr, end_yr, (start_yr + (level-1)) as YR
from hist_employee
connect by level <= end_yr-start_yr+1
  and prior id = id
  and prior sys_guid() is not null
order by id;


EMPLOYEE   START_YR     END_YR         YR
-------- ---------- ---------- ----------
JOHN           2013       2014       2013
JOHN           2013       2014       2014
WILL           2012       2016       2012
WILL           2012       2016       2013
WILL           2012       2016       2014
WILL           2012       2016       2015
WILL           2012       2016       2016
MARK           2012       2012       2012

8 rows selected.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM