Skip to content

Using SQL Regex to Classify and Filter Messy Data

Using SQL Regex to Classify and Filter Messy Data
  • Author Avatar
    Written by:

    Sara Nobrega

Messy ratings, jumbled addresses, and buried keywords: SQL regex filters and classifies all of it inside the query, in seven real interview questions.

Text data rarely arrives clean. A stars column stores 4 in one row and four in the next, an address reads Pier 39 here and 39 Pier there, and a free-text field hides the word bull inside bullish. SQL regex (or regular expressions) is how we filter and classify that mess in the query, without exporting it to Python first.

A regular expression in SQL matches a value against a pattern instead of a fixed string. With it, we can keep only rows whose values are all digits, extract a year from a wine title, or count how often a specific word appears across thousands of documents.

We pulled seven interview-style questions from StrataScratch's coding platform to show SQL regex working on real messy data. The examples run on PostgreSQL, and we flag where the syntax changes on MySQL, SQL Server, and Oracle. For every question, we show the table, what one output row means, the solution built up step by step, and the output.

Using SQL Regex to Classify and Filter Messy Data

What Is SQL Regex?

A regular expression describes a text pattern: one or more digits, a word with a boundary on each side, an optional prefix, a set of allowed characters. SQL regex is that pattern language wired into the database so you can match, extract, replace, and split text in a query.

What is SQL Regex

In PostgreSQL, the core pieces are small:

  • ~ returns true when a value matches a pattern. !~ is the negated version. ~* and !~* are the case-insensitive forms.
  • regexp_matches(text, pattern, 'g') returns the matches, one row per match, when the g (global) flag is set.
  • regexp_replace(text, pattern, replacement, 'g') rewrites every match.
  • regexp_split_to_table(text, pattern) splits a string into rows.
What is SQL Regex

The patterns themselves use a shared vocabulary. 

[0-9] is a character class (any digit). ^ and $ anchor a match to the start and end of the value. + and * are quantifiers (one-or-more, zero-or-more). (plum|cherry) is alternation (this word or that one). \D matches any non-digit. In PostgreSQL, \m and \M mark the start and end of a word, and [[:punct:]] is a POSIX class for punctuation.

What is SQL Regex

A single predicate shows how they combine. stars ~ '^[0-9]+$' reads as: from start (^) to end ($), the value is one or more digits ([0-9]+). Anything with a letter, a space, or a decimal point fails. That one line is the difference between a clean cast and a query that errors on the first four.

Why LIKE Breaks Down

LIKE and ILIKE match a substring with % wildcards. contents ILIKE '%optimism%' is true when optimism appears anywhere in the value, and that same simplicity is where it starts to leak on three fronts:

  • False positives: %...% has no idea where a word starts or ends, so %optimism% matches optimism, optimismo, and optimism-driven all the same. If the requirement is the exact word, LIKE cannot tell them apart.
  • Brittle patterns: ruling out each lookalike means adding another NOT LIKE condition for every near-miss you happen to spot, and the pattern still misses the one you did not think of.
  • Unreadable logic: a predicate that has grown three or four NOT ILIKE exceptions buries the actual intent of the query under a pile of special cases.
SQL Regex vs LIKE

On a table where no value contains a lookalike substring, a loose match returns the same rows as a stricter one would, and the question below is a case like that. The moment a value can contain a near-miss, or the requirement says "the word" instead of "mentions," a regex word boundary is the safer choice.

Keyword Filtering: LIKE vs SQL Regex

EasyID 9805

A workplace file-storage system wants to help users quickly locate their own draft files related to a specific topic. Find all files whose name starts with "draft" (regardless of case) and whose content includes "optimism" anywhere within it, also regardless of case. Output all columns for these files.

Go to the Question

Data View

Table: google_file_store
Loading Dataset

google_file_store holds one row per stored file: a filename and the file's contents as free text.

Grain (what one output row means): one file whose name starts with draft and whose contents mention optimism.

Common Mistakes

contents ILIKE '%optimism%' is a substring match: it cannot tell optimism apart from optimismo or optimism-driven, because the substring is present either way. None of the stored files here contain a lookalike, so the loose match returns the same rows as a stricter one would. 

A different table might not be so forgiving; once a lookalike substring is possible, LOWER(contents) ~ '\moptimism\M' is the safer pattern, anchoring the match to a whole word with \m and \M. The filename ILIKE 'draft%' half is a fair use of LIKE either way: an anchored prefix, which regex would write as ^draft.

For a deeper look at what LIKE and wildcards can and can't do on their own, see our guide to SQL LIKE Queries for Pattern Matching.

Solution

1) Keep draft files that mention the keyword 

PostgreSQL
Go to the question on the platformTables: google_file_store

Output

filenamecontents
draft2.txtThe stock exchange predicts a bull market which would make many investors happy, but analysts warn of possibility of too much optimism and that in fact we are awaiting a bear market.

Same table, higher stakes next. The next question counts exact word occurrences in the same contents column, where a substring match cannot get away with the shortcut it took here.

SQL Regex for Exact Word Matching

HardID 9814

Find the number of times the exact words bull and bear appear in the contents column.

Count all occurrences, even if they appear multiple times within the same row. Matches should be case-insensitive and only count exact words, that is, exclude substrings like bullish or bearing.

Output the word (bull or bear) and the corresponding number of occurrences.

Go to the Question

Data View

Table: google_file_store
Loading Dataset

Same google_file_store table. We count how many times the exact words bull and bear appear in contents, case-insensitively, counting every occurrence even when a word repeats in one row, and excluding substrings like bullish or bearing.

Grain (what one output row means): one word (bull or bear) and its total number of occurrences across all files.

How This Shows Up In Interviews

The question hands you the failure mode: "exclude substrings like bullish or bearing." That is a direct request for word boundaries, and \m(bull)\M supplies them. A follow-up we have seen is "count every occurrence, not every row." A boolean ILIKE can only tell you whether a row contains the word. regexp_matches(..., 'g') returns one row per match, so a LATERAL join over it followed by COUNT(*) counts repeats inside the same document too.

Solution

1) Count one word with word boundaries

We match bull with a start-of-word (\m) and end-of-word (\M) escape so bullish does not count. The g flag makes regexp_matches emit a row per hit, and the LATERAL join lets us count those rows.

SELECT 'bull' AS word,
       COUNT(*) AS nentry
FROM google_file_store,
     LATERAL regexp_matches(LOWER(contents), '\m(bull)\M', 'g');

2) Stack both word counts (final solution)

PostgreSQL
Go to the question on the platformTables: google_file_store

Output

wordnentry
bull3
bear2

Same table as the LIKE query, same word, different result. That is the whole case for regex over LIKE when a word boundary matters.

When SQL Regex Is the Right Tool

Reach for regex when the pattern needs more than a fixed substring:

When SQL Regex Is the Right Tool
  • A character class or shape, like "all digits" or "a letter followed by four numbers."
  • A word boundary, so rose does not fire inside rosemary.
  • Alternation, matching any of several terms in one predicate.
  • Extraction, replacement, or splitting driven by a pattern rather than a fixed position.

Stay with plain LIKE for a simple prefix, suffix, or substring: filename ILIKE 'draft%' is clearer and can use an index. Stay with string functions like split_part or substring when the position is fixed and known. And when you find yourself parsing the same field the same way in query after query, the right fix is often a modeled column, which we cover under alternatives.

How to Filter Data with SQL Regex

Filtering is the smallest, most common regex job: keep the rows whose values match a shape, drop the rest. The cleanest example is guarding a cast. A column typed as text can hold 4, four, or an empty string, and :: INTEGER throws on anything that is not a clean number. A regex predicate removes the bad rows before the cast runs.

SQL Regex for Validating Numeric Data

EasyID 10056

Yelp wants its review ratings stored as clean whole numbers so downstream reporting can rely on them. Convert the star rating for each review to an integer, since some ratings were stored as text and include values that aren't valid whole numbers, such as decimals or non-numeric characters. Exclude any review whose rating isn't a valid whole number.

Output all columns from the reviews table, with the star rating returned as an integer.

Go to the Question

Data View

Table: yelp_reviews
Loading Dataset

yelp_reviews holds one row per review. The stars column is stored as text and contains both numeric values and non-numeric junk, so a direct cast is unsafe.

Grain (what one output row means): one review whose stars value is entirely digits, with stars returned as an integer.

Validation Checks

Before trusting the cast, confirm the pattern isolates the rows you expect. Run SELECT stars, stars ~ '^[0-9]+$' FROM yelp_reviews and eyeball which values return true. The anchors matter: [0-9]+ without ^ and $ would match 4 stars because it finds digits somewhere inside the value. Anchoring to the whole string is what makes ~ '^[0-9]+$' a safeguard. Note it also rejects decimals like 4.5, since . is not in the class. If half-star ratings exist, the pattern needs to change.

Solution

1) Return all columns for rows where stars is all digits (final solution)

PostgreSQL

Output

business_namereview_iduser_idstarsreview_datereview_textfunnyusefulcool
AutohausAZC4TSIEcazRay0qIRPeMAFgjlbPUcCRiXlMtarzi9sW5w52011-06-27Autohaus is my main source for parts for an old Mercedes that has been in the family since new that I am restoring. The old beater is truly a labor of121
Citizen Public HouseZZ0paqUsSX-VJbfodTp1cQEeCWSGwMAPzwe_c1Aumd1w42013-03-18First time in PHX. Friend recommended. Friendly waitstaff. They were understaffed and extremely busy, but we never knew. The short ribs were tende000
Otto Pizza & PastrypF6W5JOPBK6kOXTB58cYrwJG1Gd2mN2Qk7UpCqAUI-BQ52013-03-14LOVE THIS PIZZA! This is now one of my favorite pizza places in phoenix area. My husband i walked into this cute family owned business and were greet000
Giant HamburgersQBddRcflAcXwE2qhsLVv7wT90ybanuLhAr0_s99GDeeg32009-03-27ok, so I tried this place out based on other reviews. I had the cheeseburger with everything, some fries, and a chocolate shake. The burger was okay, 011
Tammie Coe CakesY8UMm_Ng9oEpJbIygoGbZQMWt24-6bfv_OHLKhwMQ0Tw32008-08-25Overrated. The treats are tasty but certainly not the best I have ever had. I would have rated this a two star but the cakes and cookies are REALLY pr132
Marcellino RistoranteGTUOIBCEGGt_aGp-bRogfggopGuEb-ft6cHKMyCZEvJg12010-12-17This place sucks. Food was average and we had to wait an hour even though we had a reservation. My americano tasted like warm soy sauce and they blame230
Shanghai ClubwHfxd0Bq4JYLiUEO55xe4QeVQ_yDkMlF62oofUwc29Kw52013-09-19This is our favorite Chinese restaurant in the area! The service is always consistent and our favorite waitress - Becky - always makes time to spend w000
Freddys Frozen Custard & SteakburgersNfTR_B1yW1hPVEoXlSJV-wYnHYlN1m7jDhAH9XgR4Dlg42013-01-24Love the tiny fries.100
Chipotle Mexican Grillk-Oo0Gs4AC04GJAecu_iWgHjpzhIQFRQbFmc_7CtFDmg42011-05-11When you don't feel like a full restaurant and you want more than the normal Mexican fast food, Chipolte fits the bill. The food is always fresh, the000
Arizona Fire & Water RestorationuvVmBnYQf8Mnt-s64D8XOgzQP7cLujr-MJ207uuNFC1w52011-08-03This is a fantastic company. They have a very high rating with BBB and have won a ton of customer service awards. Hopefully you dont need them but i000
Arriba Mexican Grill0HOrc_RX87-01dbdFMSjJw8m8HQtZox4vS-N-AW3mzxw52013-12-31Open Christmas Day! Their food is delicious. Especially breakfast! Mmmm, the salsa mmmmm. Hatch chills, pork, chicken...pollo asada, carne asada...you110
Renegade Tap & KitchenZaCA3v9bWUpHuwZ6NO8C1QiVTzpbZ6qBdFllvcJLbmeg42011-02-26Dinner on a busy Friday night. Arrived on time for 7:30 reservation. Table not ready, restaurant and bar full. Host and hostess pleasant, but not o001
Chipotle_gcGIGfziNkhaIlkjhjKHg3uU_6L8GnFOHTsO4I3oedg32013-09-24I absolutely looove Chipotle. My problem is when you make me pay almost $2 extra for guacamole... please don't be stingy with it. Hook it up, Chipotl121
SanTan VillageLXiDBkXxcyL4IPnXbjw0VQBa-tIR3a8hhwIk-y_hVzFg42011-03-27Next month when the temps skyrocket to triple digits, i will prolly not think soo highly of this place, but for now, San Tan has gotten alot of my fre012
Love'sEr4Y-yj1JBW9cCbIf3ViKgbC3By-saT9ylKu-dwWgtcw42012-11-13Plenty of gas pumps and convenient to get some cooked food inside. If getting gas, caution for enter and exits at pumps.000
Dirty DrummertMYUWXoFuLdFecqqP60R3AX_kPh3nt0AJPNPHye2rTlA42011-06-18I was introduced to this place by my coworker and friend. She goes here every single day at lunch and its cute because everyone knows her there! Any132
Euro Pizza Cafex2atXyt-QwCTzHhglzxj3QJKp42Y520azWI_WBzUMxTw52012-04-07This is a nice cafe with a diverse menu. There's indoor or outdoor dining with a view of the famous fountain! I ate here with my friend Connie who als001
US Airwayso_YetnCcK_96ueIULO84fAVl4k0FiMNCRzEQwOpe4hXw22012-07-20Well, they have good flights and connections from time to time. Their fleet is very archaic though, probably most of their planes are older than me (a010
Wal-Mart Neighborhood Marketc7JHcdWo5pZ3rMIDbsDt_gIDHrwv_RCildFvmfWTkj5Q22011-10-02I occasionally stop in this store while driving past. I will try to keep my comments on the positive side. NOTE if you don't read anything else: if 011
Flancer's Cafe78XeKBmSE0reBjsmqg7HNgAYGHNy8gPxl2Q-etTT3hZw32012-12-01This place has decent food, cute atmosphere, but the service is problematic. I was stuck in Mesa for training and had lunch there on Halloween. My pal575
Pappadeaux Seafood Kitcheny6q-inMFFoEci-wRATp1-AS3bvMOL50vgS_8-TtlGi4w12010-12-02$20 for a double Maker's Mark? Me thinks not T.G.I. McPappadeaux. I was not impressed by anything that was there and will not only steer clear but m1366
Lone Star Steakhousefp73RBYM6NAnNWii9bxZ8w7Ot-v89x44U_VdIPgD3qKg42011-10-18Great food and a rare bird of a honest manager when it comes to whats in the food. They have very comfy booths. Did nor care for the country music. I000
EVO3D3Avu2d8Gj-HEbqqWhswgH982l-WK1p49z9jZFNMEfQ42013-12-29Great atmosphere and decor..a welcome change from the chains and anchor restaurants in the area that all feel the same. My wife could not stop raving000
Conocido Park0ESAQ8Ynk1nZPt5LayMwng3OelvbzNK3KSmMdL0O9nRQ42013-10-08I play this disc golf course weekly. The baskets are frequently moved to keep the course fresh. Trees provide difficulty and also shade! There are Dis010
Nate's Barber ShopKEMsCW33Y1ZQEGTiMKmcrAHm_7pViZyrp_Z62lBRopAg52012-12-12Nates barber shop is one of the best shops that I have encountered in my life. If your looking for a great inexpensive ($13) good looking haircut this000
S & S Tire and Automotive Service Center99_nV5h4JHomT7cgh0V6lghYKjQHu2fk4nMgCWO50f-w52011-02-25My partner and I needed two new tires and an alignment. We have a Chevy Cobalt and its only two years old. Went to the dealership and they wanted almo100
La Parrilla SuizaldvKeuzBSIesZEmFcr4ooQEXvhtd_05d1H9RlXa2CnIQ52012-07-27This food is just....fantastic. I don't know what else to say. I grew up eating at La Parrilla Suiza in Tucson. The tortilla soup and "Queso Suiza"010
Scratch Pastries & BistroznMtXO5hY5XPqAMj_7VLRgb2DKC4kC8-QeSeGZ_MF3XQ52012-03-16Yes, it is in a strip mall. Don't let that fool you. This is some downright good food with affordable prices. Even though service was very attentive 000
Joyride Taco HouseuyLuLYfjs3S_8u3OkrIdmwmY6zzvFbK0ENnQOdgtiT4Q22013-10-19Great food! But not worth the HORRIBLE SERVICE! Took about 15 mins for drinks that included a dr pepper. The server said the bartender is working hard232
In-N-Out BurgernxoxgQka8mTK-rLCh7sg3wOwVB3YzcYeTRV09tpNDBSA52011-07-08Nothing beats a #1 with grilled onions, no tomatoes, well done french fries and a pink lemonade. I've been known to eat here twice to three times a we000
Roka AkorfpjKqP8ONJ9rT82VoUhIQQ4ozupHULqGyO42s3zNUzOQ52011-07-18I hate to admit it, but it had been a long while since my last visit to Roka Akor. I deserve a hand slap. But last week, I had the perfect excuse to p5810
Ruth's Chris Steak Housez3pSiipCrQM3B6i9PrnoGwhJBOxmNREXmMGTfXgMcGug52010-03-30Best steak I have ever eaten is at Ruth's Chris steakhouse. Comes to your table sizzling hot. Sides are sold individually but are pretty good. The des000
Yupha's Thai Kitchenarf6Ne6h0UDXizsbMcOomQAkJFqLqHHAKY3H5R8p7cPQ52012-12-09Yupha Thai is definitely a "yuppy" in terms of being a great spot to go to. There are not too many places to eat near ASU Research Park, which is rig000
Hotel Indigo ScottsdaleVDtEMw1X397ViDlP7oErTw8Oy9-UwJQWffS0yOwPG6Ew42013-07-01I stayed here for one night while on a recent business trip! I wish I had discovered this place sooner.... I was pleasantly surprised! I never heard 000
Da VˆngxbVGTBSsXmvu56FTbXp7AwF6mQhKLdj_PEdxLvDYOm2Q52011-12-15I haven't had better pho anywhere in az. the large pho is enormous for the price. i usually go with my family and order a regular sized pho for myself000
Trader Joe'sHzI7nVlXJQJR3GO1KXYxlAcMmQsFyrYBv6hIE6NffqZQ52011-01-03Trader Joe's always goes above and beyond in all of their services. The food is fresh and delicious, the prices reasonable and the people are great. 000
Beaver Choice18fIpXUbcm9k6Pmtkbf0aA3gIfcQq5KxAegwCPXc83cQ42011-04-21So I went here tonight with a friend. I was really excited and nervous to try this place after reading the reviews. So you walk in and walk up to the110
Some BurrosOqogqje3RKspPwVcREfsXAGnqNc74So5Pc8C3hkA2hCg52009-07-10Came here w/ the hubster's. I've actually been craving this for some time now. The pollo fundido is great! So goooooood! Its a pretty big portion. The021
Superstition Ranch MarketB1xnRb2j_iW2Ws0u1B0FNwEOLRikjQxTIpXB4aV1hbPQ52012-01-05Great place to shop, buy what you will use within a few days.000
Fuego TacosJ6nrjjCjXc-hnRpZZPrLnQA99dyhEqcd_yXKPfBWeZHA32011-08-18I went to a late lunch on a Saturday and the Esplanade area was quite dead (I'm surprised because in the old days ('98-'99), I remember it to be prett000
The Woodshed#NAME?lsp7p2NuC5MX4_iuch3_OA12012-06-17We only went here because it has been a traditional Father's Day event with friends. We decided to join them for the 1st time......really...we will n010
Rosati's PizzaLd4Qg2Du0S3ulcdDCdm7JgSEDJTWEzMdqp7UsS1W3KXw32012-10-24First of all, let me just say their food is fantastic. I love their pizza. I love their salads. I love the cheesy garlic bread and their chicken parm.100
Sekai Sushi8TB8vM1H_SuEK2hS-5wu7gTDlgqAxf268QOw-OUk2Urw22012-05-02I am sorry to say people of Mesa you must have pretty low standards if this is the best sushi bar in Mesa. I went there last night and I really did no020
D'Vine Bistro & Wine BarJvHH1Z84UJ1P5T9uIxEnyQrT4ycOjlrKefSAcjoQga5g42012-04-12Love the atmosphere and fantastic happy hour! Favorite spot in Mesa:)010
Scottsdale StadiumeAYq_HT_gbD_ECgIWn3GoAMt3dPqOlnlGyVCftCcokmg42012-03-27Ignoring the fact that Scottsdale Stadium is a bit overpriced these days for Spring Training Giants tickets, its fun. Its basically one big party (in 030
Thai House#NAME?fczQCSmaWF78toLEmb0Zsw42008-07-21Damn... Helen Y beat me to the punch and got the FTR for this place! Oh well, I will say that she did a great job with her review - I think I was Tha387
Jalape–o Inferno Bistro MexicanoVRiSQiIfUnZdp0CxNMkLWg4nJ5ryQTcQKs8mCrgt8-BQ22011-04-05The dinner we had here was OK... nothing to rave about. The tortilla chips were very good as others have mentioned... a mix of corn and deep fried flo000
Caffe Boahi6a3fvAbtZq9jMIM8gkwQqa05pUVNapADHZXpHMPMeA32010-06-26Caffe Boa is an interesting jokester, so, it is interestingly hard to review it. I'll keep it short. I have gone here several times, and each time wit000
Golden ValleyZYEAmRpYHxJYcbIv-c7S2wgg_OKjOAl_vVmdh5ZETuiw32012-02-25Can I tell the difference between Uzbek cuisine and other Middle Eastern or Mediterranean cuisine? Nope. For all I know it's only a matter of where000
Canyon Cafegi9hLYOPk_fbvOr2mCHu7gnH9OZEGfgseWjC5_IPGCXw52010-08-09Everything about this place is wonderful!! I love the huge windows and outdoor patio! Gorgeous! Their food is amazing everything from the chips (there001
Panda Expressr52OE-CfRoJQyjBtn0vHIQfMyKbyYY9Poy9B_1QZPKcg12010-05-31My young children love Panda Express orange chicken. They eat it 2-4 times a month. We were at the mall on a Saturday afternoon and stopped into the f000
Gordon Biersch Brewery RestaurantpfPFWY5SXQEEnlVJbFaNqAnKaR5Z9Qmqc4RsakLLX_7w32010-03-29It's very hard for me to enjoy a house brewed schwarzbier that, to me, tastes more like Bud Light with a hint of acrid smoke flavor than what I consid243
Chili's Grill & Bar42EOZ0KMF4wU1Sz7oeLfpAAfyzIHPy5zds_mqf2Jdc9g32011-02-07I love Chili's. My husband and I always go for the 2 for $20 deal at whatever Chili's location we may be, but this location seemed to skimp out on the000
Beckett's TablelNLiQx1zi-ctta6v4LLhXwGJwbccjXgoRPbNuWcNKYXA52012-01-29Beckett's Table is a fantastic restaurant for people who want great food, great wine, and great service. My wife and I dined there with a couple of fr000
My Big Fat Greek RestaurantIuSys52QuyTxGv3HLFKBSw1gY1N3pkxTzh7kK4BxANyw42011-05-07I hated Greek food, until I tried this place. My "health nut" girlfriend had to drag me to this place, kicking and screaming. After all, my long ago233
Goldmans Deli6DggWM9rgzC_mIo4THFpMAPKZvqm3IeWiWBYoDDoEG4w52012-09-02Traveled in from the east coast so I got to Arizona very early in the day. I needed to kill some time before I could check into my hotel room so I en000
Macayo's Depot Cantina98nvcyGhtHlKO8pDlOcCsAbZFRqP7s0Vszxeu8_IwYow32008-03-21I finally ate lunch here after not being able to get decent parking for Quiznos on Mill Ave. My coworker and I were starving and needed a place to sh023
Rayner's Chocolate & Coffee ShopGCdNDjutQWsT-qaYwW0zxwM28A6JPQFBJnRBCfODe8IA42013-05-15Cute bakery/coffee shop hidden in a little plaza on 51st Ave off of Thunderbird Rd. Nice selection of unique baked goods, chocolates and coffee drinks010
Sushi Brokersup3ueFZ1xJh_ts6dVu3_0AhDlSSyDreM9xY4yQWPm54w22009-02-26--expensive for business lunch --servers very attentive, prompt --lacks nth-degree detail of a Japanese chef running things; rolls and standards are s433
The Vig Uptownrib7dXO863eL5VGUDsot8guQCk37gNl1bEmkjAv6_kAw42011-04-17My meal was great; the decor / layout is great also, with a lovely patio out back. The only downsides are parking, and the fact that the entrance is a010
Lo-Lo's Chicken & WafflesIlFoK4meMZ7Ws4enESzeTQEacK6XwZjsTD6QYSIRlJ7Q52008-07-14At first most of my noobie friends are very skeptical about the fusion of Chicken and Waffles. but after taking them to this place and experiencing f232
Shoe CarnivalYhlJA_CuoZlK4FIJUHlCnw_PzSNcfrCjeBxSLXRoMmgQ22010-05-17I had a $5 coupon in the mail so I was like what the heck. And is it next to Home Goods (one of my favie home decoration stores). I got to the store i210
My Big Fat Greek RestaurantQe0FO565tGfTxb7QtNCVwg2vl3MXKr8iQOWTNse5kgdw32008-04-03Nice menu selection; food was tasty. Good atmosphere. Wished they had a restaurant in the LA area.010
Casey Moore's Oyster HouseR7ZJPW4qEXuqI41aaWmO0ArLtl8ZkDX5vH5nAx9C3q5Q42009-04-02This is a fun place for appetizers and drinks if it is not too crowded and the temperature is just right outside. Otherwise the inside gets way too p121
Q to U BBQIJxqQwzJjAURPBAB_-iOAAMSgZpSWlf8T2H_46OWNgCQ52011-09-08Really enjoyed the Ribs and fries, I came with friends that really like BBQ, they agree, the ribs were terrific! I am sure we will be going back to Q111
Lightning LubekezCWAz6MO1wKwXB_DK-3QbwmXfjwrogAaGqV33kSVpQ32013-08-24$115 for an oil change and two air filters for my civic. I must've had a really long day or she had magical powers and made me forget I could simply c011
Athens GyrosaAgVzZU2b0YbYGi4byeI6wL8_GwFxxtGSYR2F_dglpSg42013-08-17Great food nice service. The girl that worked up front introduced herself to the other patrons that were there and asked them how the food was but no000
Metro Light RaillG8Swugg_DQxY3NgT_BEigLqgGgWi3FLHBViX9tmZ9sw32011-10-31I just wish that this stupid HUGE metropolis could have more LIGHT RAIL connections!! compared to the circus you have to stand in the buses, the LR is111
Oregano's Pizza Bistro7QvgM_LJi6SRp_GuOXPFZQfor16MiFS1M_8_cne6IbIw42013-02-04Big Delicious portions!000
Gallo BlancogULD5qz_CQI9clPWh2FNHAZ02XdD0muEz2FFQKPERMYQ52013-12-29Stopped in here one night right before Christmas. Short and sweet: Margaritas - very good (and huge by the way) Tacos - awesome Guacamole - exce222
Los Dos MolinosJcWhDcyNl3r_Tbeqiac15QGoymUzKqvET2QOZkIWZi9w42014-01-07We have a friend who said the salsa is way too hot. All I heard was "you must try Los Dos Molinos". We love spicy food and are always up to a challeng011
Nancy's Nail SalonZGo8c57MrzQrSN6R7zO1uQA9g7YnTtsSV-wEIo3HI1YQ42011-06-09Came here with my sister in law she had a coupon for a 27.99 mani/ spa pedi. The staff was friendly they have a tv and plenty of magazines to look thr010
Changing Hands Bookstore1xzMe1EEwhF23RNh3InKkQfPHLPrymsyb6WSFFKoMrTQ52010-10-26This not-so-little bookstore has it all... new and used books, a unique gift section, book signings and events, wonderful staff and a cool, organized,001
Tortilla FishOmSYYxZskG9BeRMwb5DltwMxO7EY766jVoFEZzkpwmOQ22013-10-06My experience wasn't bad, just not up to the hype of all the other reviews. I tried the shrimp, fish, campechana and machaca tacos. The shrimp tacos w000
Pet Club0LvO1yc-52fJ6vIHaFVdAwLXOhR4ZUULSbBNztxYZ2dQ22013-07-16That awkward moment when local competitors come write negative reviews about a store and then direct traffic to their own store....011
Taste of TopsJ2lGBvJOcuhmauWs3rgMSgaIAjAU-6NH583EkQ6E9KRw42009-10-09Okay, in interest of full disclosure, I literally live around the corner and across the street from Tops Liqour and have been waiting forever for this111
Carolina's Mexican FoodN6eg6Jc_mL_XHMGmw6GElwcbxUyCUMjkWAs1h4auYeAw42012-01-31If you're looking for real Mexican in a hurry this is your place. This is one of places ill always bring out of town guests who can't find good Mexica001
Super L Ranch MarketcK3J7FAqruLZM_Y5J29Q8Qz06IHGXI_ofBc2DkAbCgnA52011-05-09HOLY CRAP THEY HAVE FROZEN XIAOLONGBAO. :) These delicious little bites of porky, soupy dumpling heaven have eluded me since I first tasted them in 221
Salt CellarIXGX_Lk2NgCH-0OQNcGMpQE4HbTIHd9PVjUnEKpysaLw52011-10-04My experience here was absolutely amazing. My boyfriend and I had reservations for 7:30 pm on a Saturday night and the service was amazing and the fo021
Rosita's Placef_yQqlsim0S9YAIIYFvR5Q7nlZJW84Adt6oYn2shnn_g32013-05-30The food here is delicios, but it takes forever. From the time I ordered to when I was served 25minutes. Come only if you have time to spare to sit an000
Matador Restaurantx927gFqVNPSOPwNrKqPmmQnyHh14Vb9S269-kGKaUelg42011-08-09I've eaten at Matador almost once a month for about15 years. I won't get into the logistics of others reviews. I give the higher than average star rat001
Hanny'sriZp_RIN28ld-U2Q5dhKhAGRgBu4K7GOb3354esp_xkg42010-06-08Food = 4 stars Place = 5 stars Service = 4 stars I REALLY like this place. It looks super classy inside and the deco on both floors is a333
Crust Pizza and Wine CaferTc3d_GYXyHuf_tQoED80gAOmdmYYSeLUstcN084_wMA12012-10-16I told my friend that I'd rather eat out of a vending machine, and he said.. "Yelp THAT!" I had the calamari app, the caprese salad and the eggplant210
Ocean Air9gLTx4HjE-NeSa3KTfGJJQK0U0Hp6rgXHrYCG4jpPT8w52013-09-01Thank you! Ocean Air came highly recommended and now I know why!! Excellent, fast, friendly service!!! Reasonably price AC maintenance and FAST respon000
Sleepy Dog Brewpub9gtyU7vjWUjddmFrT97swwFm0EXFwIfDQoIm9RgcAOKQ32013-04-06As a number of others have said, the beer is good with a good number of choices. The food is pretty good too. The biggest issue has been service. The000
unPhogettableyVK0x3_-o16ufBbIyqGJRwsWh4Tjwa8ch_rziHtTN9LA52013-08-27Always amazing service! Always amazing pho! Add veggies to a meat dish! The spring rolls A1 & A6 are the best in town!!! We are now here on a weekly b111
Paradise Valley Burger CompanybkZ67PfRlKLKl4x5mIFYSgff00OcqImnNYy-OvSgUZyw52013-11-04Best burgers in town! They don't skimp on quality or ingenuity.000
Hanny's4n_3G2Xux0stcgOUsrzYawev7D2jo5OUDeHf0dWoWlsQ22012-06-25Super disappointing, they won't take reservations and I wanted to make a reservation for 20 persons. I've been here many many times and love the food 002
Golden PandaikNpO72tj7uI5VTatHpoAA80OFMLRA0yW3sE4ciYg_vA12009-10-10Why oh why is it so hard to find good Chinese food in this town? The two behind the counter at this place are Chinese - please don't tell me you eat000
Breakfast ClubU_oJEB166nCeBNY-wqadxwAMYi-53cxstrCR5wqyY1KA52011-01-11O-M-Goodness! What luck to have eaten here for breakfast!! Huge portions served with fresh fruit slices or mixed berries. Great service and very nice000
Ulta Salon Cosmetics & Fragrance9e3MOWg4zrq_NOqKP3fMcQF6QsMoJdvtohlbnST-fDyQ42011-03-14I really like this store and have been going here for as long as they have been open. 18 or 19 years. It is always exciting to be rewarded for the pro110
Essence Bakery CafŽNkekoPY-4txUxkyoN_Tu4wDrWLhrK8WMZf7Jb-Oqc7ww52012-09-14Ok, I'm not sure I ever had this pastry combination, but it was clearly a great item. It was the Chocolate almond croissant. Usually those two varieti010
zpizzaUJEPSoO6yNnR8kdneDy0rgfSi-yrKtBD58h2vPxjNE1A42010-12-01We ordered 4 rusticas for delivery using their buy-one-get-one promo online. They had a great deal on rustica pizzas, but somewhere in the fine print 211
Kona GrillosYRF4FQe4cziGIXz33eQQgITFg65GtRDUb-0n460vNg42011-03-17I've eaten at Kona Grill twice in two days while doing business in the area. I had the Kona Burger and the Pepperoni Pizza. The burger was fantastic000
White Housexn2LkVHBuRZ_jAg-LIiQ4QwFweIWhv2fREZV_dYkz_1g42011-07-25It's been a while since I took part in the nightlife in Scottsdale, it's not my scene but I was invited to White House for a party so you know how tha354
Crowne Plaza Resort Hotel San Marcos Golf ResortbMKW11Cf1Zeu1zWkzDbtrQKt9NwDONle_mc0QHTud9jw12011-07-18Rating the golf course, horrible!! Thank goodness there was no one in front of us and we zipped around the course. They clearly stopped maintaining 000
Hon Machimu8Gst6LkzG5ahmolCH55gKucBnMrhalzxnD9AWrxwYQ52011-06-21Great place for sushi and tepan - period. Not the "high-end" places but a rock solid place with lots of variety and good prices.010
Bourbon Steak a Michael Mina RestaurantAA6QQUFGWWkZlbpat46OfQcEIeuU0-4fX0Y4qCUW3PwQ22008-12-01My husband, some friends and I went to this place for restaurant week. We all sampled different dishes to experience a range of items - the multi-flav012
Tempe's Front PorchJ71o5dOSoxoOhcR8NEo4OgR4Ax3btoJ6qLXhqq6J50VQ42014-01-06This is the front porch of Monti's, my boyfriend and I were pretty confused looking for it. It's outdoors with a bunch of heating lamps, be sure to s121
Joyride Taco HousepKe_ORPqaW0vfGyFkbxdHwxv9nUSKR5RqnkgD0tufTfA42013-12-02Tried this place tonight with my boo and I am definitely a fan. He loved his carne asada burrito and my enchiladas were super tasty. They are a littl000
LunardisfpjKqP8ONJ9rT8209thIQQ9itypHULqGyO42s3zNUzOQ52018-06-11This is the nicest grocery store in the city. I actually met my wife at this grocery store while shopping for avocados.6710

The pattern ^[0-9]+$ is the workhorse of numeric filtering. It answers "is this value a plain integer?" and nothing else. We come back to it in the FAQs, and again in the classify section, where the same test decides which token in an address is the house number.

How to Classify Messy Data with SQL Regex

Filtering keeps or drops a row. Classifying labels it. Regex classifies by testing each value against a pattern and using the result to route the row, usually inside a CASE. The address problem below is the clearest example, and it shows regex doing one job while split_part and CASE handle the rest. If CASE syntax itself is rusty, our full guide to CASE WHEN statements covers the basics before you get to the address example below 

SQL Regex for Classifying Inconsistent Data

MediumID 10182

Count the number of unique street names for each postal code in the business dataset. Use only the first word of the street name, case insensitive (e.g., "FOLSOM" and "Folsom" are the same). If the structure is reversed (e.g., "Pier 39" and "39 Pier"), count them as the same street. Output the results with postal codes, ordered by the number of streets (descending) and postal code (ascending).

Go to the Question

Data View

Table: sf_restaurant_health_violations
Loading Dataset

sf_restaurant_health_violations holds one row per recorded health violation, including the business's business_postal_code and business_address. Addresses are inconsistent: some read 1000 Folsom St, others Pier 39, others 39 Pier.

Grain (what one output row means): one postal code and the number of distinct street names in it.

Trade-offs

The task splits into three jobs, and each tool does the one it is best at. split_part(business_address, ' ', 1) extracts a token by position. ~ '^[0-9]+$' classifies that token as numeric or not. CASE encodes the decision: if token 1 is the number, the street is token 2; if token 2 is the number (the 39 Pier case), the street is token 1; otherwise take token 1. Trying to do all three with regex alone would be harder to read and slower. Splitting the work is the senior move here.

Edge Cases

Two assumptions can break this. First, NULL postal codes: the WHERE business_postal_code IS NOT NULL guard drops them before they reach the GROUP BY, so a NULL code does not become its own bucket. Second, the "first word" rule assumes the street name is a single token, so Van Ness collapses to van. That is acceptable for a rough street count and would be wrong for exact address matching. Naming that assumption out loud is what an interviewer listens for.

Solution

1) Classify each address and extract the street token

For each row, we test which token is the house number and take the other one as the street name, lower-cased so Folsom and folsom collapse.

SELECT
    business_postal_code,
    business_address,
    LOWER(
        CASE
            WHEN split_part(business_address, ' ', 1) ~ '^[0-9]+$' THEN split_part(business_address, ' ', 2)
            WHEN split_part(business_address, ' ', 2) ~ '^[0-9]+$' THEN split_part(business_address, ' ', 1)
            ELSE split_part(business_address, ' ', 1)
        END
    ) AS street_name
FROM sf_restaurant_health_violations
WHERE business_postal_code IS NOT NULL;

2) Count distinct streets per postal code (final solution)

PostgreSQL
Go to the question on the platformTables: sf_restaurant_health_violations

Output

business_postal_coden_streets
9410316
9413311
9410210
941099
941078
941088
941108
941128
941047
941057
941146
941115
941155
941225
941184
941214
941324
941344
941173
941233
941243
941162
941272
941311

SQL Regex Correctness Pitfalls

Regex is precise about what you write, which means most bugs are patterns that match slightly more or slightly less than you meant. Two traps show up constantly: accidental substring hits, and the empty string that regex returns when nothing matches.

SQL Regex for Handling Word Variations

MediumID 10026

A wine curator wants to identify producers whose wines feature specific delicate aromas. Find all wineries that produce wines whose descriptions mention plum, cherry, rose, or hazelnut in singular form. Exclude any wine whose description also contains a plural form of those words (e.g., "cherries", "plums", "roses", or "hazelnuts").

Output the distinct winery names.

Go to the Question

Data View

Table: winemag_p1
Loading Dataset

winemag_p1 holds one row per wine review, including the winery and a free-text description of the wine's aromas. We want wineries whose descriptions mention plum, cherry, rose, or hazelnut in the singular, and not the plural forms.

Grain (what one output row means): one distinct winery matching the aroma rule.

Common Mistakes

Three mistakes hide in this one query. 

First, no boundaries: description ~ 'rose' matches rosemary and prosecco. The escapes \m and \M pin the match to a whole word. 

Second, treating "match the singular" as enough. Matching cherry does not exclude cherries, so the second predicate uses !~ to reject the plurals explicitly. 

Third, case. Rather than the case-insensitive operator ~*, this solution first lowercases the column with lower(description) and matches against lowercase patterns, keeping the two predicates consistent. Either approach works; mixing them is where people trip.

Solution

1) Match any of the singular aromas as whole words

Alternation (plum|cherry|rose|hazelnut) matches any one of the four, and \m ... \M keeps each to a whole word.

SELECT DISTINCT winery
FROM winemag_p1
WHERE lower(description) ~ '\m(plum|cherry|rose|hazelnut)\M';

2) Exclude the plural forms (final solution)

PostgreSQL

Output

winery
Bella Piazza
Bodega Noemaa de Patagonia
Bodega Norton
Bodegas La Guarda
Caligiore
Camlibag
Catalina Sounds
C. Donatiello
Comtesse Therese
Dashwood
Geyser Peak
Goldeneye
Grandes Vinos y Vinedos
Hopler
Il Poggione
La Capilla
La Mannella
Les Belles Collines
Mannina Cellars
Martin Ray
Niebaum-Coppola
Pine Ridge
Roagna
Sullivan
Terra Valentine
Valiano
Wolffer

The second pitfall is about what a regex returns when it finds nothing. This next question extracts a year and then has to survive titles that contain no year at all.

SQL Regex for Extracting Numbers from Text

MediumID 10039

Find the vintage years of all wines from the country of Macedonia. The year can be found in the 'title' column. Output the wine (i.e., the 'title') along with the year. The year should be a numeric or int data type.

Go to the Question

Data View

Table: winemag_p2
Loading Dataset

winemag_p2 holds one row per wine review, including the country and a title that usually contains the vintage year, as in Château 2016 Vranec. We want the year, as a numeric type, for wines from Macedonia.

Grain (what one output row means): one Macedonian wine with its extracted vintage year.

Edge Cases

regexp_replace(title, '\D', '', 'g') deletes every non-digit, collapsing the title down to its digits. The sharp edge: if a title has no digits, the result is not NULL, it is the empty string '', and ''::NUMERIC throws an error that kills the whole query. This is the single most common regex-extraction bug. Regex functions signal "no match" with an empty string, never with SQL NULL, so you have to convert it yourself. NULLIF(..., '') turns the empty result into NULL, the cast becomes safe, and the row survives with year = NULL instead of crashing the query.

Solution

1) Strip every non-digit from the title

SELECT
    title,
    regexp_replace(title, '\D', '', 'g') AS digits
FROM
    winemag_p2
WHERE
    country = 'Macedonia';

2) Convert the empty case to NULL, then cast (final solution)

PostgreSQL

Output

titleyear
Macedon 2010 Pinot Noir (Tikves)2010
Stobi 2011 Macedon Pinot Noir (Tikves)2011
Stobi 2011 Veritas Vranec (Tikves)2011
Bovin 2008 Chardonnay (Tikves)2008
Stobi 2014 uilavka (Tikves)2014

How to Test SQL Regex Patterns

A regex predicate either matches or it does not, and it will not tell you why. Test patterns before you trust them.

How to Test SQL Regex Patterns

The fastest check is to project the match result next to the value instead of filtering on it. SELECT stars, stars ~ '^[0-9]+$' AS is_int FROM yelp_reviews shows you which rows pass and which fail, so you can spot a value the pattern wrongly keeps or drops. Run it on a sample first with a LIMIT.

For extraction and matching, regexp_matches(..., 'g') is its own test tool: it returns exactly what matched, one row per hit. We used it in the bull/bear count to see every occurrence, and it works the same way for debugging, when you want proof that \m(rose)\M is not firing on rosemary. For splitting, regexp_split_to_table shows the tokens your split actually produced, which is how you catch an empty token from a double space or a stray delimiter.

Two checks catch most bugs: compare the count of matched rows against the count of unmatched rows and confirm the split makes sense, and spot-check the boundary cases by hand (a value with no match, a value with a repeat, a NULL).

SQL Regex Performance Trade-Offs

Regex is evaluated per row, and it does not use a standard B-tree index the way an anchored LIKE 'abc%' can. On a few thousand rows, this never matters. On tens of millions, it does.

Two costs are worth naming. First, a regex in a WHERE clause scans every row and runs the engine on each value. If a cheaper condition can shrink the set first, put it before the regex, the way the aroma query already filters on lowercase and the street query guards on IS NOT NULL. Second, functions that produce rows multiply the work: regexp_split_to_table in the word-frequency query turns each document into one row per word, and regexp_matches(..., 'g') in the bull/bear count does the same per match. A table with long text fields can explode into a very large intermediate result.

When a regex filter runs constantly on a large table, the durable fix is to stop parsing at query time. Compute the parsed value once and store it, either as a generated column or during load, and index that. We return to this under alternatives.

Alternatives to SQL Regex

Regex is not always the cheapest or clearest tool. Before reaching for it, weigh the alternatives.

Alternatives to SQL Regex

Plain LIKE and ILIKE win for a fixed prefix, suffix, or substring. filename ILIKE 'draft%' is readable and index-friendly, and no regex improves on it.

String functions win when the position is known. split_part, substring, left, right, position, and translate handle fixed-shape parsing without a pattern engine. The street query leans on split_part for exactly this reason and uses regex only for the numeric test.

Full-text search wins for real-word search at scale. PostgreSQL's tsvector and tsquery are built for matching words across large document sets with stemming and an index, which is a better fit than a table scan of regexp_matches when search is a core feature rather than a one-off.

Data modeling wins when you parse the same field the same way repeatedly. If every query strips a year out of a title, the year belongs in its own column, populated once, indexed, and validated on write. That turns a per-query regex into a one-time cost.

Preprocessing wins when the same cleanup runs on every read. The word-frequency query strips punctuation with regexp_replace(t.contents, '[[:punct:]]', '', 'g') every time it runs, and every other query against contents would redo that same work. Clean the text once, when the row is loaded, and later queries read already-clean text instead of paying for the regex again.

Derived columns win when the same extraction runs on every read. If every query pulls a year out of a wine title with regexp_replace and a NULLIF guard, the year belongs in its own column instead, computed once, either as a generated column or during load. Filtering on year = 2016 is also cheaper than re-evaluating NULLIF(regexp_replace(title, '\D', '', 'g'), '')::NUMERIC = 2016 on every row.

Normalization wins when one field packs more than one fact. business_address holding a house number and a street name jammed into one string is why the street query needs split_part and a regex test just to work out which token is which. Splitting that column into street_number and street_name at write time turns the three-tool question the street query answers into a plain GROUP BY on a column that already holds the right value.

Preprocessing and Normalization in SQL Regex

Common SQL Regex Use Cases

Across the questions above, the same handful of jobs keep coming up:

Common SQL Regex Use Cases
  • Validate: keep only values that match a shape, as ^[0-9]+$ does for the star ratings.
  • Extract: pull a structured value out of free text, as \D stripping does for the vintage year.
  • Clean and normalize: strip or standardize characters before further work.
  • Mine text: count exact words across documents, as the bull/bear query does.
  • Classify: label or route a row by which pattern it matches, as the address CASE does.

Cleaning and tokenizing show up so often that they deserve their own example. The next question stacks two regex tools to turn a text column into a word-frequency table.

SQL Regex for Cleaning and Tokenizing Text

MediumID 9817

Find the number of times each word appears in the contents column across all rows in the google_file_store dataset. Output two columns: word and occurrences.

Go to the Question

Data View

Table: google_file_store
Loading Dataset

The same google_file_store table. We count how many times each distinct word appears across all the contents values.

Grain (what one output row means): one distinct word and how many times it occurs in total.

Trade-offs

This is the "clean, then split" recipe. regexp_replace(t.contents, '[[:punct:]]', '', 'g') strips punctuation using the POSIX class [[:punct:]], so optimism, and optimism normalize to the same token. Then regexp_split_to_table(..., E'\\s+') splits the cleaned text on runs of whitespace into one row per word. The E'\\s+' is an escape-string literal: the E prefix lets \\s mean "whitespace," and + collapses multiple spaces into a single split so you do not get empty tokens. Doing the same cleanup with nested replace() calls would be longer and would still miss cases the character class covers for free.

Solution

1) Clean punctuation and split into one row per word

SELECT t.filename,
       regexp_split_to_table(regexp_replace(t.contents, '[[:punct:]]', '', 'g'), E'\\s+') AS word
FROM google_file_store t;

2) Group and count each word (final solution)

PostgreSQL
Go to the question on the platformTables: google_file_store

Output

wordoccurrences
market6
a5
and4
the4
of4
investors4
stock3
make3
many3
happy3
which3
bull3
would3
exchange3
predicts3
are2
optimism2
but2
much2
possibility2
too2
warn2
bear2
fact2
awaiting2
analysts2
we2
that2
in2
their1
as1
predicting1
always1
is1
best1
game1
uncertain1
all1
an1
should1
practices1
follow1
instincts1
future1

We cover regexp_split_to_table and its string-to-array cousins in more depth in our guide to string manipulation in SQL.

SQL Regex Syntax by Database

The patterns are mostly portable. The operators and functions around them are not. The ~ operator, the regexp_* functions, and the \m/\M word boundaries used above are PostgreSQL-specific. Here is how the common jobs translate.

SQL Regex Syntax by Database

Two notes matter in interviews. SQL Server had no native regex operator for years; teams worked around it with limited LIKE bracket classes or CLR functions. SQL Server 2025 added REGEXP_LIKE, REGEXP_REPLACE, REGEXP_SUBSTR, and related functions, so newer patterns now match those in the table. And SQLite has a REGEXP operator only when the host application registers a regexp() function, so it is not available by default. StrataScratch runs PostgreSQL, which is why the solutions above use ~ and the regexp_* family.

LIKE vs Regex vs String Functions vs Data Modeling

Four tools, one decision. The honest answer is that each owns a range, and picking well is the skill. The decision rules follow from the table. If the pattern is a fixed substring, use LIKE.

LIKE vs Regex vs String Functions vs Data Modeling

If it is a shape or needs a boundary, use regex. If the position is fixed, string functions are clearer and faster. And if the same parse runs in query after query on a large table, model the value into its own column so you pay the cost once. A senior answer names the trade-off out loud: a regex in an ad hoc query is fine, but the same regex in a nightly job over a hundred million rows is a reason to add a parsed column.

SQL Regex Best Practices

  • Anchor when you mean the whole value. ^[0-9]+$ checks the entire string; [0-9]+ alone matches digits anywhere inside it.
  • Use word boundaries for whole-word matches. \m word \M stops rose from firing inside rosemary.
  • Guard casts against the empty string. regexp_replace returns '', not NULL, when nothing matches, so wrap it in NULLIF(..., '') before casting.
  • Decide case handling once. Either lowercase the column and match lowercase patterns, or use ~* throughout. Do not mix the two.
SQL Regex Best Practices
  • Test with regexp_matches before filtering. Project the match result next to the value so you can see what passes and what fails.
  • Filter cheaply first. Put an indexable or low-cost condition ahead of the regex to reduce the row set.
  • Watch functions that produce rows. regexp_split_to_table and global regexp_matches multiply rows and can blow up an intermediate result.
  • Know your dialect. ~ and regexp_* are PostgreSQL; MySQL, Oracle, and SQL Server 2025 use the REGEXP_* functions instead.

Conclusion

SQL regex earns its place on the jobs LIKE and string functions cannot do cleanly: matching a shape, requiring a word boundary, extracting a value from free text, or classifying a row by pattern. The seven questions here walk that range, from a one-line digit filter to a word-frequency table built out of regexp_replace and regexp_split_to_table.

Anchor and bound your patterns in SQL Regex

The two habits that separate a working query from a fragile one are worth repeating: anchor and bound your patterns so they match exactly what you mean, and handle the empty string that regex hands back on no match. Get those right, test the pattern before you trust it, and keep an eye on cost at scale, and regex becomes a reliable way to filter and classify messy data without leaving the database.

FAQs

What is SQL Regex

Is SQL Regex Slow?

It can be on large tables, because a regex predicate runs per row and cannot use a standard B-tree index the way an anchored LIKE 'abc%' can. On small and medium tables, the difference is negligible. When it matters, filter with a cheaper condition first, and if the same regex runs constantly, precompute the parsed value into an indexed column instead of matching at query time.

How Do I Match Only Numbers Using SQL Regex?

Anchor a digit class to the whole value: col ~ '^[0-9]+$' is true only when the value is one or more digits from start to end. To extract the digits from a mixed string instead of filtering, strip the non-digits with regexp_replace(col, '\D', '', 'g'). Note that ^[0-9]+$ rejects decimals and negatives, so widen the class if you need those.

How Do I Classify Rows Using Regex in SQL?

Put the regex test inside a CASE. Test each value against a pattern and use the result to assign a label or pick a branch, as the address query does: CASE WHEN split_part(addr,' ',1) ~ '^[0-9]+$' THEN ... END. The regex decides the category; the CASE records the decision.

How Do NULL Values Behave with Regex?

A regex match against NULL returns NULL, not true or false, so a row with a NULL value never passes a ~ filter. The other direction is the trap: regexp_replace and regexp_matches return an empty string, never NULL, when nothing matches. Wrap the result in NULLIF(..., '') if you need a real NULL before casting or aggregating.

Should I Use Regex to Validate Email Addresses in SQL?

For a rough check, yes: a pattern like col ~ '^[^@\s]+@[^@\s]+\.[^@\s]+$' catches obviously malformed values. A fully correct email regex is enormous and still cannot confirm the address exists. Use a simple pattern for shape validation in the database, and verify deliverability with a confirmation email rather than a longer regex.

Can Regex Replace Data Normalization?

No. Regex cleans and reshapes text at read time, as the word-frequency query does when it strips punctuation and splits on whitespace. Normalization changes how the data is stored, so every query benefits and the cost is paid once. Use regex to prototype the cleanup and handle genuinely ad hoc text, and move repeated parsing into the schema when a field is read the same way over and over.

Share