{"id":111898,"date":"2026-08-14T12:00:00","date_gmt":"2026-08-14T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=111898"},"modified":"2026-08-03T15:54:53","modified_gmt":"2026-08-03T15:54:53","slug":"how-to-avoid-nested-replace-functions-with-translate-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/how-to-avoid-nested-replace-functions-with-translate-in-sql-server\/","title":{"rendered":"How to avoid nested REPLACE functions with TRANSLATE in SQL Server"},"content":{"rendered":"\n<p><strong>If your T-SQL is buried under layers of nested <code>REPLACE()<\/code> calls just to swap out a few characters, there&#8217;s a simpler way. SQL Server&#8217;s <code>TRANSLATE()<\/code> function \u2014 introduced in 2017 but still rarely used \u2014 lets you replace multiple characters in a single string with one function call instead of stacking several <code>REPLACE()<\/code> functions inside each other. <\/strong><\/p>\n\n\n\n<p><strong>This guide covers how <code>TRANSLATE()<\/code> works, how it compares to <code>REPLACE()<\/code>, and where it falls short compared to other SQL dialects like PostgreSQL and Oracle.<\/strong><\/p>\n\n\n\n<p id=\"h-anyone-who-has-worked-with-sql-server-and-t-sql-for-any-length-of-time-will-have-seen-code-where-replace-functions-have-been-nested-sometimes-endlessly-the-code-ends-up-hard-to-read-hard-to-understand-and-possibly-worse-easy-to-make-mistakes-with-during-maintenance-it-s-often-a-mess-but-in-many-cases-that-can-all-be-avoided\">Anyone who has worked with <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server<\/a> and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">T-SQL<\/a> will have seen code where <code><a href=\"https:\/\/www.sqlshack.com\/overview-of-the-sql-replace-function\/\" target=\"_blank\" rel=\"noreferrer noopener\">REPLACE<\/a><\/code> functions have been nested, sometimes endlessly. The code ends up hard to read, hard to understand and, worse still, easy to make mistakes with during maintenance. It&#8217;s often a mess &#8211; and in many cases, a mess that can be avoided.<\/p>\n\n\n\n<p>That&#8217;s where the <code><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/functions\/translate-transact-sql?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">TRANSLATE<\/a><\/code> function &#8211; introduced to SQL Server back in 2017 &#8211; comes in. Nearly a decade on from its introduction, I rarely see the function used in code, but it&#8217;s excellent for this use case.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-the-sql-server-translate-function\">What is the SQL Server TRANSLATE function?<\/h2>\n\n\n\n<p>Imagine I have incoming data with phone numbers in the following format:<\/p>\n\n\n\n<p><code>[02] 9232:4232<\/code><\/p>\n\n\n\n<p>In Australia, I&#8217;d like to see those phone numbers formatted like this:<\/p>\n\n\n\n<p><code>(02) 9232-4232<\/code><\/p>\n\n\n\n<p>That seems simple enough, but note the code required if I use the <code>REPLACE<\/code> function:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @PhoneNumber varchar(20) = '[02] 9232:4232';\n\nSELECT REPLACE\n       (\n           REPLACE\n           (\n               REPLACE(@PhoneNumber, '[', '('), \n               ']', ')'\n           ), \n           ':', '-'\n       );<\/pre><\/div>\n\n\n\n<p>If it was just one or two replacements, I&#8217;d put it all on a single line. Then, as the number of replacements increases, I&#8217;ll start to spread it out over a series of lines. This is to <em>try<\/em> to keep the parentheses matching &#8211; and to have some hope of following the code.<\/p>\n\n\n\n<p>The <code>TRANSLATE()<\/code> function takes three parameters: an input string, a set of characters that should be replaced, and a set of characters to replace them. Here&#8217;s how we can use it for the example above:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @PhoneNumber varchar(20) = '[02] 9232:4232';\n\nSELECT TRANSLATE(@PhoneNumber, '[]:', '()-');<\/pre><\/div>\n\n\n\n<p>The characters being matched and replaced are determined by their position in the string. <\/p>\n\n\n\n<p>That&#8217;s not all, though. You might now wonder what happens if the characters are matched multiple times. For example&#8230;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @PhoneNumber varchar(20) = '[[02]] 9232:4232';\n\nSELECT TRANSLATE(@PhoneNumber, '[]:', '()-');\n<\/pre><\/div>\n\n\n\n<p>&#8230;will return <code>((02)) 9232-4232<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-replace-vs-remove-in-sql-server\"><code>REPLACE<\/code> vs remove in SQL Server<\/h2>\n\n\n\n<p>Another reason why I often ended up using nested <code>REPLACE<\/code> functions in SQL Server was to remove a particular set of characters out of a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/find-and-replace-text-in-strings-in-t-sql\/\" target=\"_blank\" rel=\"noreferrer noopener\">string<\/a>.<\/p>\n\n\n\n<p>Perhaps I want to replace the square brackets with round ones &#8211; but also want to remove the colon, not replace it. Unfortunately, the SQL Server implementation of the <code>TRANSLATE<\/code> function doesn&#8217;t support this &#8211; as much as I wish it did!<\/p>\n\n\n\n<p>Here&#8217;s a good example of why this functionality is needed. If I execute this query&#8230;<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @PhoneNumber varchar(20) = '[[02]] 9232:4232';\n\nSELECT TRANSLATE(@PhoneNumber, '[]:', '()');<\/pre><\/div>\n\n\n\n<p>&#8230;the following error is returned:<\/p>\n\n\n\n<p><code>Msg 9828, Level 16, State 1, Line 3<br>The second and third arguments of the TRANSLATE built-in function must contain an equal number of characters.<\/code><\/p>\n\n\n\n<p>In T-SQL, the string of matching characters and the string of replacement characters <em>must<\/em> be the same length. That&#8217;s not the case in other SQL dialects. For more on this, see the <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/t-sql\/functions\/translate-transact-sql?view=sql-server-ver17#:~:text=character%20data%20type.-,translations,-A%20string%20expression\" target=\"_blank\" rel=\"noreferrer noopener\">official T-SQL documentation<\/a> (where they are called <em>translations<\/em>.)<\/p>\n\n\n\n<p>In <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL<\/a>, <a href=\"https:\/\/www.snowflake.com\/en\/\" target=\"_blank\" rel=\"noreferrer noopener\">Snowflake<\/a>, Databricks\/<a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/apache-spark-for-net-developers\/\" target=\"_blank\" rel=\"noreferrer noopener\">Apache Spark<\/a> SQL, <a href=\"https:\/\/www.red-gate.com\/blog\/what-is-amazon-redshift\/\" target=\"_blank\" rel=\"noreferrer noopener\">Amazon Redshift<\/a>, and <a href=\"https:\/\/www.intersystems.com\/uk\/products\/intersystems-iris\/\" target=\"_blank\" rel=\"noreferrer noopener\">InterSystems IRIS<\/a>, if the replacement string is shorter than the matching string, the extra characters are removed. You can even have an empty string for the replacement and just use <code>TRANSLATE<\/code> to remove a set of characters.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/introduction-to-oracle-database-for-database-professionals\/\" target=\"_blank\" rel=\"noreferrer noopener\">Oracle<\/a> also allows the shorter replacement string, but you can&#8217;t have an empty string for the replacement. That&#8217;s treated as <code>NULL<\/code> so, if you do that, the function returns <code>NULL<\/code> instead. In my experience, people tend to put a &#8216;dummy&#8217; matching character first, then use the same character as a replacement.<\/p>\n\n\n\n<p>I do wish SQL Server implemented the <code>TRANSLATE<\/code> function better, but at least it has the function in the first place &#8211; unlike <a href=\"https:\/\/cloud.google.com\/bigquery\" target=\"_blank\" rel=\"noreferrer noopener\">Google BigQuery<\/a> and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/getting-started-mysql\/\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>If you&#8217;re still writing lots of nested <code>REPLACE<\/code> functions in SQL Server, it&#8217;s time to see if any of them could be replaced (pun intended) by using the little-known <code>TRANSLATE<\/code> function instead. It&#8217;s great for this use case. <\/p>\n\n\n\n<p>All I&#8217;d really like to see now is the SQL Server team remove the restriction on requiring the characters and the translations parameters that force them to be the same length.<\/p>\n\n\n\n<section id=\"my-first-block-block_bf31fa884f9564009cfff15df1437983\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Fast, reliable and consistent SQL Server development&#8230;<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            &#8230;with SQL Toolbelt Essentials. 10 ingeniously simple tools for accelerating development, reducing risk, and standardizing workflows.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-toolbelt-essentials\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Fast, reliable and consistent SQL Server development...\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Using TRANSLATE instead of REPLACE in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What does the TRANSLATE function do in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\">It replaces multiple single characters in a string with one function call, matching characters by position instead of nesting several <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">REPLACE()<\/code> calls.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How is TRANSLATE different from REPLACE?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\"><code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">REPLACE()<\/code> handles one substring at a time and needs nesting for multiple swaps. <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">TRANSLATE()<\/code> does several character replacements in a single, cleaner call.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can TRANSLATE remove characters instead of replacing them?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\">Not in SQL Server \u2014 the character and replacement sets must be equal length, or you&#8217;ll get error 9828. PostgreSQL, Snowflake, and Oracle allow shorter replacement strings for removal.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What happens if a character appears multiple times?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\">Every occurrence is replaced. For example, <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">[[02]] 9232:4232<\/code> with <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">[]:<\/code> \u2192 <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">()-<\/code> returns <code class=\"bg-text-200\/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">((02)) 9232-4232<\/code>.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Which databases support TRANSLATE?<\/h3>\n            <div class=\"faq-answer\">\n                <div role=\"feed\" aria-label=\"Chat messages\" aria-describedby=\"_r_180_\" data-find-provider-scope=\"\">\n<div data-sizer-excess=\"0\">\n<div data-index=\"3\" data-last-message=\"true\">\n<div role=\"article\" aria-label=\"Message 4 of 4\">\n<div data-test-render-count=\"1\">\n<div class=\"group\">\n<div class=\"contents\">\n<div class=\"group relative relative pb-[var(--msg-assistant-pb,0.75rem)]\" data-is-streaming=\"false\">\n<div class=\"font-claude-response relative leading-[1.65rem] [&amp;_pre&gt;div]:bg-bg-000\/50 [&amp;_pre&gt;div]:border-0.5 [&amp;_pre&gt;div]:border-border-400 [&amp;_.ignore-pre-bg&gt;div]:bg-transparent [&amp;_.standard-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.standard-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8 [&amp;_.progressive-markdown_:is(p,blockquote,h1,h2,h3,h4,h5,h6)]:pl-2 [&amp;_.progressive-markdown_:is(p,blockquote,ul,ol,h1,h2,h3,h4,h5,h6)]:pr-8\">\n<div>\n<div class=\"grid grid-rows-[auto_auto] min-w-0\">\n<div class=\"row-start-2 col-start-1 relative grid grid-rows-[auto_auto] isolate min-w-0\">\n<div class=\"row-start-1 col-start-1 relative z-[2] min-w-0\">\n<div>\n<div>\n<div class=\"standard-markdown grid-cols-1 grid [&amp;_&gt;_*]:min-w-0 gap-3 standard-markdown\">\n<p class=\"font-claude-response-body break-words whitespace-normal\">SQL Server, PostgreSQL, Snowflake, Databricks, Redshift, InterSystems IRIS, and Oracle. It&#8217;s missing from BigQuery and MySQL.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div>\n<div class=\"flow-root\">\n<div>\n<div class=\"ml-1 mt-6 flex items-center transition-transform duration-300 ease-out\">\n<div class=\"p-1 -translate-x-px\">\n<div aria-hidden=\"true\">\u00a0<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn how SQL Server&#8217;s TRANSLATE function replaces messy nested REPLACE statements. Simplify multi-character string replacements in T-SQL with one clean call.&hellip;<\/p>\n","protected":false},"author":346483,"featured_media":104555,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,143524,143531],"tags":[4168,4170,4150,4151,4252],"coauthors":[159368],"class_list":["post-111898","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-sql-server","category-t-sql-programming-sql-server","tag-database","tag-database-administration","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111898","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/346483"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=111898"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111898\/revisions"}],"predecessor-version":[{"id":111991,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111898\/revisions\/111991"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/104555"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=111898"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=111898"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=111898"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=111898"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}