Skip to content
Adapters

Convert Sybase T-SQL to PostgreSQL PL/pgSQL with a mapping table and the six ASE behaviors that change results

9 min read Migration The Adapters team

Field mapping auto-plugged · tap a port to rewire

5 sample records ready

To convert Sybase T-SQL to PostgreSQL, run the schema and procedures through AWS SCT, DMS Schema Conversion, SQLines or Ispirer, then review six ASE behaviors by hand, because they translate into PL/pgSQL that compiles and returns a different answer. Most of the work is mechanical: @@rowcount becomes GET DIAGNOSTICS, raiserror becomes RAISE EXCEPTION, *= becomes LEFT OUTER JOIN. The six that are not mechanical are below, with the rewrite for each.

Key takeaways

  • Converters exist and work. AWS SCT reads ASE 12.5.4 to 16.0, and DMS Schema Conversion has handled ASE 16 to PostgreSQL with generative AI since November 2025.
  • pgloader is not one of them. Its documentation lists no Sybase source.
  • ASE stored empty strings as a space for years. SAP documents it, and PostgreSQL will not, so tables end up with two kinds of empty.
  • Transactions change shape. ASE commits statement by statement by default. A PL/pgSQL function commits all or nothing.
  • Procedures stop returning rows. Anything the app reads as a result set has to become a function.

How do I convert Sybase T-SQL to PostgreSQL?

Inventory first, convert second, review third. Pull the procedure, trigger and view source from syscomments, count objects by type, and run the whole set through a converter that writes an assessment. AWS SCT lists SAP ASE 12.5.4, 15.0.2, 15.5, 15.7 and 16.0 as sources and converts object, variable and parameter names to lowercase by default. DMS Schema Conversion, in the AWS console, added SAP ASE to RDS and Aurora PostgreSQL on 20 November 2025 and uses generative AI on procedures, functions and triggers. Ispirer and SQLines sell converters that do the same outside AWS.

A converter gets the syntax right. What it cannot know is what your application relied on. The mapping table below is the mechanical part, and the six behaviors after it are the part that needs a person who knows the code. If the data side of the project is still open, every tool for the schema and the load is compared on our Sybase to PostgreSQL migration tools page.

Sybase ASE to PostgreSQL T-SQL mapping table

Eighteen constructs that appear in almost every ASE code base, with the PL/pgSQL each one becomes and the note a reviewer should check.

SAP ASE PostgreSQL Check
CREATE PROCEDURE p @id int AS CREATE PROCEDURE p(p_id int) LANGUAGE plpgsql AS $$ ... $$ A FUNCTION if it returns rows
@@rowcount GET DIAGNOSTICS n = ROW_COUNT, or FOUND Read it right after the statement
@@error after each statement BEGIN ... EXCEPTION WHEN ... END Error checks become a block
@@identity INSERT ... RETURNING id Safer than lastval()
raiserror 20001 'msg' RAISE EXCEPTION 'msg' USING ERRCODE = ... Callers match on SQLSTATE now
print 'msg' RAISE NOTICE 'msg' Clients must read notices
select top 10 ... SELECT ... LIMIT 10 Add ORDER BY if order mattered
set rowcount 1000 LIMIT on SELECT, a key subquery on DELETE PostgreSQL DELETE has no LIMIT
select ... into #work CREATE TEMP TABLE work ON COMMIT DROP AS ... Lifetime differs, see below
a.id *= b.id a LEFT OUTER JOIN b ON a.id = b.id Filters move into ON
getdate() clock_timestamp() or now() now() is the transaction start
isnull(a, b) COALESCE(a, b) Same result for two arguments
convert(varchar, d, 101) to_char(d, 'MM/DD/YYYY') One format code per style number
datediff(day, a, b) b::date - a::date Counts boundaries, as ASE does
dateadd(dd, n, d) d + n * interval '1 day' make_interval() also works
charindex(s, str) strpos(str, s) Argument order flips
datalength(s) octet_length(s) Bytes, not characters
exec p @id = 5 CALL p(5), or SELECT * FROM f(5) Depends on what p returns

Six ASE behaviors that convert cleanly and return a different answer

Each of these produces valid PL/pgSQL. None raises an error in testing unless the test data happens to hit it. They are listed in the order they tend to surface after go-live.

Behavior On SAP ASE On PostgreSQL Rewrite
Blank strings '' is stored as one space in varchar '' is stored as empty Pick one meaning and apply it to old and new rows
getdate() in a loop Returns the current time on every call now() returns the transaction start every call clock_timestamp() where the time must move
Unchained mode Each statement commits on its own by default A function runs as one unit and rolls back whole Decide where the commits belong, then use a procedure
set rowcount on DELETE Caps deletes, updates and selects in the session No LIMIT on DELETE, so the batch cap vanishes DELETE ... WHERE key IN (SELECT ... LIMIT n)
#temp tables Dropped when the procedure ends Live until the session ends ON COMMIT DROP, or IF NOT EXISTS plus TRUNCATE
Result sets Any SELECT in a procedure returns rows Procedures return no rows A function with RETURNS TABLE, and new call sites

Blank strings. SAP's manual says an empty string "is interpreted as a single blank in insert or assignment statements on varchar data". Every row your ASE application wrote with '' holds a space. Move it as is and the first PostgreSQL insert stores a true empty string, so WHERE middle_name = '' finds only new rows. Decide once: trim the old blanks during the load, or keep them and make new code write a space. Then search the procedures for = '' and = ' ', because both forms will be in there.

getdate() in a loop. PostgreSQL documents that now() returns the start time of the current transaction, and a PL/pgSQL function runs inside one. A converted procedure that stamps each row with getdate() while it loops writes the same timestamp on every row. Audit trails and elapsed-time logic break first. Use clock_timestamp() wherever the value must move.

Unchained mode. ASE's default is unchained, or Transact-SQL, mode, where a statement outside an explicit transaction commits by itself. A batch procedure that fails at row 900 has already kept rows 1 to 899. The same logic as a PL/pgSQL function rolls all 900 back. Sometimes that is an improvement, and sometimes a retry job now redoes work it used to skip. Where the commits matter, write a PostgreSQL procedure and COMMIT inside it on purpose.

set rowcount on DELETE. ASE teams use set rowcount to delete in batches and keep the log small. PostgreSQL's DELETE has no LIMIT clause, so a converter that drops the setting turns a 1,000-row batch into one delete of everything. The rewrite in the figure above selects the keys with LIMIT and deletes those.

#temp tables. In ASE a temporary table created inside a procedure disappears when the procedure ends. In PostgreSQL it lives until the session ends, so the second call on a pooled connection fails with "relation already exists". Create it with ON COMMIT DROP, or with IF NOT EXISTS followed by TRUNCATE.

Result sets. Any SELECT inside an ASE procedure returns rows to the caller. A PostgreSQL procedure returns none. Procedures the application reads from have to become functions with RETURNS TABLE, and every call site changes from exec to SELECT. Count these early, because the application team owns that work, not the DBA.

What is the PostgreSQL equivalent of @@rowcount?

GET DIAGNOSTICS n = ROW_COUNT; inside PL/pgSQL, placed directly after the statement you want to measure. For a yes or no check, the built-in FOUND variable is shorter and reads the same way. AWS's own ASE migration guidance notes that PostgreSQL has no @@rowcount function, so every reference has to be rewritten, and the read has to move next to its statement.

How do I convert raiserror to PostgreSQL?

Use RAISE EXCEPTION 'message' USING ERRCODE = '...'. The message carries over, but the ASE error number does not, so any caller that branches on 20001 or 20002 must branch on a SQLSTATE instead. Pick a small set of custom codes, document them, and change the application's error handling in the same release. Informational print statements become RAISE NOTICE.

Can AWS SCT convert Sybase stored procedures?

Yes. AWS SCT converts SAP ASE procedures, functions and triggers to PL/pgSQL for PostgreSQL and Aurora PostgreSQL, and flags what it cannot convert in an assessment report. DMS Schema Conversion does the same for ASE 16 in the AWS console with generative AI. Neither knows your application, so the six behaviors above still need a reviewer.

How do I verify a Sybase to PostgreSQL conversion?

Run the same inputs through both and compare outputs, not just row counts. For each converted procedure, capture a set of real calls on ASE, replay them on PostgreSQL against the same data, and diff the returned rows and the rows changed. Compare money columns to four decimal places, since a MONEY column mapped to PostgreSQL's MONEY keeps two under a US locale. During the cutover window, point uptime monitoring at the application's health endpoints so a failing procedure shows up as an outage alert rather than a support ticket.

If ASE stays live for months while the code is rewritten, PostgreSQL has to be kept current and checked against it the whole time. That is a data sync, not a code conversion, and it is what our plans do: declare the columns once with NUMERIC(19,4) for money, then sync incrementally with per-record logs. The closest relative of this guide, for Microsoft's dialect, is converting SQL Server T-SQL to PostgreSQL, and the capture options for a long cutover are compared in change data capture tools.

Keep PostgreSQL current from Sybase ASE while the code is rewritten

Declare the columns once with MONEY kept to four places and BIT as BOOLEAN NOT NULL, run the backfill, then sync incrementally with retries, alerts and per-record logs. From $49 a month, with no per-row overage fees.

The live demo needs no card, and Starter is $49 a month.

Get started