ABAP Number Ranges: Automatic Number Assignment

Category
ABAP-Statements
Published
Author
Johannes

Number ranges enable automatic, unique assignment of numbers for documents, receipts, and other business objects. They guarantee uniqueness even with parallel access.

Number Range Concepts

ConceptDescription
Number Range ObjectContainer for intervals (SNRO)
IntervalNumber range (from-to)
Internal AssignmentSystem assigns automatically
External AssignmentUser provides number

Important Transactions

TransactionDescription
SNROMaintain number range objects
SNUMMaintain number range levels
SLG1Number range log

Basic Examples

Get Next Number

DATA: lv_number TYPE char20,
lv_rc TYPE inri-returncode.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01' " Interval number
object = 'ZORDER' " Number range object
IMPORTING
number = lv_number
returncode = lv_rc
EXCEPTIONS
interval_not_found = 1
number_range_not_intern = 2
object_not_found = 3
quantity_is_0 = 4
quantity_is_not_1 = 5
interval_overflow = 6
buffer_overflow = 7
OTHERS = 8.
IF sy-subrc = 0.
WRITE: / 'New number:', lv_number.
ELSE.
MESSAGE 'Error in number assignment' TYPE 'E'.
ENDIF.

Number with Year Dependency

DATA: lv_number TYPE char20,
lv_year TYPE nriv-toyear.
lv_year = sy-datum(4).
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZINVOICE'
toyear = lv_year " Fiscal year
IMPORTING
number = lv_number
EXCEPTIONS
OTHERS = 1.
" Result e.g.: 2025-0000001
DATA(lv_doc_number) = |{ lv_year }-{ lv_number ALPHA = IN }|.

Get Multiple Numbers at Once

DATA: lv_from_number TYPE char20,
lv_to_number TYPE char20,
lv_quantity TYPE i VALUE 10.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZBATCH'
quantity = lv_quantity " 10 numbers
IMPORTING
number = lv_from_number
returncode = lv_rc
EXCEPTIONS
OTHERS = 1.
" lv_from_number = first number
" lv_from_number + quantity - 1 = last number
lv_to_number = lv_from_number + lv_quantity - 1.
WRITE: / 'Number range:', lv_from_number, 'to', lv_to_number.

Number with Subobject

" Subobject for cross-client number ranges
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZMATERIAL'
subobject = 'PLANT1' " e.g., per plant
IMPORTING
number = lv_number
EXCEPTIONS
OTHERS = 1.

Read Current Level

DATA: ls_nriv TYPE nriv.
CALL FUNCTION 'NUMBER_GET_INFO'
EXPORTING
nr_range_nr = '01'
object = 'ZORDER'
IMPORTING
interval = ls_nriv
EXCEPTIONS
OTHERS = 1.
WRITE: / 'From number:', ls_nriv-fromnumber.
WRITE: / 'To number:', ls_nriv-tonumber.
WRITE: / 'Current level:', ls_nriv-nrlevel.

Check Number Range Interval

DATA: lv_percent TYPE p DECIMALS 2.
CALL FUNCTION 'NUMBER_GET_INFO'
EXPORTING
nr_range_nr = '01'
object = 'ZORDER'
IMPORTING
interval = ls_nriv
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
" Calculate utilization
DATA(lv_total) = ls_nriv-tonumber - ls_nriv-fromnumber.
DATA(lv_used) = ls_nriv-nrlevel - ls_nriv-fromnumber.
IF lv_total > 0.
lv_percent = ( lv_used / lv_total ) * 100.
ENDIF.
WRITE: / 'Utilization:', lv_percent, '%'.
IF lv_percent > 90.
MESSAGE 'Number range almost exhausted!' TYPE 'W'.
ENDIF.
ENDIF.

Check External Number Assignment

DATA: lv_extern_number TYPE char10 VALUE '1000000001'.
" Check if number is in valid range
CALL FUNCTION 'NUMBER_CHECK'
EXPORTING
nr_range_nr = '02' " External interval
object = 'ZORDER'
number = lv_extern_number
EXCEPTIONS
interval_not_found = 1
number_outside = 2
OTHERS = 3.
CASE sy-subrc.
WHEN 0.
" Number is valid, now reserve it
CALL FUNCTION 'NUMBER_MARK_AS_USED'
EXPORTING
nr_range_nr = '02'
object = 'ZORDER'
number = lv_extern_number
EXCEPTIONS
OTHERS = 1.
WHEN 2.
MESSAGE 'Number is outside the range' TYPE 'E'.
ENDCASE.

Number Range Buffer

" Buffer for better performance (in mass processes)
CALL FUNCTION 'NUMBER_RANGE_ENQUEUE'
EXPORTING
object = 'ZORDER'
EXCEPTIONS
OTHERS = 1.
" Get numbers from buffer (faster)
DO 100 TIMES.
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZORDER'
IMPORTING
number = lv_number.
" Processing...
ENDDO.
" Write buffer back
CALL FUNCTION 'NUMBER_RANGE_DEQUEUE'
EXPORTING
object = 'ZORDER'.

Reset Number Range Level (Caution!)

" Only for test systems - NEVER in production!
DATA: ls_interval TYPE nriv.
ls_interval-nrrangenr = '01'.
ls_interval-fromnumber = '0000000001'.
ls_interval-tonumber = '9999999999'.
ls_interval-nrlevel = '0000000000'. " Reset
CALL FUNCTION 'NUMBER_RANGE_INTERVAL_UPDATE'
EXPORTING
object = 'ZORDER'
interval = ls_interval
EXCEPTIONS
OTHERS = 1.

Class CL_NUMBERRANGE_RUNTIME

" Modern API (from 7.50)
DATA: lo_nr TYPE REF TO cl_numberrange_runtime,
lv_number TYPE cl_numberrange_runtime=>nr_number.
TRY.
lo_nr = cl_numberrange_runtime=>get_instance(
iv_object = 'ZORDER' ).
lv_number = lo_nr->get_next(
iv_nrrangenr = '01' ).
WRITE: / 'Number:', lv_number.
CATCH cx_nr_object_not_found.
MESSAGE 'Number range object not found' TYPE 'E'.
CATCH cx_nr_subobject.
MESSAGE 'Subobject error' TYPE 'E'.
CATCH cx_nr_interval.
MESSAGE 'Interval error' TYPE 'E'.
ENDTRY.

Number Range in Update Task

" Reserve number only on COMMIT
CALL FUNCTION 'NUMBER_GET_NEXT'
EXPORTING
nr_range_nr = '01'
object = 'ZORDER'
IMPORTING
number = lv_number
EXCEPTIONS
OTHERS = 1.
" Save document with number
ls_order-order_id = lv_number.
INSERT zorders FROM ls_order.
" On ROLLBACK, number is not consumed
" (with buffered assignment)
COMMIT WORK.

Create Own Number Range Object (SNRO)

" Create programmatically (rarely needed)
DATA: ls_attributes TYPE nrobj.
ls_attributes-object = 'ZORDER'.
ls_attributes-domlen = 'CHAR10'.
ls_attributes-percentage = 10. " Warning at 10% remaining
ls_attributes-devclass = 'ZDEV'.
CALL FUNCTION 'NUMBER_RANGE_OBJECT_UPDATE'
EXPORTING
object = 'ZORDER'
attributes = ls_attributes
EXCEPTIONS
OTHERS = 1.

Create Interval Programmatically

DATA: ls_interval TYPE nriv.
ls_interval-nrrangenr = '01'.
ls_interval-fromnumber = '0000000001'.
ls_interval-tonumber = '9999999999'.
ls_interval-externind = ' '. " Internal
CALL FUNCTION 'NUMBER_RANGE_INTERVAL_CREATE'
EXPORTING
object = 'ZORDER'
interval = ls_interval
EXCEPTIONS
OTHERS = 1.

Number Range Transport

" Number range objects are transported
" Intervals must be maintained separately!
" In target system, create intervals:
" Transaction SNUM -> Maintain intervals
" Or via report:
REPORT znr_interval_setup.
CALL FUNCTION 'NUMBER_RANGE_INTERVAL_CREATE'
EXPORTING
object = 'ZORDER'
interval = VALUE nriv(
nrrangenr = '01'
fromnumber = '0000000001'
tonumber = '9999999999' )
EXCEPTIONS
interval_already_exist = 1
OTHERS = 2.
IF sy-subrc = 1.
" Interval already exists
ENDIF.

Number Range Types

TypeFlagDescription
Internalexternind = ’ ‘System assigns number
Externalexternind = ‘X’User provides number
Bufferedbuffer > 0Numbers are preloaded
Rollback-capableNot consumed on error

Interval Strategy

ScenarioRecommendation
Year-dependentInterval per year (01-2024, 01-2025)
Client-specificSubobject = Client
Plant-relatedSubobject = Plant
High throughputActivate buffering

Best Practices

  1. Gap-free assignment: Only when really required
  2. Buffering: Activate for mass processes
  3. Monitoring: Check utilization regularly
  4. Test data: Use separate intervals for tests
  5. Rollback: Don’t waste numbers on errors
  6. Year change: Create new intervals in time