PostgreSQL has been around for thirty years as of this year, and is the go-to choice for applications that need reliability, performance and scalability. It's an open-source SQL database used by lots of big companies and startups, from web apps to scientific research. But as more complex apps need to be built, the limitations of traditional SQL databases lead developers to look for alternatives. One of the main issues developers come up against with PostgreSQL is writing complex joins. It's even tricky for experienced SQL programmers.
Putting an end to this complexity is one of the reasons to opt for SurrealDB instead. Let's contrast the two and see how they compare.
What SurrealQL is all about
SurrealDB uses its own query language called SurrealQL. This language strongly resembles traditional SQL and in many cases can be used identically, but has many differences and improvements. Note the phrasing "traditional SQL" and not "standard SQL", because there is in fact no such thing as a universally standardised SQL language; SurrealQL can be thought of as another one of its dialects if you prefer to see it that way.
As anyone who’s done plenty of database migrations will tell you,
“standard SQL” is a cruel joke filled with false hopes and pitfalls.
Even when migrating between the most well-known “standard SQL”
databases such as MySQL and PostgreSQL, you’ll hit a lot of
inconsistencies such as:
Since each SQL dialect is annoyingly different anyway,
why force yourself to pretend to be “standard SQL”, when
you can take the best ideas from SQL and combine them
with the best ideas from other NoSQL query languages?
Inside familiar statement names like SELECT, CREATE, UPDATE, DELETE, RELATE and INSERT there are many ways to retrieve data, such as . dot notation, .{ field_1, field_2 } destructuring notation, and -> graph semantics.
SurrealQL allows records to link to other records and travel across all embedded links or graph connections as required.
With that quick introduction out of the way, let's see how SurrealQL can overcome some of the limitations that relational databases have.
Building an e-commerce platform
In this blog, we'll be studying an e-commerce platform built using PostgreSQL and SurrealQL side by side.
An e-commerce application needs a special schema that can handle product variations, pricing tiers, and customer details. While e-commerce databases can be successfully built using relational databases, the schema can be complex with numerous tables and joins in order to maintain the transactional data.
Below is the relational schema of a lightweight e-commerce platform. It has a limited scope and will insert only three customers and a couple of products so we can focus on each query and compare it with SurrealQL to understand how they differ.
Data Manipulation and Querying
Let's look at how we INSERT data in a table in PostgreSQL vs in SurrealDB.
Manipulation and querying of data in SurrealQL is done using the SELECT, CREATE, INSERT, UPDATE, UPSERT, and DELETE statements. These enable selecting or modifying individual records, or whole tables. Each statement supports multiple different tables or record types at once.
If you were to use SurrealDB in a strictly SCHEMAFULL approach, you would define a schema similar to PostgreSQL. You can find a detailed explanation of defining tables in SurrealDB by viewing the DEFINE TABLE statement documentation.
But in the default SCHEMALESS approach, you also have the option to quickly get started without having to define every column. In SurrealDB we can define relationships between entities directly. We do not need to know about foreign keys and neither do we have to write logic about how to store them.
Since the PostgreSQL INSERT statement above would not work until the schema is defined, let's get to that part first.
PostgreSQL schema
We need to define the tables to insert up front in PostgreSQL that store the metadata about our e-commerce database.
-- Product details in an order CREATETABLE order_item( order_item_idINTPRIMARYKEY, order_id INT, product_id INT, quantity INT, price DECIMAL(10,2), FOREIGN KEY(order_id) REFERENCES customer_order(order_id), FOREIGN KEY (product_id) REFERENCES product(id) );
These tables aren't required in SurrealDB because the vertex->edge->vertex syntax inside a RELATE statement is all that is needed to link two records and store metadata. We will look at this statement in more detail in a moment.
RELATEcustomer:meriel->bought->product:iphoneSET// set metadata here...
You can of course define your tables and fields up front if you prefer.
The most common practice in SurrealQL is to begin with an entirely schemaless schema (the default), and then populate the schema with statements to firm it up and add assertions once you are sure exactly how it should look. That allows you to get the best of both worlds by quickly iterating when prototyping, followed by a strict schema to ensure consistent output.
Here are some examples of DEFINE FIELD statements that you might use.
DEFINEFIELDquantityONboughtTYPEint; DEFINEFIELDtotalONbought TYPEint ASSERT$value<=1000; // Can't order more than 1000 items DEFINEFIELDstatusONbought TYPE'Pending' | 'Delivered' | 'Cancelled'; // Must be one of these three values, nothing else
Populating the database
Let's imagine that we wanted to add a new field just before populating the database. In PostgreSQL, this would mean using ALTER TABLE. Once this is done, an INSERT can be used.
If we wanted to follow a stricter pattern by defining the field up front, we would use a DEFINE FIELD statement. Here is one way to do it.
DEFINEFIELDoptionsONproductTYPEoption<object>;
We could also refine the TYPE clause to ensure that the fields inside the optional object fit a certain pattern. These statements will ensure that the colours field of the optional object will only hold strings, and that the sizes field will only hold strings that are 'S' or 'M' or 'L' - and nothing else.
At their core, the SELECT statements in SurrealDB are similar to PostgreSQL.
PostgreSQL
SELECT * FROMproductwhereid=1;
SurrealQL
SELECT * FROMproduct:shirt;
The SELECT statement will fetch all required details from the product tables. In SurrealDB you can assign a unique id to each product. In case you do not assign it an id, it auto-assigns a unique ID to every record. In PostgreSQL, this can be achieved by using a uuid column and it has to be explicitly mentioned initially.
The RELATE statement
The RELATE statement can be used to generate graph edges between two records in the database. The graph edges stand for the relationship between two nodes and are standalone records themselves.
When a customer buys a product, SurrealDB relates the customer with the product using the bought relation. You can name your relationship whatever you feel fits right. It could also be called purchased or ordered.
As the statements show, a RELATE statement can be followed with all the needed metadata for the relation that binds one table to another.
Let's select all the products bought by a particular customer.
PostgreSQL
SELECTp.idASproduct_id, p.nameASproduct_name FROMproduct p JOINorder_itemoi ON p.id=oi.product_id JOINcustomer_orderco ON oi.order_id =co.order_id JOINcustomerc ON co.customer_id =c.customer_id WHEREc.name='Meriel' ORDERBYp.id;
SurrealQL
SELECT * FROMcustomer:meriel->bought;
You may have noticed pretty big difference in complexity between the two queries! One of the most powerful features in SurrealDB is the capability to relate records using graph connections and links. Instead of pulling data from multiple tables and merging that data together, SurrealDB allows you to select related records efficiently without needing to use JOINs.
Here's what you get when you fire off the above SurrealQL query.
If you study the queries you will realise that customers Meriel and Martin both bought shirts and an iPhone. We have a new customer Tobie who also buys a shirt. Your e-commerce system wants to recommend the products that other customers have bought to Tobie.
How would you do this using PostgreSQL?
To recommend products to Tobie based on what Meriel and Martin have bought, we need to first find out what products Meriel and Martin have purchased and then look for other customers who have purchased the same products.
This query first selects all the products bought by Meriel and Martin, then filters out the products already bought by Tobie. The resulting list includes the product_id and product_name of the items to be recommended to Tobie.
SELECTDISTINCT p.idASproduct_id, p.nameASproduct_name FROMproduct p JOINorder_itemoi ON p.id=oi.product_id JOINcustomer_orderco ON oi.order_id =co.order_id JOINcustomerc ON co.customer_id =c.customer_id WHEREc.nameIN ('Meriel', 'Martin') ANDp.id NOT IN ( SELECTp2.id FROMproduct p2 JOINorder_itemoi2 ON p2.id=oi2.product_id JOINcustomer_orderco2 ON oi2.order_id=co2.order_id JOINcustomerc2 ON co2.customer_id =c2.customer_id WHEREc2.name='Tobie' ) ORDER BYp.id;
Is your head spinning by now? In today's age, this sort of query would probably be offloaded to an LLM tool to get the job done.
In fact, that query could have been even shorter. That's because we can just go straight from the record itself using just product:shirt<-bought<-customer->bought->product.distinct() plus .* at the end to fetch all the fields. Here it is displayed over multiple lines to show just how readable this syntax is and how it follows a very human train of thought.
product:shirt// Start at product:shirt <-bought<-customer// Which customers was it bought by? ->bought->product// Now which products did they buy? .distinct() // Remove duplicates .*; // Fetch all the fields
And if you want the same structure as above that returns the output as the products field inside an object, just enclose it in braces and assign the output to the products field name.
Want to try some of these queries yourself? You can do that online by visiting the Surrealist Sandbox. Just paste these statements in to add the schema and data, and query away!
In this blog post, we saw how SurrealDB can make it easier for you to structure and query your database compared to PostgreSQL. With SurrealDB you can truly have the best of both databases while not compromising on security and flexibility. SurrealDB has a lot of features like real-time queries with highly efficient related data retrieval, advanced security permissions for multi-tenant access, and support for performant analytical workloads to offer. You can read more about them here.
Next Steps
If you haven't started with SurrealDB yet, you can get started by visiting the install page. Drop your questions on our Discord and don't forget to star us on GitHub.