{"id":2708,"date":"2024-06-17T05:28:41","date_gmt":"2024-06-17T11:28:41","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2708"},"modified":"2024-06-17T05:28:42","modified_gmt":"2024-06-17T11:28:42","slug":"introducing-drizzle","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/introducing-drizzle\/","title":{"rendered":"Introducing Drizzle"},"content":{"rendered":"\n<p>This is a post about an exciting new ORM tool (that&#8217;s &#8220;object relational mapper&#8221;) that is different than any ORM I&#8217;ve used before\u2014and I&#8217;ve used quite a few! Spoiler: it&#8217;s <a href=\"https:\/\/orm.drizzle.team\/\">Drizzle<\/a>. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wait, what&#8217;s an ORM?<\/h2>\n\n\n\n<p>Even if you&#8217;re using a non-relational database now (think MongoDB or Redis), sooner or later you&#8217;ll likely need a relational DB. <\/p>\n\n\n\n<p>Knowing SQL is an essential skill for any software engineer, but writing SQL directly can be tricky! The tooling is usually primitive, with only minimal auto-complete to guide you, and you invariably go through a process of running your query, correcting errors, and repeating until you get it right.<\/p>\n\n\n\n<p>ORMs try to help you with this process of crafting SQL. Typically, you tell the ORM about the shape of your DB and it exposes APIs to do typical things. If you have a <code>books<\/code> table in your DB, an ORM will give you an API for it where you can do stuff like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> longBooks = books.find({ <span class=\"hljs-attr\">pages<\/span>: { <span class=\"hljs-attr\">gt<\/span>: <span class=\"hljs-number\">500<\/span> } });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Behind the scenes, the SQL created might be something like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span> * <span class=\"hljs-keyword\">FROM<\/span> books <span class=\"hljs-keyword\">WHERE<\/span> pages &gt; <span class=\"hljs-number\">500<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That may not look like a massive simplification, but as the parameters get more complex or strung together, the SQL can get a bit mindbending. Not to mention the ORM keeps that code in a language you&#8217;re likely already using, like JavaScript.<\/p>\n\n\n\n<p>This ease of use may seem nice, but it can cause other problems. For example, you might struggle figuring out how to do non-trivial queries. And there are performance foot-guns, such as the infamous\u00a0<a href=\"https:\/\/planetscale.com\/blog\/what-is-n-1-query-problem-and-how-to-solve-it\">Select N + 1 problem<\/a>, which you might cause without realizing it due to the abstracted away syntax.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"why-drizzle-is-different\">Why Drizzle is Different<\/h2>\n\n\n\n<p>Drizzle takes a novel approach. Drizzle\u00a0<em>does<\/em>\u00a0provide you a traditional ORM querying API, like we saw above. But in addition to that, it\u00a0<em>also<\/em>\u00a0provides an API that is essentially a layer of typing on top of SQL itself. So rather than what we saw before, we might query our books table like this<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> longBooks = <span class=\"hljs-keyword\">await<\/span> db\n  .select()\n  .from(books)\n  .where(gt(books.pages, <span class=\"hljs-number\">500<\/span>));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It&#8217;s more lines, but it&#8217;s closer to actual SQL, which provides us some nice benefits: it&#8217;s easier to learn, more flexible, and avoids traditional ORM footguns.<\/p>\n\n\n\n<p>Let&#8217;s dive in and look closer. This post will take a brief overview of setting up Drizzle, and querying, and then do a deeper dive showing off some of its powerful abilities with this typed SQL querying API. The docs&nbsp;<a href=\"https:\/\/orm.drizzle.team\/docs\/overview\">are here<\/a>&nbsp;if you&#8217;d like to look closer at anything.<\/p>\n\n\n\n<p class=\"learn-more\">Using Drizzle in general, and some of the advanced things we&#8217;ll cover in this post requires a decent knowledge of SQL. If you&#8217;ve never, ever used SQL, you might struggle with a few of the things we discuss later on. That&#8217;s expected. Skim and jump over sections as needed. If nothing else, hopefully this post will motivate you to look at SQL.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"setting-up-the-schema\">Setting up the Schema<\/h2>\n\n\n\n<p>Drizzle can&#8217;t do much of anything if it doesn&#8217;t know about your database. There&#8217;s lots of utilities for showing Drizzle the structure (or schema) of your tables. We&#8217;ll take a very brief look, but a more complete example can be found&nbsp;<a href=\"https:\/\/github.com\/arackaf\/booklist\/blob\/master\/svelte-kit\/src\/data\/drizzle-schema.ts\">here<\/a>.<\/p>\n\n\n\n<p>Drizzle supports Postgres, MySQL, and SQLite. The ideas are the same either way, but we&#8217;ll be using MySQL.<\/p>\n\n\n\n<p>Let&#8217;s start to set up a table.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { int, json, mysqlTable, varchar } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-orm\/mysql-core\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> books = mysqlTable(<span class=\"hljs-string\">\"books\"<\/span>, {\n  <span class=\"hljs-attr\">id<\/span>: int(<span class=\"hljs-string\">\"id\"<\/span>).primaryKey().autoincrement(),\n  <span class=\"hljs-attr\">userId<\/span>: varchar(<span class=\"hljs-string\">\"userId\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">50<\/span> }).notNull(),\n  <span class=\"hljs-attr\">isbn<\/span>: varchar(<span class=\"hljs-string\">\"isbn\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">25<\/span> }),\n  <span class=\"hljs-attr\">pages<\/span>: int(<span class=\"hljs-string\">\"pages\"<\/span>),\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We tell Drizzle about our columns (we won\u2019t show all of them here), and their data types.<\/p>\n\n\n\n<p>Now we can run queries:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> db\n  .select()\n  .from(books)\n  .orderBy(desc(books.id))\n  .limit(<span class=\"hljs-number\">1<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This query returns an array of items which match the schema we provided Drizzle for this table.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"320\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-1-first-query.jpg?resize=1024%2C320&#038;ssl=1\" alt=\"First Query\" class=\"wp-image-2723\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-1-first-query.jpg?resize=1024%2C320&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-1-first-query.jpg?resize=300%2C94&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-1-first-query.jpg?resize=768%2C240&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-1-first-query.jpg?w=1426&amp;ssl=1 1426w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Alternatively, as expected, we can&nbsp;also narrow our select list.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> db\n  .select({ <span class=\"hljs-attr\">id<\/span>: books.id, <span class=\"hljs-attr\">isbn<\/span>: books.isbn })\n  .from(books)\n  .orderBy(desc(books.id))\n  .limit(<span class=\"hljs-number\">1<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note that the types of the columns match whatever we define in the schema. We won&#8217;t go over every possible column type (check the docs), but let&#8217;s briefly look at the JSON type:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> books = mysqlTable(<span class=\"hljs-string\">\"books\"<\/span>, {\n  <span class=\"hljs-attr\">id<\/span>: int(<span class=\"hljs-string\">\"id\"<\/span>).primaryKey().autoincrement(),\n  <span class=\"hljs-attr\">userId<\/span>: varchar(<span class=\"hljs-string\">\"userId\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">50<\/span> }).notNull(),\n  <span class=\"hljs-attr\">isbn<\/span>: varchar(<span class=\"hljs-string\">\"isbn\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">25<\/span> }),\n  <span class=\"hljs-attr\">pages<\/span>: int(<span class=\"hljs-string\">\"pages\"<\/span>),\n  <span class=\"hljs-attr\">authors<\/span>: json(<span class=\"hljs-string\">\"authors\"<\/span>),\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This adds an authors field to each book. But the type might not be what you want. Right now it&#8217;s <code>unknown<\/code>. This makes sense: JSON can have just about any structure. Fortunately, if you know your <code>json<\/code> column will have a predictable shape, you can specify it, like this:\u00a0\u00a0<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> books = mysqlTable(<span class=\"hljs-string\">\"books\"<\/span>, {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">id<\/span>: int(<span class=\"hljs-string\">\"id\"<\/span>).primaryKey().autoincrement(),\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">userId<\/span>: varchar(<span class=\"hljs-string\">\"userId\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">50<\/span> }).notNull(),\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">isbn<\/span>: varchar(<span class=\"hljs-string\">\"isbn\"<\/span>, { <span class=\"hljs-attr\">length<\/span>: <span class=\"hljs-number\">25<\/span> }),\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attr\">pages<\/span>: int(<span class=\"hljs-string\">\"pages\"<\/span>),\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-attr\">authors<\/span>: json(<span class=\"hljs-string\">\"authors\"<\/span>).$type&lt;string&#91;]&gt;(),\n<\/span><\/mark><span class='shcb-loc'><span>});\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And now, when we check, the&nbsp;<code>authors<\/code>&nbsp;property is of type&nbsp;<code>string[] | null<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"218\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?resize=1024%2C218&#038;ssl=1\" alt=\"Typed JSON\" class=\"wp-image-2726\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?resize=1024%2C218&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?resize=300%2C64&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?resize=768%2C163&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?resize=1536%2C327&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-2-typed-json.jpg?w=1804&amp;ssl=1 1804w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>If you were to mark the&nbsp;<code>authors<\/code>&nbsp;column as&nbsp;<code>notNull()<\/code>&nbsp;it would be typed as&nbsp;<code>string[]<\/code>. As you might expect, you can pass any type you&#8217;d like into the&nbsp;<code>$type<\/code>&nbsp;helper.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"query-whirlwind-tour\">Query Whirlwind Tour<\/h2>\n\n\n\n<p>Let&#8217;s run a non-trivial, but still basic query to see what Drizzle looks like in practice. Let&#8217;s say we&#8217;re looking to find some nice beach reading for the summer. We want to find books that belong to you (userId == &#8220;123&#8221;), and is either less than 150 pages, or was written by Stephan Jay Gould. We want the first ten, and we want them sort from most recently added to least recently added (the id key is auto-numbered, so we can sort on that for the same effect)<\/p>\n\n\n\n<p>In SQL we&#8217;d do something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span> *\n<span class=\"hljs-keyword\">FROM<\/span> books\n<span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span> <span class=\"hljs-keyword\">AND<\/span> (pages &lt; <span class=\"hljs-number\">150<\/span> <span class=\"hljs-keyword\">OR<\/span> <span class=\"hljs-keyword\">authors<\/span> <span class=\"hljs-keyword\">LIKE<\/span> <span class=\"hljs-string\">'%Stephen Jay Gould%'<\/span>)\n<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">id<\/span> <span class=\"hljs-keyword\">desc<\/span>\n<span class=\"hljs-keyword\">LIMIT<\/span> <span class=\"hljs-number\">10<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With Drizzle we&#8217;d write this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> db\n  .select()\n  .from(books)\n  .where(\n    and(\n      eq(books.userId, userId),\n      or(lt(books.pages, <span class=\"hljs-number\">150<\/span>), like(books.authors, <span class=\"hljs-string\">\"%Stephen Jay Gould%\"<\/span>))\n    )\n  )\n  .orderBy(desc(books.id))\n  .limit(<span class=\"hljs-number\">10<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Which works!<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">&#91;\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">1088<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Siry, Steven E\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"Greene: Revolutionary General (Military Profiles)\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"9781574889130\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">144<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">828<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Morton J. Horwitz\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"The Warren Court and the Pursuit of Justice\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"0809016257\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">144<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">506<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Stephen Jay Gould\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"Bully for Brontosaurus: Reflections in Natural History\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"039330857X\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">544<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">412<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Stephen Jay Gould\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"The Flamingo's Smile: Reflections in Natural History\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"0393303756\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">480<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">356<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Stephen Jay Gould\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"Hen's Teeth and Horse's Toes: Further Reflections in Natural History\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"0393311031\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">416<\/span>\n  },\n  {\n    <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-number\">319<\/span>,\n    <span class=\"hljs-attr\">\"userId\"<\/span>: <span class=\"hljs-string\">\"123\"<\/span>,\n    <span class=\"hljs-attr\">\"authors\"<\/span>: &#91;<span class=\"hljs-string\">\"Robert J. Schneller\"<\/span>],\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"Cushing: Civil War SEAL (Military Profiles)\"<\/span>,\n    <span class=\"hljs-attr\">\"isbn\"<\/span>: <span class=\"hljs-string\">\"1574886967\"<\/span>,\n    <span class=\"hljs-attr\">\"pages\"<\/span>: <span class=\"hljs-number\">128<\/span>\n  }\n]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The Drizzle version was actually a little bit longer. But we&#8217;re not optimizing for fewest possible lines of code. The Drizzle version is typed, with autocomplete to guide you toward a valid query, and TypeScript to warn you when you miss. The query is also a lot more composable. What do I mean by that?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"putting-queries-together\">Composability: Putting Queries Together<\/h2>\n\n\n\n<p>Let&#8217;s write something&nbsp;<em>slightly<\/em>&nbsp;more advanced and&nbsp;<em>slightly<\/em>&nbsp;more realistic. Let&#8217;s code up a function that takes any number of search filters, and puts together a query. Here&#8217;s what the filters look like<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">type<\/span> SearchPacket = Partial&lt;{\n  title: <span class=\"hljs-built_in\">string<\/span>;\n  author: <span class=\"hljs-built_in\">string<\/span>;\n  maxPages: <span class=\"hljs-built_in\">number<\/span>;\n  subjects?: <span class=\"hljs-built_in\">number<\/span>&#91;];\n}&gt;;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note the&nbsp;<a href=\"https:\/\/www.typescriptlang.org\/docs\/handbook\/utility-types.html#partialtype\"><code>Partial<\/code><\/a>&nbsp;type. We&#8217;re taking in any number of these filters\u2014possibly none of them. Whichever filters are passed, we want them to be additive; we want them combined with&nbsp;<code>and<\/code>. We&#8217;ve seen&nbsp;<code>and<\/code>&nbsp;already, and it can take the result of calls to&nbsp;<code>eq<\/code>,&nbsp;<code>lt<\/code>, and&nbsp;<a href=\"https:\/\/orm.drizzle.team\/docs\/operators\">lots of others<\/a>. We&#8217;ll need to create an array of all of these filters, and Drizzle gives us a parent type that can hold any of them:&nbsp;<code>SQLWrapper<\/code>.<\/p>\n\n\n\n<p>Let&#8217;s get started.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">searchBooks<\/span>(<span class=\"hljs-params\">args: SearchPacket<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchConditions: SQLWrapper&#91;] = &#91;];\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We&#8217;ve got our array of filters. Now let&#8217;s start filling it up.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">searchBooks<\/span>(<span class=\"hljs-params\">args: SearchPacket<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchConditions: SQLWrapper&#91;] = &#91;];\n  <span class=\"hljs-keyword\">if<\/span> (args.title) {\n    searchConditions.push(like(books.title, <span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.title}<\/span>%`<\/span>));\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Nothing new, yet. This is the same filter we saw before with authors.<\/p>\n\n\n\n<p>Speaking of authors, let&#8217;s add that query next. But let&#8217;s make the author check a little more realistic. It&#8217;s not a <code>varchar<\/code> column, it holds JSON values, which themselves are strings of arrays. MySQL gives us a way to search JSON: the&nbsp;<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/json-search-functions.html#operator_json-inline-path\"><code>-&gt;&gt;<\/code>&nbsp;operator<\/a>. This takes a JSON column, and evaluates a&nbsp;<em>path<\/em>&nbsp;on it. So if you had objects in there, you&#8217;d pass string paths to get properties out. We just have an array of strings, so our path is&nbsp;<code>$<\/code>, which is the actual values in the array. And the string comparrisons when we&#8217;re filtering on JSON columns like this is no longer case insensitive, so we&#8217;ll want to use the LOWER function in MySQL.<\/p>\n\n\n\n<p>Typically, with traditional ORM&#8217;s you&#8217;d scramble to the docs to look for an equivalent to the&nbsp;<code>-&gt;&gt;<\/code>&nbsp;operator, as well as the LOWER function. Drizzle does something better, and gives us a nice escape hatch to just write SQL directly in situations like this. Let&#8217;s implement our authors filter.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">searchBooks<\/span>(<span class=\"hljs-params\">args: SearchPacket<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchConditions: SQLWrapper&#91;] = &#91;];\n  <span class=\"hljs-keyword\">if<\/span> (args.title) {\n    searchConditions.push(like(books.title, <span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.title}<\/span>%`<\/span>));\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.author) {\n    searchConditions.push(\n      sql<span class=\"hljs-string\">`LOWER(<span class=\"hljs-subst\">${books.authors}<\/span>-&gt;&gt;\"$\") LIKE <span class=\"hljs-subst\">${<span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.author.toLowerCase()}<\/span>%`<\/span>}<\/span>`<\/span>\n    );\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note the&nbsp;<code>sql<\/code>&nbsp;tagged template literal. It lets us put arbitrary SQL in for one-off operations that may not be implemented in the ORM. Before moving on, let&#8217;s take a quick peak at the SQL generated by this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"sql\"<\/span>: <span class=\"hljs-string\">\"select `id`, `userId`, `authors`, `title`, `isbn`, `pages` from `books` where (`books`.`userId` = ? and LOWER(`books`.`authors`-&gt;&gt;\\\"$\\\") LIKE ?) order by `books`.`id` desc limit ?\"<\/span>,\n  <span class=\"hljs-attr\">\"params\"<\/span>: &#91;<span class=\"hljs-string\">\"123\"<\/span>, <span class=\"hljs-string\">\"%gould%\"<\/span>, <span class=\"hljs-number\">10<\/span>]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s zoom in on the authors piece. What we entered as&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">sql<span class=\"hljs-string\">`LOWER(<span class=\"hljs-subst\">${books.authors}<\/span>-&gt;&gt;\"$\") LIKE <span class=\"hljs-subst\">${<span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.author.toLowerCase()}<\/span>%`<\/span>}<\/span>`<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>&#8230; gets transformed as:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\">LOWER(`books`.`authors`-&gt;&gt;\"$\") LIKE ?<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Our search term was parameterized; however, Drizzle was smart enough to&nbsp;<em>not<\/em>&nbsp;parameterize our column. I&#8217;m continuously impressed by small details like this. The <code>maxPages<\/code> piece is the same as before<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span> (args.maxPages) {\n  searchConditions.push(lte(books.pages, args.maxPages));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Nothing new or interesting. Now let&#8217;s look at the&nbsp;<code>subjects<\/code>&nbsp;filter. We can pass in an array of subject ids, and we want to filter books that have that subject. The relationship between books and subjects is stored in a separate table,&nbsp;<code>booksSubjects<\/code>. This table simply has rows with an id, a book id, and a subject id (and also the userId for that book, to make other queries easier).<\/p>\n\n\n\n<p>So if book 12 has subject 34, there&#8217;ll be a row with bookId of 12, and subjectId of 34.<\/p>\n\n\n\n<p>In SQL when we want to see if a given row&nbsp;<em>exists<\/em>&nbsp;in some table, we use the&nbsp;<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/exists-and-not-exists-subqueries.html\">exists<\/a>&nbsp;keyword, and Drizzle has an&nbsp;<code>exists<\/code>&nbsp;function for this very purpose. Let&#8217;s move on with our function<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">searchBooks<\/span>(<span class=\"hljs-params\">args: SearchPacket<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchConditions: SQLWrapper&#91;] = &#91;];\n  <span class=\"hljs-keyword\">if<\/span> (args.title) {\n    searchConditions.push(like(books.title, <span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.title}<\/span>%`<\/span>));\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.author) {\n    searchConditions.push(\n      sql<span class=\"hljs-string\">`LOWER(<span class=\"hljs-subst\">${books.authors}<\/span>-&gt;&gt;\"$\") LIKE <span class=\"hljs-subst\">${<span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.author.toLowerCase()}<\/span>%`<\/span>}<\/span>`<\/span>,\n    );\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.maxPages) {\n    searchConditions.push(lte(books.pages, args.maxPages));\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.subjects?.length) {\n    searchConditions.push(\n      exists(\n        db\n          .select({ _: sql<span class=\"hljs-string\">`1`<\/span> })\n          .from(booksSubjects)\n          .where(\n            and(\n              eq(books.id, booksSubjects.book),\n              inArray(booksSubjects.subject, args.subjects),\n            ),\n          ),\n      ),\n    );\n  }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We can pass an&nbsp;<code>exists()<\/code>&nbsp;call right into our list of filters, just like with real SQL. This bit:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">_: sql<span class=\"hljs-string\">`1`<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>&#8230; is curious, but that&#8217;s just us saying\u00a0<code>SELECT 1<\/code>\u00a0which is a common way of putting something into a SELECT list, even though we&#8217;re not pulling back any data; we&#8217;re just checking for existence. Lastly, the\u00a0<code>inArray<\/code>\u00a0Drizzle helper is how we generate an IN query. Here&#8217;s what the generated SQL looks like for this subjects query:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">select<\/span> <span class=\"hljs-string\">`id`<\/span>, <span class=\"hljs-string\">`userId`<\/span>, <span class=\"hljs-string\">`authors`<\/span>, <span class=\"hljs-string\">`title`<\/span>, <span class=\"hljs-string\">`isbn`<\/span>, <span class=\"hljs-string\">`pages`<\/span>\n<span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">`books`<\/span>\n<span class=\"hljs-keyword\">where<\/span> (<span class=\"hljs-string\">`books`<\/span>.<span class=\"hljs-string\">`userId`<\/span> = ? <span class=\"hljs-keyword\">and<\/span> <span class=\"hljs-keyword\">exists<\/span> (<span class=\"hljs-keyword\">select<\/span> <span class=\"hljs-number\">1<\/span>\n                                        <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">`books_subjects`<\/span>\n                                        <span class=\"hljs-keyword\">where<\/span> (<span class=\"hljs-string\">`books`<\/span>.<span class=\"hljs-string\">`id`<\/span> = <span class=\"hljs-string\">`books_subjects`<\/span>.<span class=\"hljs-string\">`book`<\/span> <span class=\"hljs-keyword\">and<\/span>\n                                               <span class=\"hljs-string\">`books_subjects`<\/span>.<span class=\"hljs-string\">`subject`<\/span> <span class=\"hljs-keyword\">in<\/span> (?, ?))))\n<span class=\"hljs-keyword\">order<\/span> <span class=\"hljs-keyword\">by<\/span> <span class=\"hljs-string\">`books`<\/span>.<span class=\"hljs-string\">`id`<\/span> <span class=\"hljs-keyword\">desc<\/span>\n<span class=\"hljs-keyword\">limit<\/span> ?<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That was our last filter. Now we can pipe our filters in to execute the query we put together.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"TypeScript\" data-shcb-language-slug=\"typescript\"><span><code class=\"hljs language-typescript\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">searchBooks<\/span>(<span class=\"hljs-params\">args: SearchPacket<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> searchConditions: SQLWrapper&#91;] = &#91;];\n  <span class=\"hljs-keyword\">if<\/span> (args.title) {\n    searchConditions.push(like(books.title, <span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.title}<\/span>%`<\/span>));\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.author) {\n    searchConditions.push(\n      sql<span class=\"hljs-string\">`LOWER(<span class=\"hljs-subst\">${books.authors}<\/span>-&gt;&gt;\"$\") LIKE <span class=\"hljs-subst\">${<span class=\"hljs-string\">`%<span class=\"hljs-subst\">${args.author.toLowerCase()}<\/span>%`<\/span>}<\/span>`<\/span>\n    );\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.maxPages) {\n    searchConditions.push(lte(books.pages, args.maxPages));\n  }\n  <span class=\"hljs-keyword\">if<\/span> (args.subjects?.length) {\n    searchConditions.push(\n      exists(\n        db\n          .select({ _: sql<span class=\"hljs-string\">`1`<\/span> })\n          .from(booksSubjects)\n          .where(\n            and(\n              eq(books.id, booksSubjects.book),\n              inArray(booksSubjects.subject, args.subjects)\n            )\n          )\n      )\n    );\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> db\n    .select()\n    .from(books)\n    .where(and(eq(books.userId, userId), ...searchConditions))\n    .orderBy(desc(books.id))\n    .limit(<span class=\"hljs-number\">10<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">TypeScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">typescript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The ability to treat SQL queries as typed function calls that can be combined arbitratily is what makes Drizzle shine.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"digging-deeper\">Digging deeper<\/h2>\n\n\n\n<p>We could end the post here, but let&#8217;s go further and see how Drizzle handles something fairly complex. You might never need (or want to) write queries like this. My purpose in including this section is to show that you <em>can<\/em>, if you ever need to.<\/p>\n\n\n\n<p>With that out of the way, let&#8217;s write a query to get aggregate info about our books. We want our most and least popular subject(s), and how many books we have with those subjects. We also want to know any unused subjects, as well as that same info about tags (which we haven&#8217;t talked about). And also the total number of books we have overall. This data might be displayed in a screen like this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"564\" height=\"650\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-3-aggregate-screen.jpg?resize=564%2C650&#038;ssl=1\" alt=\"aggregate screen\" class=\"wp-image-2727\" style=\"width:311px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-3-aggregate-screen.jpg?w=564&amp;ssl=1 564w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-3-aggregate-screen.jpg?resize=260%2C300&amp;ssl=1 260w\" sizes=\"auto, (max-width: 564px) 100vw, 564px\" \/><figcaption class=\"wp-element-caption\">Screenshot<\/figcaption><\/figure>\n\n\n\n<p>To keep this section manageable we&#8217;ll just get the book counts and the most and least subjects. The other pieces are variations on that theme. You can <a href=\"https:\/\/github.com\/arackaf\/booklist\/blob\/master\/svelte-kit\/src\/data\/user-summary.ts\">see the finished product&nbsp;here<\/a>.<\/p>\n\n\n\n<p>Let&#8217;s look at some of the SQL for this and how to write it with Drizzle.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"number-of-books-per-subject\">Number of books per subject<\/h3>\n\n\n\n<p>In SQL we can group things together with GROUP BY.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span>\n    subject,\n    <span class=\"hljs-keyword\">count<\/span>(*)\n<span class=\"hljs-keyword\">FROM<\/span> books_subjects\n<span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> subject<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now our SELECT list, rather than pulling items from a table, is now pulling from a (conceptual) lookup table. We (conceptually) have a bunch of buckets stored by subject id. So we can select those subject id&#8217;s, as well as aggregate info from the buckets themselves, which we do with the&nbsp;<code>count(*)<\/code>. This selects each subject, and the number of books under that subject.<\/p>\n\n\n\n<p>And it works:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"696\" height=\"242\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-4-group-by.jpg?resize=696%2C242&#038;ssl=1\" alt=\"Group by\" class=\"wp-image-2728\" style=\"width:455px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-4-group-by.jpg?w=696&amp;ssl=1 696w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-4-group-by.jpg?resize=300%2C104&amp;ssl=1 300w\" sizes=\"auto, (max-width: 696px) 100vw, 696px\" \/><\/figure>\n\n\n\n<p>But we want the most, and least popular subjects. SQL also has what are called window functions. We can, on the fly, sort these buckets in some order, and then ask questions about the data, sorted in that way. We basically want the subject(s) with the highest, or lowest number of books, including ties. It turns out&nbsp;<a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/window-function-descriptions.html#function_rank\">RANK<\/a>&nbsp;is exactly what we want. Let&#8217;s see how this works<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span>\n    subject,\n    <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>,\n    <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">DESC<\/span>) MaxSubject,\n    <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">ASC<\/span>) MinSubject\n<span class=\"hljs-keyword\">FROM<\/span> books_subjects\n<span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span>\n<span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> subject<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We ask for the rank of each row, when the whole result set is sorted in whatever way we describe.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"165\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank.jpg?resize=1024%2C165&#038;ssl=1\" alt=\"rank\n\" class=\"wp-image-2729\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank.jpg?resize=1024%2C165&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank.jpg?resize=300%2C48&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank.jpg?resize=768%2C124&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank.jpg?w=1228&amp;ssl=1 1228w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Subjects 79, 137 and 150 all have a <code>minSubject<\/code> rank of 1, which means they are the least used subject, which makes sense since there&#8217;s only one book with that subject.<\/p>\n\n\n\n<p>It&#8217;s a little mind bendy at first, so don&#8217;t worry if this looks a little weird. The point is to show how well Drizzle can simplify SQL for us, not to be a deep dive into SQL, so let&#8217;s move on.<\/p>\n\n\n\n<p>We want the subjects with a <code>MaxSubject<\/code> of 1, or a <code>MinSubject<\/code> of 1. We can&#8217;t use <code>WHERE<\/code> for this, at least not directly. The solution in SQL is to turn this query into a virtual table, and query&nbsp;<em>that<\/em>. It looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span>\n    t.subject <span class=\"hljs-keyword\">id<\/span>,\n    <span class=\"hljs-keyword\">CASE<\/span> <span class=\"hljs-keyword\">WHEN<\/span> t.MinSubject = <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">THEN<\/span> <span class=\"hljs-string\">'MinSubject'<\/span> <span class=\"hljs-keyword\">ELSE<\/span> <span class=\"hljs-string\">'MaxSubject'<\/span> <span class=\"hljs-keyword\">END<\/span> <span class=\"hljs-keyword\">as<\/span> label,\n    t.count\n<span class=\"hljs-keyword\">FROM<\/span> (\n    <span class=\"hljs-keyword\">SELECT<\/span>\n        subject,\n        <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>,\n        <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">DESC<\/span>) MaxSubject,\n        <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">ASC<\/span>) MinSubject\n    <span class=\"hljs-keyword\">FROM<\/span> books_subjects\n    <span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span>\n    <span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> subject\n) t\n<span class=\"hljs-keyword\">WHERE<\/span> t.MaxSubject = <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">OR<\/span> t.MinSubject = <span class=\"hljs-number\">1<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And it works.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"682\" height=\"194\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank-2.jpg?resize=682%2C194&#038;ssl=1\" alt=\"rank\" class=\"wp-image-2730\" style=\"width:493px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank-2.jpg?w=682&amp;ssl=1 682w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-5-rank-2.jpg?resize=300%2C85&amp;ssl=1 300w\" sizes=\"auto, (max-width: 682px) 100vw, 682px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"moving-this-along\">Moving this along<\/h3>\n\n\n\n<p>We won&#8217;t show tags, since it&#8217;s basically identical except we hit a <code>books_tags<\/code> table, instead of <code>books_subjects<\/code>. We also won&#8217;t show unused subjects (or tags), which is also very similar, except we use a <code>NOT EXISTS<\/code> query.<\/p>\n\n\n\n<p>The query to get the total number of books looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>\n<span class=\"hljs-keyword\">FROM<\/span> books\n<span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let&#8217;s add some columns&nbsp;to get it in the same structure as our subjects queries:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span>\n    <span class=\"hljs-number\">0<\/span> <span class=\"hljs-keyword\">id<\/span>,\n    <span class=\"hljs-string\">'Books Count'<\/span> <span class=\"hljs-keyword\">as<\/span> label,\n    <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>\n<span class=\"hljs-keyword\">FROM<\/span> books\n<span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now, since these queries return the same structure, let&#8217;s combine them into one big query. We use UNION for this.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\"><span class=\"hljs-keyword\">SELECT<\/span> *\n<span class=\"hljs-keyword\">FROM<\/span> (\n    <span class=\"hljs-keyword\">SELECT<\/span>\n        t.subject <span class=\"hljs-keyword\">id<\/span>,\n        <span class=\"hljs-keyword\">CASE<\/span> <span class=\"hljs-keyword\">WHEN<\/span> t.MinSubject = <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">THEN<\/span> <span class=\"hljs-string\">'MinSubject'<\/span> <span class=\"hljs-keyword\">ELSE<\/span> <span class=\"hljs-string\">'MaxSubject'<\/span> <span class=\"hljs-keyword\">END<\/span> <span class=\"hljs-keyword\">as<\/span> label,\n        t.count\n    <span class=\"hljs-keyword\">FROM<\/span> (\n        <span class=\"hljs-keyword\">SELECT<\/span>\n            subject,\n            <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>,\n            <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">DESC<\/span>) MaxSubject,\n            <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">ASC<\/span>) MinSubject\n        <span class=\"hljs-keyword\">FROM<\/span> books_subjects\n        <span class=\"hljs-keyword\">GROUP<\/span> <span class=\"hljs-keyword\">BY<\/span> subject\n    ) t\n    <span class=\"hljs-keyword\">WHERE<\/span> t.MaxSubject = <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">OR<\/span> t.MinSubject = <span class=\"hljs-number\">1<\/span>\n) subjects\n<span class=\"hljs-keyword\">UNION<\/span>\n    <span class=\"hljs-keyword\">SELECT<\/span>\n        <span class=\"hljs-number\">0<\/span> <span class=\"hljs-keyword\">id<\/span>,\n        <span class=\"hljs-string\">'Books Count'<\/span> <span class=\"hljs-keyword\">as<\/span> label,\n        <span class=\"hljs-keyword\">count<\/span>(*) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-keyword\">count<\/span>\n    <span class=\"hljs-keyword\">FROM<\/span> books\n    <span class=\"hljs-keyword\">WHERE<\/span> userId = <span class=\"hljs-string\">'123'<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And it works! Phew! <\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"708\" height=\"292\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-6-union-query.jpg?resize=708%2C292&#038;ssl=1\" alt=\"union query\" class=\"wp-image-2731\" style=\"width:383px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-6-union-query.jpg?w=708&amp;ssl=1 708w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/img-6-union-query.jpg?resize=300%2C124&amp;ssl=1 300w\" sizes=\"auto, (max-width: 708px) 100vw, 708px\" \/><\/figure>\n\n\n\n<p>But this is gross to write manually, and even grosser to maintain. There&#8217;s a lot of pieces here and there&#8217;s no (good) way to break this apart and manage separately. SQL is ultimately text, and you can, of course, generate these various pieces of text with different functions in your code, and then concatenate them together.<\/p>\n\n\n\n<p>But that&#8217;s fraught with difficulty too. It&#8217;s easy to get small details wrong when you&#8217;re pasting strings of code together. And believe it or not, this query is much simpler than much of what I&#8217;ve seen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-drizzle-way\">The Drizzle Way<\/h2>\n\n\n\n<p>Let&#8217;s see what this looks like in Drizzle. Remember that initial query to get each subject, with its count, and rank? Here it is in Drizzle<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> subjectCountRank = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span>\n  db\n    .select({\n      <span class=\"hljs-attr\">subject<\/span>: booksSubjects.subject,\n      <span class=\"hljs-attr\">count<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`COUNT(*)`<\/span>.as(<span class=\"hljs-string\">\"count\"<\/span>),\n      <span class=\"hljs-attr\">rankMin<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`RANK() OVER (ORDER BY COUNT(*) ASC)`<\/span>.as(<span class=\"hljs-string\">\"rankMin\"<\/span>),\n      <span class=\"hljs-attr\">rankMax<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`RANK() OVER (ORDER BY COUNT(*) DESC)`<\/span>.as(<span class=\"hljs-string\">\"rankMax\"<\/span>),\n    })\n    .from(booksSubjects)\n    .where(eq(booksSubjects.userId, userId))\n    .groupBy(booksSubjects.subject)\n    .as(<span class=\"hljs-string\">\"t\"<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Drizzle supports grouping, and it even has an&nbsp;<code>as<\/code>&nbsp;function to alias a query, and enable it to be&nbsp;<em>queried from<\/em>. Let&#8217;s do that next.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> subjectsQuery = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> subQuery = subjectCountRank();\n\n  <span class=\"hljs-keyword\">return<\/span> db\n    .select({\n      <span class=\"hljs-attr\">label<\/span>:\n        sql&lt;string&gt;<span class=\"hljs-string\">`CASE WHEN t.rankMin = 1 THEN 'MIN Subjects' ELSE 'MAX Subjects' END`<\/span>.as(\n          <span class=\"hljs-string\">\"label\"<\/span>\n        ),\n      <span class=\"hljs-attr\">count<\/span>: subQuery.count,\n      <span class=\"hljs-attr\">id<\/span>: subQuery.subject,\n    })\n    .from(subQuery)\n    .where(or(eq(subQuery.rankMin, <span class=\"hljs-number\">1<\/span>), eq(subQuery.rankMax, <span class=\"hljs-number\">1<\/span>)));\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We stuck our query to get the ranks in a function, and then we just called that function, and queried from its result. SQL is feeling a lot more like normal coding!<\/p>\n\n\n\n<p>The query for the total book count is simple enough.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">db\n  .select({ <span class=\"hljs-attr\">label<\/span>: sql&lt;string&gt;<span class=\"hljs-string\">`'All books'`<\/span>, <span class=\"hljs-attr\">count<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`COUNT(*)`<\/span>, <span class=\"hljs-attr\">id<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`0`<\/span> })\n  .from(books)\n  .where(eq(books.userId, userId)),<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hopefully we won&#8217;t be too surprised to learn that Drizzle has a\u00a0<code>union<\/code>\u00a0function, to union queries together. Let&#8217;s see it all together:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> dataQuery = union(\n  db\n    .select({\n      <span class=\"hljs-attr\">label<\/span>: sql&lt;string&gt;<span class=\"hljs-string\">`'All books'`<\/span>,\n      <span class=\"hljs-attr\">count<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`COUNT(*)`<\/span>,\n      <span class=\"hljs-attr\">id<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`0`<\/span>,\n    })\n    .from(books)\n    .where(eq(books.userId, userId)),\n  subjectsQuery()\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Which generates this SQL for us:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"SQL (Structured Query Language)\" data-shcb-language-slug=\"sql\"><span><code class=\"hljs language-sql\">(<span class=\"hljs-keyword\">select<\/span> <span class=\"hljs-string\">'All books'<\/span>, <span class=\"hljs-keyword\">COUNT<\/span>(*), <span class=\"hljs-number\">0<\/span> <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">`books`<\/span> <span class=\"hljs-keyword\">where<\/span> <span class=\"hljs-string\">`books`<\/span>.<span class=\"hljs-string\">`userId`<\/span> = ?)\n<span class=\"hljs-keyword\">union<\/span>\n(<span class=\"hljs-keyword\">select<\/span> <span class=\"hljs-keyword\">CASE<\/span> <span class=\"hljs-keyword\">WHEN<\/span> t.rankMin = <span class=\"hljs-number\">1<\/span> <span class=\"hljs-keyword\">THEN<\/span> <span class=\"hljs-string\">'MIN Subjects'<\/span> <span class=\"hljs-keyword\">ELSE<\/span> <span class=\"hljs-string\">'MAX Subjects'<\/span> <span class=\"hljs-keyword\">END<\/span> <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-string\">`label`<\/span>, <span class=\"hljs-string\">`count`<\/span>, <span class=\"hljs-string\">`subject`<\/span>\n <span class=\"hljs-keyword\">from<\/span> (<span class=\"hljs-keyword\">select<\/span> <span class=\"hljs-string\">`subject`<\/span>,\n              <span class=\"hljs-keyword\">COUNT<\/span>(*)                             <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-string\">`count`<\/span>,\n              <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">COUNT<\/span>(*) <span class=\"hljs-keyword\">ASC<\/span>)  <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-string\">`rankMin`<\/span>,\n              <span class=\"hljs-keyword\">RANK<\/span>() <span class=\"hljs-keyword\">OVER<\/span> (<span class=\"hljs-keyword\">ORDER<\/span> <span class=\"hljs-keyword\">BY<\/span> <span class=\"hljs-keyword\">COUNT<\/span>(*) <span class=\"hljs-keyword\">DESC<\/span>) <span class=\"hljs-keyword\">as<\/span> <span class=\"hljs-string\">`rankMax`<\/span>\n       <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">`books_subjects`<\/span>\n       <span class=\"hljs-keyword\">where<\/span> <span class=\"hljs-string\">`books_subjects`<\/span>.<span class=\"hljs-string\">`userId`<\/span> = ?\n       <span class=\"hljs-keyword\">group<\/span> <span class=\"hljs-keyword\">by<\/span> <span class=\"hljs-string\">`books_subjects`<\/span>.<span class=\"hljs-string\">`subject`<\/span>) <span class=\"hljs-string\">`t`<\/span>\n <span class=\"hljs-keyword\">where<\/span> (<span class=\"hljs-string\">`rankMin`<\/span> = ? <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-string\">`rankMax`<\/span> = ?))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">SQL (Structured Query Language)<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">sql<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Basically the same thing we did before, but with a few more parens, plus some <code>userId<\/code> filtering I left off for clarity.<\/p>\n\n\n\n<p>I left off the tags queries, and the unused subjects\/tags queries, but if you&#8217;re curious what they look like, the code is&nbsp;<a href=\"https:\/\/github.com\/arackaf\/booklist\/blob\/master\/svelte-kit\/src\/data\/user-summary.ts\">all here<\/a>&nbsp;and the final union looks like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> dataQuery = union(\n  db\n    .select({\n      <span class=\"hljs-attr\">label<\/span>: sql&lt;string&gt;<span class=\"hljs-string\">`'All books'`<\/span>,\n      <span class=\"hljs-attr\">count<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`COUNT(*)`<\/span>,\n      <span class=\"hljs-attr\">id<\/span>: sql&lt;number&gt;<span class=\"hljs-string\">`0`<\/span>,\n    })\n    .from(books)\n    .where(eq(books.userId, userId)),\n  subjectsQuery(),\n  unusedSubjectsQuery(),\n  tagsQuery(),\n  unusedTagsQuery()\n);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Just more function calls thrown into the union.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"flexibility\">Flexibility<\/h3>\n\n\n\n<p>Some of you might wince seeing that many large queries all <code>union<\/code>&#8216;d together. Those queries are actually run one after the other on the MySQL box. But, for this project it&#8217;s a small amount of data and there&#8217;s not multiple round trips over the network to do it. Our MySQL engine executes those queries one after the other.<\/p>\n\n\n\n<p>But let&#8217;s say you decide you&#8217;re better off breaking that union apart, and sending N queries, with each piece, and putting it all together in application code. These queries are&nbsp;<em>already<\/em>&nbsp;separate function calls. It would be fairly easy to remove those calls from the union, and instead invoke them in isolation (and then modify your application code).<\/p>\n\n\n\n<p>This kind of flexibility is what I love the most about Drizzle. Refactoring large, complex stored procedure has always been a pain with SQL. When you code it through Drizzle, it becomes much more like refactoring a typed programming language like TypeScript or C#.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"debugging-queries\">Debugging Queries<\/h2>\n\n\n\n<p>Before we wrap up, let&#8217;s take a look at how easily Drizzle let&#8217;s you debug your queries. Let&#8217;s say the query from earlier didn&#8217;t return what we expected, and we want to see the actual SQL being run. We can do that by\u00a0<strong>removing<\/strong>\u00a0the\u00a0<code>await<\/code>\u00a0from the query, and then calling\u00a0<code>toSQL<\/code>\u00a0on the result.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> { and, desc, eq, like, lt, or } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"drizzle-orm\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> result = db\n  .select()\n  .from(books)\n  .where(\n    and(\n      eq(books.userId, userId),\n      or(lt(books.pages, <span class=\"hljs-number\">150<\/span>), like(books.authors, <span class=\"hljs-string\">\"%Stephen Jay Gould%\"<\/span>))\n    )\n  )\n  .orderBy(desc(books.id))\n  .limit(<span class=\"hljs-number\">10<\/span>);\n\n<span class=\"hljs-built_in\">console<\/span>.log(result.toSQL());<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This displays the following:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"sql\"<\/span>: <span class=\"hljs-string\">\"select `id`, `userId`, `authors`, `title`, `isbn`, `pages` from `books` where (`books`.`userId` = ? and (`books`.`pages` &lt; ? or `books`.`authors` like ?)) order by `books`.`id` desc limit ?\"<\/span>,\n  <span class=\"hljs-attr\">\"params\"<\/span>: &#91;<span class=\"hljs-string\">\"123\"<\/span>, <span class=\"hljs-number\">150<\/span>, <span class=\"hljs-string\">\"%Stephen Jay Gould%\"<\/span>, <span class=\"hljs-number\">10<\/span>]\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>result.toSQL()<\/code>&nbsp;returned an object, with a&nbsp;<code>sql<\/code>&nbsp;field with our query, and a&nbsp;<code>params<\/code>&nbsp;field with the parameters. As any ORM would, Drizzle parameterized our query, so fields with invalid characters wouldn&#8217;t break anything. You can now run this query directly against your database to see what went wrong.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"wrapping-up\">Wrapping Up<\/h2>\n\n\n\n<p>I hope you&#8217;ve enjoyed this introduction to Drizzle. If you&#8217;re not afraid of a little SQL, it can make your life a lot easier.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Drizzle is a new ORM tool that blends traditional ORM querying with a typed SQL API. Drizzle supports various databases (Postgres, MySQL, SQLite) and aims to simplify SQL crafting while avoiding common ORM pitfalls.<\/p>\n","protected":false},"author":21,"featured_media":2725,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[174,188],"class_list":["post-2708","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-data","tag-drizzle"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/drizzle-thumb.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2708","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2708"}],"version-history":[{"count":21,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2708\/revisions"}],"predecessor-version":[{"id":2756,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2708\/revisions\/2756"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2725"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2708"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2708"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2708"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}