<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>DataSQRL Blog</title>
        <link>https://www.datasqrl.com/blog</link>
        <description>DataSQRL Blog</description>
        <lastBuildDate>Mon, 05 Aug 2024 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[How Acorn Agent Unlocks the Value of Your Business Data with GenAI]]></title>
            <link>https://www.datasqrl.com/blog/acorn-agent-financial-use-cases</link>
            <guid>https://www.datasqrl.com/blog/acorn-agent-financial-use-cases</guid>
            <pubDate>Mon, 05 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Have you wondered how companies can leverage their data through GenAI technologies for business growth? With the open-source Acorn Agent Framework, businesses can now extract actionable insights, enhance user engagement, and catalyze new revenue streams by using Generative AI technologies on their data. Acorn Agent enables Large Language Models (LLMs) to connect seamlessly with a company’s data via API calls, providing natural language access to complex sources of data. Let’s explore how Acorn Agent can unlock the value of your data, using financial services as an example.]]></description>
            <content:encoded><![CDATA[<p>Have you wondered how companies can leverage their data through GenAI technologies for business growth? With the open-source <a href="https://github.com/DataSQRL/Acorn/" target="_blank" rel="noopener noreferrer"><strong>Acorn Agent Framework</strong></a>, businesses can now extract actionable insights, enhance user engagement, and catalyze new revenue streams by using Generative AI technologies on their data. Acorn Agent enables Large Language Models (LLMs) to connect seamlessly with a company’s data via API calls, providing natural language access to complex sources of data. Let’s explore how Acorn Agent can unlock the value of your data, using financial services as an example.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="unlocking-business-potential-with-acorn-agent">Unlocking Business Potential with Acorn Agent<a href="#unlocking-business-potential-with-acorn-agent" class="hash-link" aria-label="Direct link to Unlocking Business Potential with Acorn Agent" title="Direct link to Unlocking Business Potential with Acorn Agent">​</a></h2><p>Imagine a financial institution, such as a bank, which has extensive data on their users and their transactions. How can this data be used to drive additional business? Here are some examples that unlock the value of customer data through LLM Agents powered by Acorn Agent.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-1-developing-a-financial-assistant-chatbot">Step 1: Developing a Financial Assistant ChatBot<a href="#step-1-developing-a-financial-assistant-chatbot" class="hash-link" aria-label="Direct link to Step 1: Developing a Financial Assistant ChatBot" title="Direct link to Step 1: Developing a Financial Assistant ChatBot">​</a></h3><p>A Financial Assistant ChatBot that can retrieve user transactions and credit card history when responding to user questions and extract valuable insights about user spending. Having access to the user data, the Financial Assistant can perform tasks like:</p><ul><li><strong>Transaction Analysis</strong>: Which transactions accounted for most of my spending this month?</li><li><strong>Expense Breakdown</strong>: Show me a breakdown of my expenses per category for this month.</li><li><strong>Trend Identification</strong>: Highlight categories where my expenses increased from last month.</li></ul><img loading="lazy" src="/img/blog/acorn_financial_use_case.png" alt="Acorn Agent Financial Use Case >" width="100%" class="img_ev3q"><p>By displaying information in natural language, tables and visual charts, this chatbot enables users to retrieve the information they need. This improves customer satisfaction and reduces customer support costs. And as users interact with the chatbot for insights and analytics, they use their bank accounts more, fostering loyalty and increasing user engagement.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-2-elevating-to-a-financial-advisor">Step 2: Elevating to a Financial Advisor<a href="#step-2-elevating-to-a-financial-advisor" class="hash-link" aria-label="Direct link to Step 2: Elevating to a Financial Advisor" title="Direct link to Step 2: Elevating to a Financial Advisor">​</a></h3><p>Taking it a step further, the next layer of value comes from creating a comprehensive Financial Advisor. While the Financial Assistant focuses on past behavior analysis, the Financial Advisor delivers forward-looking recommendations for future actions and potential savings. For instance, it can answer:</p><ul><li><strong>Reward Tracking</strong>: How many rewards have I earned with this credit card so far?</li><li><strong>Product Review</strong>: Based on my transactions over the past six months, which credit cards would have given me the most rewards?</li><li><strong>Product Recommendation</strong>: What financial products or services would I benefit from and why?</li></ul><p>By accessing user transactions and credit card data through the institution’s API, the Financial Advisor can calculate total rewards and suggest better alternatives. It presents this information in natural language, tables, or even charts, making it easy for users to understand and act upon.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-acorn-agent-works">How Acorn Agent works<a href="#how-acorn-agent-works" class="hash-link" aria-label="Direct link to How Acorn Agent works" title="Direct link to How Acorn Agent works">​</a></h2><p>The <a href="https://github.com/DataSQRL/Acorn/" target="_blank" rel="noopener noreferrer">Acorn Agent Framework</a> makes it simple to implement such LLM applications by safely integrating with the bank's existing data APIs. Acorn Agent creates a layer of APIs on top of the bank's internal data, which can take the form of REST or GraphQL APIs. These APIs provide all the necessary data for an agent to respond to user queries. The API endpoints are described in an LLM tool definition, which is a JSON file that outlines the required data and its type. Fortunately, most APIs already come with reference documentation, making it possible to automate their transformation into a JSON Schema.</p><p>With the Acorn Agent Framework, an LLM Agent application is built. Developers can choose from a wide range of LLMs available through the platform, including OpenAI models and open-source models on AWS Bedrock. Once the model is selected, the API tool definitions are loaded into the application, followed by the creation of a user-friendly interface – the access point for end-users.</p><p>The user interacts with the Agent as they would with a personal assistant. When the Agent receives a user query, the LLM behind it determines which API tools to call to gather the necessary data. The Acorn Agent framework handles the LLM tool calls, retrieves the API responses, and returns them to the LLM. This enables the Agent to use the additional information to provide a more accurate response to the user's query. By facilitating this workflow, we can deliver real value through user-centric applications.</p><img loading="lazy" src="/img/generic/acorn_overview.png" alt="Acorn Agent Overview" width="100%" class="img_ev3q"><p>The above diagram shows the typical data flow for an Application built with Acorn Agent:</p><ol><li>User inputs a question about their data (“Show me my most expensive transaction from last month”) into the Acorn Agent - enabled User Interface</li><li>The Acorn Agent - enabled Server Application sends this question to the LLM. The LLM decides that it can not answer this question based on its training data, so it formulates a Function Call (function <code>transactions</code> with arguments <code>{fromDate: 2024-05-09T00:00:00, toDate: 2024-06-09T00:00:00}</code>)</li><li>The Acorn Agent - enabled Server Application recognizes this LLM function call and executes this Request against the API by injecting a secure context that authenticates the user. The important safety feature defends against injection attacks. The API Response is retrieved.</li><li>The Acorn Agent - enabled Server Application sends the initial user question and the API response to the LLM, which is then enabled to answer the initial question.</li><li>The LLM sends the Final Response to the Server, which, in turn surfaces it to the user on the User Interface</li></ol><p>Acorn Agent creates a secure “sandbox” around the LLM and guarantees that the LLM can only access user-authorized data. It guarantees safe and secure access to data and gives the LLM access to the most up-to-date information in real-time, which reduces hallucinations and results in the most accurate responses for users.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="other-potential-business-applications-of-acorn-agent">Other potential Business Applications of Acorn Agent<a href="#other-potential-business-applications-of-acorn-agent" class="hash-link" aria-label="Direct link to Other potential Business Applications of Acorn Agent" title="Direct link to Other potential Business Applications of Acorn Agent">​</a></h2><p>Acorn Agent can be the backbone of a multitude of applications across various industries. Let's delve into a few scenarios illustrating how businesses can harness this technology to unlock additional value.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="enhanced-customer-support">Enhanced Customer Support<a href="#enhanced-customer-support" class="hash-link" aria-label="Direct link to Enhanced Customer Support" title="Direct link to Enhanced Customer Support">​</a></h3><p>Imagine a telecommunications company that leverages Acorn Agent to revolutionize their customer support operations. Instead of relying on traditional scripted responses, the company's customer service agents could interact with a powerful, AI-driven chat interface that accesses customer records, identifies issues, and suggests resolutions in real time.</p><p>For instance, a customer might inquire, "Why was my bill higher this month?" With Acorn Agent, the AI can quickly access the billing API, analyze the statement, and provide a detailed explanation, such as additional data usage or recent plan changes. This not only speeds up resolution processes, enhances customer satisfaction by providing precise and immediate responses and reduces the cost of customer support.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="streamlined-internal-operations">Streamlined Internal Operations<a href="#streamlined-internal-operations" class="hash-link" aria-label="Direct link to Streamlined Internal Operations" title="Direct link to Streamlined Internal Operations">​</a></h3><p>In industries such as logistics and supply chain management, the ability to quickly access and update records can be a game-changer. Using Acorn Agent, employees can chat with their internal databases to track shipments, update inventory levels, and manage deliveries.</p><p>For example, an operations manager might ask, "What’s the current status of order #12345?" The AI, via the Acorn Agent interface, can query the order management system and provide immediate updates with relevant details, such as location, expected delivery time, and any issues encountered. This capability significantly reduces manual record-keeping time and allows staff to concentrate on high-value tasks that drive business growth.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="dynamic-hr-management">Dynamic HR Management<a href="#dynamic-hr-management" class="hash-link" aria-label="Direct link to Dynamic HR Management" title="Direct link to Dynamic HR Management">​</a></h3><p>Human Resource departments can leverage Acorn Agent to streamline numerous processes, from recruitment to employee management. An HR manager might ask, "Who are the top-performing employees this quarter?" The AI would retrieve performance data from the company's HR management system and generate a detailed report with actionable insights.</p><p>When integrated with scheduling APIs, employees could interact with the AI to manage requests like leave, check holiday entitlements, or learn about upcoming training sessions. Queries such as "Do I have any remaining vacation days?" or "When is the next training session on workplace safety?" can be instantly resolved, simplifying HR processes, making HR managers more efficient and boosting employee engagement.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="retail-and-customer-experience">Retail and Customer Experience<a href="#retail-and-customer-experience" class="hash-link" aria-label="Direct link to Retail and Customer Experience" title="Direct link to Retail and Customer Experience">​</a></h3><p>Retail businesses can utilize Acorn Agent to enhance customer interactions and improve the shopping experience. For instance, a customer might ask, "Do you have this product in stock in size M?" By connecting to the inventory management API, the AI provides real-time availability, suggests alternatives if the item is out of stock, and guides the user through the purchase process seamlessly.</p><p>Order tracking in retail also stands to benefit. Customers can inquire, "What’s the status of my order #987654?" and the AI can fetch real-time tracking information from the logistics API, offering immediate updates on order status, expected delivery times, and any potential delays, increasing customer satisfaction and engagement.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="driving-business-growth-with-acorn-agent">Driving Business Growth with Acorn Agent<a href="#driving-business-growth-with-acorn-agent" class="hash-link" aria-label="Direct link to Driving Business Growth with Acorn Agent" title="Direct link to Driving Business Growth with Acorn Agent">​</a></h3><p>The Acorn Agent Framework’s powerful capability to transform data into actionable insights opens avenues for driving new business growth by:</p><ul><li>Enhancing user engagement and satisfaction with interactive and insightful tools.</li><li>Providing data-driven recommendations for cross-selling and upselling products.</li><li>Personalizing user experiences, ultimately leading to higher customer satisfaction and loyalty.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2><p>In conclusion, the <a href="https://github.com/DataSQRL/Acorn/" target="_blank" rel="noopener noreferrer">Acorn Agent Framework</a> represents a transformative leap in how businesses can utilize their data. By integrating LLMs with APIs, companies can unlock unprecedented value, drive user engagement, and unveil new business opportunities. Explore the power of <a href="https://github.com/DataSQRL/Acorn/" target="_blank" rel="noopener noreferrer">Acorn Agent</a> and set your business on a path to innovation and growth.</p>]]></content:encoded>
            <category>acorn</category>
            <category>GenAI</category>
        </item>
        <item>
            <title><![CDATA[Acorn Agent Framework for Building LLM Applications]]></title>
            <link>https://www.datasqrl.com/blog/acorn-agent-announcement</link>
            <guid>https://www.datasqrl.com/blog/acorn-agent-announcement</guid>
            <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Large-Language Models (LLMs) provide a natural language interface capable of understanding user intent and delivering highly specific responses. The recent addition of "tooling" as a primary feature of LLMs has enabled them to retrieve information on-demand and trigger actions. This makes LLMs a viable natural language interface for various applications, including dashboards, ERPs, CRMs, BI systems, HRMS, SCM, and even customer-facing mobile and web applications. The Acorn Agent framework offers the infrastructure to build such LLM-powered applications by instrumenting LLMs with custom tooling in a safe, secure, and efficient manner. The Acorn Agent framework is open-source under the Apache 2.0 license.]]></description>
            <content:encoded><![CDATA[<p>Large-Language Models (LLMs) provide a natural language interface capable of understanding user intent and delivering highly specific responses. The recent addition of "tooling" as a primary feature of LLMs has enabled them to retrieve information on-demand and trigger actions. This makes LLMs a viable natural language interface for various applications, including dashboards, ERPs, CRMs, BI systems, HRMS, SCM, and even customer-facing mobile and web applications. The Acorn Agent framework offers the infrastructure to build such LLM-powered applications by instrumenting LLMs with custom tooling in a safe, secure, and efficient manner. The Acorn Agent framework is open-source under the Apache 2.0 license.</p><img loading="lazy" src="/img/generic/acorn_agent_mascot.png" alt="Acorn Agent Mascot >" width="30%" class="img_ev3q"><h2 class="anchor anchorWithStickyNavbar_LWe7" id="background">Background<a href="#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background">​</a></h2><p>Large-Language Models are neural networks that process input text and incrementally generate intelligent responses. Advances in the size, topology, and training of LLMs increased their performance as conversational interfaces to near-human levels. To date, LLMs have been confined to chat and search applications, where they are trained on extensive corpora and augmented with query-specific information through methods such as RAG, FLARE, or text search.</p><p>The recent addition of "tooling" as a primary feature has broadened the applicability of LLMs. "Tooling" is a set of function definitions provided to the LLM either in-prompt or through training. The LLM can invoke these functions to retrieve information on demand (e.g., looking up the current weather) or trigger an external action (e.g., placing an order). This enables LLMs to interact with external systems and information outside the immediate user context. The LLM can invoke APIs, run database queries, execute computations, trigger UI updates, and more.</p><p>Tooling makes it possible to build conversational interfaces with LLMs for user facing applications that enable the user to interact naturally with the application through text or voice.
However, integrating LLMs with custom tooling poses several challenges:</p><ul><li><strong>Safety</strong>: LLMs are non-deterministic and prone to hallucinations, requiring careful validation and comprehensive observability to build safe and predictable applications.</li><li><strong>Security</strong>: LLMs are susceptible to injection attacks, necessitating safeguards to build secure applications that do not leak user information or secrets.</li><li><strong>Performance</strong>: LLMs are expensive to invoke and highly sensitive to prompt variation, requiring efficient function management to reduce context tokens and improve performance.</li><li><strong>Efficiency</strong>: The implementations of tools in chatbots or agents have to be written from scratch by the developers, needing to create the function definitions for the LLM, but also the function execution against an API or a Database.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="acorn-agent-framework">Acorn Agent Framework<a href="#acorn-agent-framework" class="hash-link" aria-label="Direct link to Acorn Agent Framework" title="Direct link to Acorn Agent Framework">​</a></h2><img loading="lazy" src="/img/generic/acorn_overview.png" alt="Acorn Agent Overview" width="100%" class="img_ev3q"><p>Acorn Agent is a framework for building LLM-powered applications through tooling. It provides the necessary infrastructure to instrument LLMs with custom tools, addressing challenges related to safety, security, performance, and efficiency:</p><ul><li><strong>Safety</strong>: The framework validates function calls and supports auto-retry of invalid function invocations. It enables quick iteration of function definitions to improve accuracy and performance.</li><li><strong>Security</strong>: The framework sandboxes function calls through a defined "context," which includes sensitive function arguments (e.g., userid, sessionid, tokens) that are handled by the framework and not exposed to the LLM call stack.</li><li><strong>Performance</strong>: The framework gives developers full control over the context window construction to optimize cost and performance.</li><li><strong>Efficiency</strong>: The framework provides abstraction layers for managing tooling across many popular LLMs through a standard interface which reduces boilerplate code and custom instrumentation logic. while giving developers full control over context window constructionAt its core, Acorn Agent is a repository of tools that instruments and executes tools for LLMs using a JSON configuration format for semantic annotations, execution logic, and security context.</li></ul><p>Additionally, Acorn Agent facilitates seamless integration of APIs, databases, libraries, and UI frontends as tooling for LLMs. Acorn Agent supports three types of tools:</p><ul><li><strong>API</strong>: Invokes an external system through an API to retrieve information or trigger an action. The framework supports GraphQL, REST, and JDBC, giving the LLM access to internal microservices, external web services, databases, search engines, ERP systems, and more.</li><li><strong>Local</strong>: Invokes a local function to execute a library method or computation. This enables the LLM to execute mathematical or other computations where accuracy and determinism are important.</li><li><strong>Client</strong>: Forwards the tool call to the client or frontend to trigger a UI update, implemented as a function callback in JavaScript. This allows the LLM to customize the presentation of information to the user.</li></ul><p>The Acorn Agent framework is open-source under the Apache 2.0 license. You can view the full source code, download it, and contribute to the project on <a href="https://github.com/DataSQRL/Acorn/" target="_blank" rel="noopener noreferrer">Github</a>.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="example-llm-application">Example LLM Application<a href="#example-llm-application" class="hash-link" aria-label="Direct link to Example LLM Application" title="Direct link to Example LLM Application">​</a></h2><p>To illustrate how the Acorn Agent framework works, consider building an LLM application providing a natural language interface for an online garden store. This application uses the same GraphQL API that the web application uses to place and retrieve customer orders. Additionally, it implements functions for unit conversions and React components on the frontend to display order status.
With Acorn Agent, we can register all of these as tools within the framework, configure an LLM (such as OpenAI’s GPT, Llama3 on AWS Bedrock, or Google Gemini), and set up our system prompt for a friendly shopping assistant.
When a user issues a request, the Acorn Agent framework manages the tools for the LLM.</p><img loading="lazy" src="/img/generic/acorn_call_diagram.png" alt="Acorn Agent Example Instrumentation" width="100%" class="img_ev3q"><p>For example, suppose the user requests: "Order me fertilizer for 3 plots of lawn: 10x20 ft, 50x15 ft, and 30x35 ft. The same fertilizer I ordered last time."
Acorn Agent injects the relevant tool definitions into the context and hands it to the LLM. The LLM processes the request and calls tools as follows:</p><ul><li><strong>Look up the last orders</strong> for the fertilizer product ID and weight. Acorn Agent augments secure information from the user session, invokes the GraphQL API to retrieve the user’s last orders, and returns the information to the LLM. The LLM identifies which order was for fertilizer as well as the associated product id and weight.</li><li><strong>Convert the given measurements</strong> to total square footage and compute the number of bags needed for the fertilizer product based on the retrieved weight. Acorn Agent executes that tool by invoking a local function that implements the math and conversion. It then returns the number of bags to the LLM.</li><li><strong>Place the order</strong> for the computed number of fertilizer bags and retrieved product ID. Acorn Agent invokes the GraphQL API to place the order within the secure sandbox of the user session. The order details are returned to the LLM.</li><li><strong>Update the UI</strong> with the order information. Acorn Agent forwards that client function call with additional context to the UI to update the React component.</li></ul><p>Acorn Agent handles all tool validations, sandboxing, invocations, and result propagations, allowing developers to focus on building tools and optimizing the context window.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="using-acorn-agent-framework">Using Acorn Agent Framework<a href="#using-acorn-agent-framework" class="hash-link" aria-label="Direct link to Using Acorn Agent Framework" title="Direct link to Using Acorn Agent Framework">​</a></h2><p>To experiment with Acorn Agent as a natural language interface for an existing API or database, you need only write tools and a configuration file—no coding required. Check out the <a href="https://github.com/DataSQRL/Acorn/tree/main/examples" target="_blank" rel="noopener noreferrer">examples</a> for public APIs to get started.
To build an LLM microservice or web application, you can include the Acorn Agent framework as a dependency in your Java, Scala, or Kotlin project. See the <a href="https://github.com/DataSQRL/Acorn/tree/main/java" target="_blank" rel="noopener noreferrer">documentation</a> for all the details. As an example, refer to the <a href="https://github.com/DataSQRL/Acorn/tree/main/java/acorn-spring" target="_blank" rel="noopener noreferrer">Spring Boot application</a> using Acorn Agent to implement a ChatBot.
If you have any questions, feedback, or would like to contribute, please join us on <a href="https://join.slack.com/t/datasqrlcommunity/shared_invite/zt-2l3rl1g6o-im6YXYCqU7t55CNaHqz_Kg" target="_blank" rel="noopener noreferrer">Slack</a>.
Currently, the Acorn Agent framework is limited to the JVM. We aim to support other programming environments soon.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2><p>Building natural language interfaces for user facing applications requires instrumenting LLMs with custom tooling. The open-source Acorn Agent framework provides the infrastructure to manage custom tooling for LLMs and ensures that application is safe, secure, and efficient.</p>]]></content:encoded>
            <category>launch</category>
        </item>
        <item>
            <title><![CDATA[Canz Joins DataSQRL]]></title>
            <link>https://www.datasqrl.com/blog/canz-joins-datasqrl</link>
            <guid>https://www.datasqrl.com/blog/canz-joins-datasqrl</guid>
            <pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Unfortunately, my semi-retirement has come to an end.]]></description>
            <content:encoded><![CDATA[<p>Unfortunately, my semi-retirement has come to an end.</p><p>It’s been an incredible ten months waking up and not having to be anywhere other than school dropoff.&nbsp; However, all good things must turn into even better things!</p><p>I’m happy to announce that I’ve joined my old friends from DataStax, <a href="https://www.linkedin.com/in/matthiasbroecheler/" target="_blank" rel="noopener noreferrer">Matthias Broecheler</a>, and <a href="https://www.linkedin.com/in/daniel-henneberger-8721a5155/" target="_blank" rel="noopener noreferrer">Daniel Henneberger</a>, as co-founder of <a href="https://www.datasqrl.com" target="_blank" rel="noopener noreferrer">DataSQRL</a>.</p><p>And what a time to join! Our self-funded venture is already profitable, with a growing customer base.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-does-datasqrl-do">What does <a href="https://www.datasqrl.com" target="_blank" rel="noopener noreferrer">DataSQRL</a> do?<a href="#what-does-datasqrl-do" class="hash-link" aria-label="Direct link to what-does-datasqrl-do" title="Direct link to what-does-datasqrl-do">​</a></h2><p>Your “<strong><em>Operating System for Data</em></strong>” builds data products in <strong><em>minutes</em></strong>.&nbsp; Gone are months wasted attempting to integrate multiple vendor technologies for a data pipeline.</p><p>DataSQRL abstracts and automates the building of data pipelines through our SQL interface, which we’ve affectionately dubbed SQRL.&nbsp; Yes, pronounced “Squirrel,” just like the fuzzy little guys running around your yard.</p><p>Have you struggled with Apache Kafka?&nbsp; Flink?&nbsp; Streaming?&nbsp; Data in motion? Integrating your Artificial Intelligence into your systems?&nbsp; Anything “Real-Time?” Struggle no more, and <a href="https://www.datasqrl.com/docs/getting-started/getting-started/" target="_blank" rel="noopener noreferrer">get started here!</a></p><p>We provide an open-source tool that can help you along with some professional services.</p><p>Now that <em>that</em> is out of the way, onto more interesting things…</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-did-i-do-over-these-last-ten-months-youre-wondering">What did I do over these last ten months? You’re wondering…<a href="#what-did-i-do-over-these-last-ten-months-youre-wondering" class="hash-link" aria-label="Direct link to What did I do over these last ten months? You’re wondering…" title="Direct link to What did I do over these last ten months? You’re wondering…">​</a></h2><p>What <em>didn’t</em> I do?&nbsp; I…</p><ul><li>Spent a ton of time watching my autistic son make incredible progress</li><li>Helped produce a shark documentary, <a href="https://www.imdb.com/title/tt15475848/?ref_=tt_mv_close" target="_blank" rel="noopener noreferrer">Unmasking Monsters Below</a>, with my friend, Kevin Lapsley</li><li>Traveled to more than 30 different places, both nationally and internationally (pictures below)</li><li>Read 41 books</li><li>Wrote 200 pages in my journal</li><li>Started writing a new book</li><li>Started writing a new screenplay</li><li>Continued working on my photography skills</li><li>Watched SO MUCH college football (We are!)</li><li>Cooked my wife breakfast (almost) every day</li><li>Made a ton of new recipes</li><li>Worked out 4-6 days per week</li><li>Mentored several young professionals to find their way</li><li>Caught up with some old friends</li><li>Served as an advisor to ten AI-based startups</li><li>And attended a few concerts…</li></ul><p>More on all of this as the months roll on…</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="whats-next-for-me-and-datasqrl">What’s next for me and DataSQRL?<a href="#whats-next-for-me-and-datasqrl" class="hash-link" aria-label="Direct link to What’s next for me and DataSQRL?" title="Direct link to What’s next for me and DataSQRL?">​</a></h2><p>If you’re attending <a href="https://datadaytexas.com/" target="_blank" rel="noopener noreferrer">Data Days</a> in Austin, TX, next week, check out Matthias’ presentation.</p><p>Otherwise, make sure you follow us <a href="https://www.linkedin.com/company/datasqrl/" target="_blank" rel="noopener noreferrer">here on LinkedIn</a>.</p><p>I promise that some of my upcoming posts will cover what I did over the last ten months.&nbsp; You know me… However, here are some highlights in pictures…</p><p><strong><em>A 3x Silicon Valley Unicorn veteran with IPO experience, award-winning screenwriter, and 2006 Time Magazine “Person of the Year,” Canz is often mistaken for Joe Rogan while walking down the street.&nbsp; He can be found on <a href="https://www.linkedin.com/in/canz/" target="_blank" rel="noopener noreferrer">LinkedIn</a>, <a href="https://www.imdb.com/name/nm3376659/?ref_=fn_al_nm_1" target="_blank" rel="noopener noreferrer">IMDB</a>, and helping people how to pronounce “Conshohocken.”</em></strong></p><p>#autism #journaling #imdb #joerogan #movies #metallica #concerts</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="picture-highlights">Picture Highlights<a href="#picture-highlights" class="hash-link" aria-label="Direct link to Picture Highlights" title="Direct link to Picture Highlights">​</a></h2><img loading="lazy" src="/img/blog/canz-joins/img1.jpg" alt="Highlights |" width="100%" class="img_ev3q"><p>Innescron, County Sligo, Ireland</p><img loading="lazy" src="/img/blog/canz-joins/img2.jpg" alt="Highlights |" width="100%" class="img_ev3q"><p>Mystic, CT</p><img loading="lazy" src="/img/blog/canz-joins/img3.jpg" alt="Highlights |" width="100%" class="img_ev3q"><p>Innescron, County Sligo, Ireland</p><img loading="lazy" src="/img/blog/canz-joins/img4.jpg" alt="Highlights |" width="100%" class="img_ev3q">Newport, RI<img loading="lazy" src="/img/blog/canz-joins/img5.jpeg" alt="Highlights |" width="100%" class="img_ev3q">Metlife Stadium, NJ - Metallica Concert<img loading="lazy" src="/img/blog/canz-joins/img6.jpg" alt="Highlights |" width="100%" class="img_ev3q">Napa Valley, CA]]></content:encoded>
            <category>launch</category>
        </item>
        <item>
            <title><![CDATA[Personalized AI Search with Vector Embeddings for Semantic Profiles]]></title>
            <link>https://www.datasqrl.com/blog/personalized-ai-search</link>
            <guid>https://www.datasqrl.com/blog/personalized-ai-search</guid>
            <pubDate>Mon, 20 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[A common problem in search is ordering large result sets. Consider a user searching for “jacket” on an e-commerce platform. How do we order the large number of results to show the most relevant products first? In other words, what kind of jackets is the user looking for? Suit jackets, sport jackets, winter jackets?]]></description>
            <content:encoded><![CDATA[<p>A common problem in search is ordering large result sets. Consider a user searching for “jacket” on an e-commerce platform. How do we order the large number of results to show the most relevant products first? In other words, what kind of jackets is the user looking for? Suit jackets, sport jackets, winter jackets?</p><p>Often, we have the context to infer what kind of jacket a user is looking for based on their interactions on the site. For example, if a user has men’s running shoes in their shopping cart, they are likely looking for men’s sports jackets when they search for “jacket”.</p><p>At least to a human that seems pretty obvious. Yet, Amazon will return a somewhat random assortment of jackets in this scenario as shown in the screenshot below.</p><img loading="lazy" src="/img/blog/ai_personalized_search_screenshot.png" alt="Amazon search results for `jacket` |" width="100%" class="img_ev3q"><p>To humans the semantic association between “running shoes” and “sport jackets” is natural, but for machines making such associations has been a challenge.
With recent advances in large-language models (LLMs) computers can now compute semantic similarities with high accuracy.</p><p>We are going to use LLMs to compute the semantic context of past user interactions via vector embeddings, aggregate them into a semantic profile, and then use the semantic profile to order search results by their semantic similarity to a user’s profile.</p><p>In other words, we are going to rank search results by their semantic similarity to the things a user has been browsing. That gives us the context we are missing when the user enters a search query.</p><p>In this article, you will learn how to build a personalized shopping search with semantic vector embeddings step-by-step. You can apply the techniques in this article to any kind of search where a user can browse and search a collection of items: event search, knowledge bases, content search, etc.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-1-setup">Step 1: Setup<a href="#step-1-setup" class="hash-link" aria-label="Direct link to Step 1: Setup" title="Direct link to Step 1: Setup">​</a></h2><p>First, we need to find some realistic product data we can use for our personalized search. We’ll use a recent web scrape of the Flipkart product catalog from <a href="https://www.kaggle.com/datasets/aaditshukla/flipkart-fasion-products-dataset/" target="_blank" rel="noopener noreferrer">Kaggle</a>. Flipkart is India’s largest e-commerce site. We removed incomplete and duplicate records from the dataset.</p><img loading="lazy" src="/img/blog/ai_personalized_search_robot.png" alt="AI Shopping Assistant >" width="40%" class="img_ev3q"><p>Second, we need a vector embedding model. A vector embedding model maps a piece of text to a high-dimensional vector that preserves the semantic meaning of the text in the sense that two pieces of text that are similar map to vectors that are close to each other in space.</p><p>The embedding model we are using for this tutorial is a quantized version of the <a href="https://www.sbert.net/docs/pretrained_models.html" target="_blank" rel="noopener noreferrer">all-MiniLM-L6-v2</a> pre-trained model. This is an off-the-shelf model for sentence embedding trained on a large corpus. The model is small and fast while delivering good performance. “Quantized” means that the model has been transformed to run efficiently on CPUs.</p><p>Download <a href="https://drive.google.com/file/d/1a0oc4By3W0QCdjvKObbjqW_9QfHZQNmz/view" target="_blank" rel="noopener noreferrer">this zip archive</a> for the data and vector model you need to follow the steps in this tutorial. It also contains all the code we’ll be writing.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-2-api--architecture">Step 2: API &amp; Architecture<a href="#step-2-api--architecture" class="hash-link" aria-label="Direct link to Step 2: API &amp; Architecture" title="Direct link to Step 2: API &amp; Architecture">​</a></h2><p>We are going to implement the personalized search as a microservice that exposes a GraphQL API. The API has one query endpoint for the personalized search and one mutation endpoint to record the products a user has browsed. The GraphQL schema for our API is shown below:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">searchapi.graphqls</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">ProductSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">Products</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Products</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">brand</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">average_rating</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Float</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">sub_category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Mutation</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">ProductView</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">view</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token atom-input class-name">ProductViewInput</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">ProductViewed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">input</span><span class="token plain"> </span><span class="token atom-input class-name">ProductViewInput</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">productid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">ProductViewed</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">_source_time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We use PostgreSQL to implement the search and store the semantic user profiles. PostgreSQL is a relational database that supports efficient full-text search and vector data types.</p><p>We use Apache Kafka to capture users' product views so that we can process them asynchronously to compute the aggregated semantic user profiles. Kafka is a stream platform to efficiently store and process data streams.</p><p>We use Apache Flink to ingest the product data, compute vector embeddings, and compute the semantic user profiles. Flink is a stream processor that reliably processes data from streams with exactly-once guarantees.</p><p>We use these 3 data systems for a reliable, flexible, and scalable architecture. PostgreSQL manages data at rest, Kafka manages data in motion, and Flink processes the data. The architecture diagram shows how the data moves through those systems.</p><img loading="lazy" src="/img/blog/ai_personalized_search_architecture.svg" alt="Microservice architecture for Personalized AI Search >" width="60%" class="img_ev3q"><ol><li>The <code>ProductView</code> mutation endpoint gets invoked when a user views a product on the website.</li><li>The event gets recorded in Kafka</li><li>Flink processes the <code>ProductView</code> events and aggregates them into semantic user profiles (more on that below).</li><li>Flink also ingests product data from the external product catalog, so we have up-to-date product information to search.</li><li>Flink writes the semantic user profiles and product information to PostgreSQL.</li><li>The <code>ProductSearch</code> query endpoint gets invoked when a user searches, which is translated to an SQL query against PostgreSQL, and the results are returned to the user.</li></ol><p>We could have implemented the entire search microservice with just PostgreSQL and a server. That’s a simpler architecture but has several downsides:</p><ol><li>We have to implement a lot of data processing and failure handling logic in the server to make it work reliably under load.</li><li>Computing vector embedding and aggregating them synchronously can lead to long latencies.</li><li>A spike in traffic to our shopping site would result in a high server load and lots of inserts to PostgreSQL which would degrade search performance.</li></ol><p>Using an event-driven architecture as shown above allows us to process the product views and product updates asynchronously, which results in a more robust, reliable, and performant system.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-3-implementation">Step 3: Implementation<a href="#step-3-implementation" class="hash-link" aria-label="Direct link to Step 3: Implementation" title="Direct link to Step 3: Implementation">​</a></h2><p>The big downside of our event-driven architecture is that it’s a lot of work to implement all of those components and make them work efficiently together. Luckily, there is a tool that makes this a lot easier: DataSQRL.</p><p>DataSQRL is an open-source compiler for event-driven microservices. With DataSQRL we only have to implement the processing logic of our search microservice and DataSQRL compiles the entire event-driven architecture for us.</p><p>Here is how that works. We implement our personalized search engine in a file called <code>aisearch.sqrl</code> using SQL. DataSQRL uses an SQL dialect that adds some syntax and features for stream processing. Let’s look at each of the four parts of the script in more detail.</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">aisearch.sqrl</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- ### PART 1: Imports</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> externaldata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Products</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import product information from the catalog</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> searchapi</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ProductView</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Ingest ProductView events from the API</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import some functions we are going to need for data processing</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">textsearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- ### PART 2: Process product data</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Compute a semantic vector embedding for each product</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Products</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">semantics :</span><span class="token operator">=</span><span class="token plain"> onnxEmbed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'\n'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/build/embedding/model_quantized.onnx'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Convert the stream of product updates to a state table with the most recent product information</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Products :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> Products </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> _ingest_time </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- ### PART 3: Compute semantic profiles from product views</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Join in the semantic vector embedding for the product a user viewed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ProductViewVector :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">semantics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_source_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> ProductView v TEMPORAL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Products p </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">productid </span><span class="token operator">=</span><span class="token plain"> p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Aggregate all those vector embeddings into a semantic profile for each user</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">UserProfile :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> center</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">semantics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> semanticContext </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> ProductViewVector </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- ### PART 4: Personalized Search</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Retrieve the products that match the query and order them by semantic similarity to the user's profile</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ProductSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@query</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> Products </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> textsearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> p</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> UserProfile </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> userid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> u</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">coalesce</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cosineSimilarity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">p</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">semantics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">semanticContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">0.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Let’s go over the 4 parts of the implementation:</p><ol><li>We import the product data from an external data system. For this tutorial, the product data is imported from a file (included in the archive), but DataSQRL supports multiple data sources. We also import <code>ProductView</code> events from the API that are created when a user invokes the corresponding mutation endpoint. Lastly, we import a few functions that we’ll use in our script.</li><li>We preprocess the <code>Products</code> data by computing a vector embedding for the title and description of each product using the LLM. Since we are importing a stream of product data, because products may change over time, we deduplicate the stream to get the most recent version for each product.</li><li>We join the <code>ProductView</code> events against the <code>Products</code> state table to look up the vector embedding for the product a user viewed. We use a <a href="/blog/temporal-join/">temporal join</a> so that we look up the associated vector at the time of the event. Then we aggregate all the <code>ProductViewVectors</code> by <code>userid</code> to create the semantic <code>UserProfile</code> as the centroid of all product vectors the user has visited.</li><li>To answer a search query, we retrieve the matching products and the vector that represents the semantic profile of a user. We order the results by their semantic similarity to the semantic user profile by computing the cosine similarity between the respective vectors.</li></ol><h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-4-running-our-personalized-search">Step 4: Running Our Personalized Search<a href="#step-4-running-our-personalized-search" class="hash-link" aria-label="Direct link to Step 4: Running Our Personalized Search" title="Direct link to Step 4: Running Our Personalized Search">​</a></h2><p>That’s all we have to implement. Let’s run our personalized search and see how it works in practice.</p><p>First, we compile the SQRL script to a microservice with the command:</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run --rm -v </span><span class="token environment constant" style="color:rgb(189, 147, 249)">$PWD</span><span class="token plain">:/build datasqrl/cmd compile aisearch.sqrl searchapi.graphqls --mnt </span><span class="token environment constant" style="color:rgb(189, 147, 249)">$PWD</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Make sure you run the command in the folder where you unzipped the archive containing the data.</p><p>Second, we run the compiled microservice with docker:</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cd build/deploy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose up</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>You can access the API in your browser through GraphiQL, which is an IDE for GraphQL APIs, by opening <a href="http://localhost:8888/graphiql/" target="_blank" rel="noopener noreferrer">http://localhost:8888/graphiql/</a>.</p><p>Copy-paste the following GraphQL query into the left-hand side and hit the run button to execute the search query for “jackets”. You’ll get a list of jackets that are offered on Flipkart.</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">Query</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property-query">ProductSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"jacket"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"myuserid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">title</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">description</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">brand</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>The search results are not personalized because we don’t have any context for the user “myuserid” yet. Let’s add some context by having our user visit a product page for running shoes. We do that by running the following mutation query with a payload:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">Query</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mutation</span><span class="token plain"> </span><span class="token definition-mutation function" style="color:rgb(80, 250, 123)">AddView</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$view</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token atom-input class-name">ProductViewInput</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property-query property-mutation">ProductView</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">view</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$view</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">_source_time</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Mutations require a payload that specifies which product a user visited. Enter the following JSON payload under "Query Variables" in the bottom left of GraphiQL before running the query:</p><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">Query Variables</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"view"</span><span class="token operator">:</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"productid"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"f40b93aa-c3b2-5cf4-97c0-92151279464f"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"userid"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"myuserid"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Now, if you run the same search query for “jackets” again with the “myuserid” (click on the history tab to go back to the first query), you should see that the results are tailored to the semantic context of our user. Or try searching for "shorts" for the same user, and you'll get athletic and running shorts.</p><img loading="lazy" src="/img/blog/ai_personalized_search_graphiql.png" alt="Personalized search results shown in GraphiQL |" width="100%" class="img_ev3q"><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-5-next-steps">Step 5: Next Steps<a href="#step-5-next-steps" class="hash-link" aria-label="Direct link to Step 5: Next Steps" title="Direct link to Step 5: Next Steps">​</a></h3><p>And that’s how you build a personalized search with vector embeddings. Our implementation is only a few lines of code, because DataSQRL generated all the data plumbing for our microservice.</p><p>We hope you can use this tutorial as a starting point to implement your own personalized search with LLMs. With DataSQRL, you can add additional sources of data, implement custom scoring functions, and fine-tune the search to your particular use case. For extra credit, you could change the aggregation function to prioritize recent product views or fine-tune the LLM for sentence embedding to our dataset. We’ll discuss those improvements in a future tutorial.</p><p>Check out the <a href="/docs/intro/">DataSQRL documentation</a> for more information and <a href="https://github.com/DataSQRL/sqrl" target="_blank" rel="noopener noreferrer">star DataSQRL on Github</a> to bookmark the open-source project. Good luck building your AI search and reach out to the <a href="/community/">community</a> if you have any questions.</p>]]></content:encoded>
            <category>AI</category>
            <category>Kafka</category>
            <category>Flink</category>
            <category>Postgres</category>
            <category>search</category>
            <category>personalization</category>
            <category>DataSQRL</category>
        </item>
        <item>
            <title><![CDATA[Personalized Recommendations for Current23 with Vector Embeddings in Flink and Kafka]]></title>
            <link>https://www.datasqrl.com/blog/recommendations-current23</link>
            <guid>https://www.datasqrl.com/blog/recommendations-current23</guid>
            <pubDate>Thu, 21 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Let’s build a personalized recommendation engine using AI as an event-driven microservice with Kafka, Flink, and Postgres. And since Current23 is starting soon, we will use the events of this event-driven conference as our input data (sorry for the pun). You’ll learn how to apply AI techniques to streaming data and what talks you want to attend at the Kafka conference - double win!]]></description>
            <content:encoded><![CDATA[<p>Let’s build a personalized recommendation engine using AI as an event-driven microservice with Kafka, Flink, and Postgres. And since Current23 is starting soon, we will use the events of this event-driven conference as our input data (sorry for the pun). You’ll learn how to apply AI techniques to streaming data and what talks you want to attend at the Kafka conference - double win!</p><div style="float:right;width:40%"><iframe width="100%" height="100%" src="https://www.youtube.com/embed/gI_TRF1z-So" title="DataSQRL Introduction" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div><p>We will implement the whole microservice in 50 lines of code thanks to the DataSQRL compiler, which eliminates all the data plumbing so we can focus on building.</p><p>Watch the video to see the microservice in action or read below for step-by-step building instructions and details.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-we-will-build">What We Will Build<a href="#what-we-will-build" class="hash-link" aria-label="Direct link to What We Will Build" title="Direct link to What We Will Build">​</a></h2><p>We are going to build a recommendation engine and semantic search that uses AI to provide personalized results for users based on user interactions.</p><p>Let’s break that down:
Our input data is a stream of conference events, namely the talks with title, abstract, speakers, time, and so forth. We consume this data from an external data source.</p><p>In addition, our microservice has endpoints to capture which talks a user has liked and what interests a user has expressed. We use those user interactions to create a semantic user profile for personalized recommendations and personalized search results.</p><p>We create the semantic user profile through vector embeddings, an AI technique for mapping text to numbers in a way that preserves the content of the text for comparison. It’s a great tool for representing the meaning of text in a computable way. It's like mapping addresses (i.e. street, city, zip, country) onto geo-coordinates. It’s hard to compare two addresses, but easy to compute the distance between two geo-coordinates. Vector embeddings do the same thing for natural language text.</p><p>Those semantic profiles are then used to serve recommendations and personalized search results. </p><p>To summarize, our microservice will expose the following API (expressed in GraphQL schema):</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Mutation</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">Likes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">liked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token atom-input class-name">LikedInput</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">LikeAdded</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">interest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">AddInterest</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">InterestAdded</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">offset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">Events</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">EventsLiked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">EventsLiked</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">RecommendedEvents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">RecommendedEvents</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">PersonalizedEventSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">PersonalizedEventSearch</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>The API has two mutations (for REST folks, think of those as POST): one captures which events a user has liked, and another captures a user’s interests.</p><p>We have four query endpoints (those are like GET): two simple ones that return all the events and the events a user has liked and two AI-powered ones that return recommended events for a user and personalized search results for a user’s search query.</p><p>You can see the full GraphQL API with the mutation and return types <a href="https://gist.github.com/mbroecheler/bd3ba8a8307fc36836a91599b9ff2643" target="_blank" rel="noopener noreferrer">here</a>.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="architecture">Architecture<a href="#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture">​</a></h2><p>We will implement our conference recommendation service as an event-driven microservice for robust, real-time processing at scale. The architecture of the microservice is shown below and uses Kafka for event storage, Flink for stream processing, Postgres for querying, and GraphQL for the API.</p><img loading="lazy" src="/img/blog/current23_microservice.svg" alt="Event-driven microservice architecture >" width="60%" class="img_ev3q"><p>The data travels counter-clockwise from the top:</p><ol><li>When a user interaction is captured through the mutation endpoint,</li><li>the input data is written to Kafka as an event, </li><li>which gets picked up by Flink, processed, embedded as a vector, and aggregated into a semantic user profile, </li><li>which is stored in Postgres. </li><li>When a user requests data through the query endpoint, the data is fetched from Postgres. </li><li>In addition, the conference events are ingested by Flink from an external data source and similarly processed and stored in the database for querying.</li></ol><p>Each component serves a distinct purpose in this event-driven architecture: the GraphQL server acts as the interface to the outside world, Kafka manages events in motion, Flink processes the event stream, and Postgres stores the processed data for retrieval on request.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="implementation">Implementation<a href="#implementation" class="hash-link" aria-label="Direct link to Implementation" title="Direct link to Implementation">​</a></h2><p>Now, we could implement this event-driven microservice by implementing each of the components: implement the GraphQL server, set up the Kafka topics and event schemas, implement a Flink job for data processing, and design a database schema plus SQL queries. We would have to write a ton of data plumbing code: moving data between systems, mapping schemas, designing data models, and stitching it all together. There is a reason event-driven microservices are built by teams of specialists.</p><p>There is a better way: We are going to use the open-source DataSQRL compiler to generate all of that data plumbing code for us. That means we can implement our entire microservice in just 50 lines of SQL code as follows (<a href="https://gist.github.com/mbroecheler/315f99fc53768f579014ab9be7cc2fd4" target="_blank" rel="noopener noreferrer">click here</a> to see the entire SQL script):</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="imports">Imports<a href="#imports" class="hash-link" aria-label="Direct link to Imports" title="Direct link to Imports">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> conference</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">--import external data</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> recAPI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Likes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">--import data from API mutation</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> recAPI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">--import data from API mutation</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> vector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parseTimestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>At the beginning, we import the source tables that we are processing in this script. DataSQRL uses packages to represent data sources for modularity and ease of reuse. It’s like importing an external library but for data.
Our API is treated as a data source which allows us to import the mutation input data as a table.</p><p>We are also importing functions for string processing, vector embedding, etc. DataSQRL uses the same packaging structure to organize functions.</p><p>Now, onto the actual logic of our script.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="processing-event-data">Processing Event Data<a href="#processing-event-data" class="hash-link" aria-label="Direct link to Processing Event Data" title="Direct link to Processing Event Data">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">coalesce</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">regexExtract</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'(\d*)$'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">full_text :</span><span class="token operator">=</span><span class="token plain"> concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token string" style="color:rgb(255, 121, 198)">'\n'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">abstract</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startTime :</span><span class="token operator">=</span><span class="token plain"> concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">trim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">regexExtract</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'^[^-]*'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token string" style="color:rgb(255, 121, 198)">' '</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">trim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">regexExtract</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'\d\d?:\d\d\s(AM|PM)'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startTimestamp :</span><span class="token operator">=</span><span class="token plain"> parseTimestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">startTime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token string" style="color:rgb(255, 121, 198)">' PDT'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'MMMM d, yyyy h:mm a z'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding :</span><span class="token operator">=</span><span class="token plain"> onnxEmbed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">full_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/build/embedding/model_quantized.onnx'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> Events </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> last_updated </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>First, we are adding additional columns to the <code>Events</code> table and then deduplicating the data stream so we have the most recent version of each event.</p><p>We are adding columns mostly to clean up our ingested events data. When you are dealing with external data, cleanup is often necessary. In this case, we need to do some work to extract the event timestamp and id.</p><p>We are also adding the <code>embedding</code> column to compute a vector embedding for the <code>title</code> and <code>abstract</code> of each talk. We are using the <a href="https://onnxruntime.ai/" target="_blank" rel="noopener noreferrer">ONNX AI</a> runtime to execute the embedding model. The embedding model we are using here is a quantized version of the <a href="https://www.sbert.net/docs/pretrained_models.html" target="_blank" rel="noopener noreferrer">all-MiniLM-L6-v2</a> pre-trained model. This is a model for sentence embedding trained on a large corpus that is small and fast while delivering good performance. “Quantized” means that the model has been transformed to run efficiently on CPUs for those of us who aren’t hoarding GPUs right now.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="processing-user-interactions">Processing User Interactions<a href="#processing-user-interactions" class="hash-link" aria-label="Direct link to Processing User Interactions" title="Direct link to Processing User Interactions">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding :</span><span class="token operator">=</span><span class="token plain"> onnxEmbed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/build/embedding/model_quantized.onnx'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">LikeVector :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_source_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> Likes l TEMPORAL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Events e </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">eventId </span><span class="token operator">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">liked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">UserInterestVectors :</span><span class="token operator">=</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> _source_time </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> LikeVector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNION</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALL</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> _source_time </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">UserInterests :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> center</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> interestVector </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> UserInterestVectors </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Next, we are processing the user interactions. We are adding an embedding vector for the user interests captured in the AddInterst table. We are joining the user <code>Likes</code> with the deduplicated events table using a <a href="/blog/temporal-join/">temporal join</a> to get the embedding vector for the liked event. Both of those capture the semantic interests of a user.</p><p>Then, we combine those data streams in the <code>UserInterstVectors</code> table and aggregate them by computing the centroid for all those vectors for each user. That summary of user interest vectors gives us the semantic profile of each user.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="user-analytics">User Analytics<a href="#user-analytics" class="hash-link" aria-label="Direct link to User Analytics" title="Direct link to User Analytics">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">UserLikes :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> Likes </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> eventId </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> _source_time </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">EventLikeCount :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> eventid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> num</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">avg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">eventid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> test</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> UserLikes l </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">liked </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> eventid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">likeCount :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> EventLikeCount l </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.id</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">eventid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">EventsLiked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> :</span><span class="token operator">=</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> UserLikes l </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Events e </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">eventId </span><span class="token operator">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> l</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startTimestamp </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ASC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>To show you that DataSQRL also supports good old-fashioned data analytics, we are adding some user likes analytics. We deduplicate the stream of <code>Likes</code> (in case a user liked and then unliked an event) and aggregate them by event into the <code>EventLikeCount</code> table.</p><p>We add a relationship between <code>Events</code> and <code>EventLikeCount</code> so that the like count can be accessed from the event through the API. DataSQRL adds relationships to SQL, so you can structure your data for API access and to simplify join expressions.</p><p>We add a table function that returns all the events a user has liked which maps onto the query endpoint in the GraphQL API of the same name.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="personalized-recommendation">Personalized Recommendation<a href="#personalized-recommendation" class="hash-link" aria-label="Direct link to Personalized Recommendation" title="Direct link to Personalized Recommendation">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">RecommendedEvents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> :</span><span class="token operator">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> cosineSimilarity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">interestVector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> score</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> UserInterests i </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Events e</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>To serve users personalized recommendations, we compute the similarity between the event embedding and the aggregated semantic user profile of the <code>UserInterests</code> table using cosine similarity between the vectors.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="personalized-search">Personalized Search<a href="#personalized-search" class="hash-link" aria-label="Direct link to Personalized Search" title="Direct link to Personalized Search">​</a></h3><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">PersonalizedEventSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@query</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain">: String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> :</span><span class="token operator">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">coalesce</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cosineSimilarity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">interestVector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">0.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> score</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> Events e </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> UserInterests i </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@userid</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> textsearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> abstract</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>For personalized search, we retrieve those events where the title or abstract matches the search query and then rank the results based on how similar the event is to the aggregated user interests.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2><p>And that’s it. A complete event-driven microservice with vector embedding, personalized search, and user interaction analytics in 50 lines of SQL code.</p><p>And DataSQRL handles all the rest: mapping mutations onto Kafka topics and events, ingesting those events into Flink and mapping schemas, designing the physical data models in Kafka, Flink, and the database, mapping API calls onto database queries, optimizing index structures, and moving the data efficiently between all those components. That’s a whole lot of soul-sucking work we did not have to do.</p><p>If you want to learn more about DataSQRL, visit <a href="/">datasqrl.com</a>, take a look at the <a href="/docs/getting-started/quickstart/">quickstart tutorial</a>, or <a href="/community/">join the community</a> on <a href="/community/">Slack</a> to ask questions and share your thoughts and feedback.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="run-microservice">Run Microservice<a href="#run-microservice" class="hash-link" aria-label="Direct link to Run Microservice" title="Direct link to Run Microservice">​</a></h2><p>Want to run the recommendation microservice yourself? It’s easy. Follow these steps:</p><ol><li>In your command line, create an empty folder and go into the folder:</li></ol><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">mkdir</span><span class="token plain"> current23</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> current23</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ol start="2"><li><p>Download the SQRL script, GraphQL schema, vector embedding model and event data source by <a href="https://drive.google.com/file/d/15p6erQnG8S4eDVgjxiOdYEfvqdEgjc0M/view?usp=sharing" target="_blank" rel="noopener noreferrer">clicking here</a>, moving the zip file into folder you just created and unpacking it. You should see 3 directories (conference, conferencedata, and embedding) as well as a <code>sqrl</code> and <code>graphqls</code> file. The SQRL script and GraphQL schema are the ones we walked through above.</p></li><li><p>Compile the SQRL script and GraphQL schema into an event-driven microservice by running:</p></li></ol><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run --rm -v </span><span class="token environment constant" style="color:rgb(189, 147, 249)">$PWD</span><span class="token plain">:/build datasqrl/cmd compile conference-recommendation.sqrl recAPI.graphqls --mnt </span><span class="token environment constant" style="color:rgb(189, 147, 249)">$PWD</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ol start="4"><li>Stand up the entire microservice in docker by running:</li></ol><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cd build/deploy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose up</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Note, that the microservice does not contain DataSQRL. DataSQRL is only the compiler and generates the docker-compose script for orchestrating the microservice. The microservice itself only consists of Kafka, Flink, Postgres, and GraphQL server.
If you want to take a look at the deployment artifacts that DataSQRL compiled for each component, take a peek inside the <code>build/deploy</code> folder.</p><p>Once the microservice is up and running (it takes a little while for all the components to boot up and initialize), the GraphQL API is accessible. You can access the API directly or open <a href="http://localhost:8888/graphiql/" target="_blank" rel="noopener noreferrer">http://localhost:8888/graphiql/</a> to try out queries in your browser.</p><p>For example, run this query to get a list of events.</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property-query">Events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">title</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">abstract</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">time</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">location</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Add a user interest by running the following mutation:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mutation</span><span class="token plain"> </span><span class="token definition-mutation function" style="color:rgb(80, 250, 123)">AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$interest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">AddInterest</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property-query property-mutation">AddInterest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">interest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$interest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">_source_time</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>And add the following query payload under "Query Variables":</p><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"interest"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"userid"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1234"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"I want to learn more about Apache Flink and how to use it for real-time stream processing."</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Then look at the recommendations for the user <code>1234</code>:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property-query">RecommendedEvents</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1234"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">title</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">abstract</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>You can like an event with this mutation:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mutation</span><span class="token plain"> </span><span class="token definition-mutation function" style="color:rgb(80, 250, 123)">AddLike</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$liked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token atom-input class-name">LikedInput</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property-query property-mutation">Likes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">liked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token variable variable-input" style="color:rgb(189, 147, 249);font-style:italic">$liked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">_source_time</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>and this payload:</p><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"liked"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"userid"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1234"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"eventId"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1136822</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"liked"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>And then see how that impacts the personalized search results with this query:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property-query">PersonalizedEventSearch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"kafka"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token attr-name" style="color:rgb(241, 250, 140)">userid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1234"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">id</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">title</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">abstract</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>As you can see, our search results are strongly biased in the direction of Apache Flink since that's what we liked a Flink talk and expressed an interest in Flink.</p><p>Enjoy playing with the API and finding the talks you want to attend at the conference.</p><p>To shut the microservice down, hit CTRL-C and then run <code>(cd build/deploy; docker compose down -v)</code> to remove the volumes.</p>]]></content:encoded>
            <category>AI</category>
            <category>Kafka</category>
            <category>Flink</category>
            <category>Postgres</category>
            <category>microservice</category>
            <category>DataSQRL</category>
        </item>
        <item>
            <title><![CDATA[To Preprocess or to Query, that’s the Question!]]></title>
            <link>https://www.datasqrl.com/blog/preprocess-or-query</link>
            <guid>https://www.datasqrl.com/blog/preprocess-or-query</guid>
            <pubDate>Tue, 15 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[When developing streaming applications or event-driven microservices, you face the decision of whether to preprocess data transformations in the stream engine or execute them as queries against the database at request time. The choice impacts your application’s performance, behavior, and cost. An incorrect decision results in unnecessary work and potential application failure.]]></description>
            <content:encoded><![CDATA[<p>When developing streaming applications or event-driven microservices, you face the decision of whether to preprocess data transformations in the stream engine or execute them as queries against the database at request time. The choice impacts your application’s performance, behavior, and cost. An incorrect decision results in unnecessary work and potential application failure.</p><img loading="lazy" src="/img/blog/preprocessOrQuery.png" alt="To preprocess or to query? >|" width="50%" class="img_ev3q"><p>In this article, we’ll delve into the tradeoff between preprocessing and querying, guiding you to make the right decision. We’ll also introduce tools to simplify this process. Plus, you’ll learn how building streaming applications is related to fine cuisine. It’ll be a fun journey through the land of stream processing and database querying. Let’s go!</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="recap-anatomy-of-a-streaming-application">Recap: Anatomy of a Streaming Application<a href="#recap-anatomy-of-a-streaming-application" class="hash-link" aria-label="Direct link to Recap: Anatomy of a Streaming Application" title="Direct link to Recap: Anatomy of a Streaming Application">​</a></h2><p>If you're in the process of building an event-driven microservice or streaming application, let's recap what that entails. An event-driven microservice consumes data from one or multiple data streams, processes the data, writes the results to a data store, and exposes the final data through an API for external users to access.</p><p>The figure below visualizes the high-level architecture of a streaming application and its components: data streams (e.g. Kafka), stream processor (e.g. Flink), database (e.g. Postgres), and API server (e.g. GraphQL server).</p><img loading="lazy" src="/img/blog/dataflow-stages.svg" alt="Streaming Application Architecture" width="100%" class="img_ev3q"><p>An actual event-driven microservice might have a more intricate architecture, but it will always include these four elements: a system for managing data streams, an engine for processing streaming data, a place to store the results, and a server to expose the service endpoint.</p><p>This means an event-driven architecture has two stages: the preprocess stage, which processes data as it streams in, and the query stage which processes user requests against the API. Each stage handles data, but they differ in what triggers the processing: incoming data triggers the preprocess stage, while user requests trigger the query stage. The preprocess stage handles data before the user needs it, and the query stage handles data when the user explicitly requests it.</p><p>Understanding these two stages is vital for the successful implementation of event-driven microservices. Unlike most web services with only a query stage or data pipelines with only a preprocess stage, event-driven microservices require a combination of both stages.</p><p>This leads to the question: Where should data transformations be processed? In the preprocessing stage or the query stage? And what’s the difference, anyways? That’s what we will be investigating in this article.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-balancing-act-preprocess-vs-query">The Balancing Act: Preprocess vs. Query<a href="#the-balancing-act-preprocess-vs-query" class="hash-link" aria-label="Direct link to The Balancing Act: Preprocess vs. Query" title="Direct link to The Balancing Act: Preprocess vs. Query">​</a></h2><p>Picture yourself as the chef of a renowned Italian restaurant famous for its pasta dishes. You must decide what food to prepare during the day and what to cook when the order comes in. Cooking everything from scratch upon receiving an order would lead to long waiting times and require more kitchen staff. However, preparing all dishes ahead of time would result in overwork, significant food waste, and stale food. The solution lies somewhere in between. So you marinate the chicken and harvest the basil in the morning but cook the pasta on order.</p><p>Building streaming applications is a lot like being a chef. You must determine what data to preprocess for immediate availability and what data to compute fresh upon user request. If all your data processing happens in the database or server at request time, you'll face high request latencies and need to store a significant amount of data, increasing your costs. However, preprocessing everything might also be too expensive, result in unnecessary computations, and yield stale results.</p><p>Therefore, the optimal solution for data processing in streaming applications, like in a kitchen, often involves a balance between preprocessing and querying</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="striking-a-balance-preprocess-or-query">Striking a Balance: Preprocess or Query?<a href="#striking-a-balance-preprocess-or-query" class="hash-link" aria-label="Direct link to Striking a Balance: Preprocess or Query?" title="Direct link to Striking a Balance: Preprocess or Query?">​</a></h2><p>How do you find the right balance to determine whether a particular data transformation should be preprocessed or queried?</p><p>The answer lies in the following 4 requirements of your streaming application:</p><ul><li>Latency: What is the maximum request latency for the API endpoint?</li><li>Cost: What’s the operational budget for your application?</li><li>Freshness: How quickly should incoming data points be reflected in request responses?</li><li>Consistency: What API results need to be consistent with each other?</li></ul><p>Let’s look at each of these factors in detail to learn how they influence the decision on where the data should be processed.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="latency">Latency<a href="#latency" class="hash-link" aria-label="Direct link to Latency" title="Direct link to Latency">​</a></h3><p>Quantifying the latency requirements of your microservice API is typically straightforward.  For customer-facing microservices, it’s common to expect a p99.9 latency of a few hundred milliseconds, meaning 99.9% of all user requests complete within this time frame. If unsure, your product manager probably has an opinion on the target request latency 😉.</p><p>The maximum request latency sets a limit on the amount of data processing you can perform in the query stage. More data processing in the query stage results in longer request latency. This is largely influenced by the number of records you need to process and the complexity of the data processing required.</p><p>For instance, if a running deep-learning inference model at request time consumes 700 of your 1000 ms p99.9 latency, you must do most of the data processing (i.e., feature computation) in the preprocessing stage.</p><p>Likewise, pulling a million records from the database for aggregation is likely going to eat up your entire request latency budget. In particular, watch out for data transformations on data with highly skewed distributions. This occurs when the average number of records processed per request is small, but occasionally you have to process a lot of records which impacts your p99.9 latency.</p><p><strong>The general rule of thumb is: The lower the request latency, the more preprocessing you want to do.</strong></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="cost">Cost<a href="#cost" class="hash-link" aria-label="Direct link to Cost" title="Direct link to Cost">​</a></h3><p>The cost of running your microservice is another factor you have to keep within a certain budget. Let's explore how to optimize the cost of our streaming application, considering both the query and preprocessing stages.</p><h4 class="anchor anchorWithStickyNavbar_LWe7" id="query-cost">Query Cost<a href="#query-cost" class="hash-link" aria-label="Direct link to Query Cost" title="Direct link to Query Cost">​</a></h4><p>A big factor in our database cost is how much data we need to store. Naturally, that determines the storage cost for the database but it also increases the cost of all database operations. The more data a database must manage, the more computing resources it must utilize to retrieve data, maintain index structures, manage buffers, etc.
Therefore, the simplest way to decrease our database expense is by reducing the volume of data we store in it. One effective method to achieve this is by aggregating data in the preprocessing stage.</p><p>For example, imagine we are building an IoT microservice that gathers temperature readings from a million sensors that operate at 10Hz (i.e. they take 10 readings every second). This amounts to a quarter gigabyte per second or over 20 TB per day. If we stored all this data in a database, we’d face a pretty hefty bill. However, if we only require min, max, avg, and median readings for each minute, we can aggregate the data during preprocessing and significantly reduce the volume of data written to the database (approximately 500 times less), thereby reducing our database expenses.</p><p>The other factor that drives our query stage cost is the amount of data processing. Many applications are read-heavy, meaning the same processed data is accessed multiple times in different requests. By preprocessing that data and storing the result in the database, we can reduce our cost because we only have to run the computation once. This approach is somewhat akin to caching but with the assurance that the results are instantly updated as new data is received by the preprocessing stage.</p><h4 class="anchor anchorWithStickyNavbar_LWe7" id="preprocessing-cost">Preprocessing Cost<a href="#preprocessing-cost" class="hash-link" aria-label="Direct link to Preprocessing Cost" title="Direct link to Preprocessing Cost">​</a></h4><p>Now, let’s look at the preprocessing stage. Similar to the query stage, the cost is driven primarily by the amount of computation and the volume of data that needs to be stored. Stream engines are highly efficient at executing time-bound operations, such as time-window aggregations and temporal joins, because those limit the amount of state - and therefore storage - that the stream engine has to maintain over time.</p><p>On the other hand, data transformations that aren’t bound in time can accumulate state indefinitely which is costly, hurts performance, and can crash the entire application due to memory exhaustion. A common culprit for high preprocessing cost is the inner join. Computing inner joins on changing streams requires that the stream engine hold the entire dataset for both sides of the stream in memory AND issue updates for all previously joined records whenever a record on either side of the join changes. That’s why joins are typically much cheaper to execute in the database at request time.</p><p><strong>The general rule of thumb is: Reduce the cost of the query stage by preprocessing data but be mindful of costly streaming operations like joins.</strong></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="data-freshness">Data Freshness<a href="#data-freshness" class="hash-link" aria-label="Direct link to Data Freshness" title="Direct link to Data Freshness">​</a></h3><p>Much like chicken cooked the previous day, some data preprocessing leads to stale results that aren’t acceptable to the user of your API.</p><p>For example, let’s go back to our IoT microservice that collects temperature data. The sensors are producing 10 readings every second that we are aggregating into 1 second time-windows. In other words, we are effectively downsampling our readings from 10 to 1 per second. For applications where users expect to see the most up-to-date temperature that may not be acceptable.</p><p>The impact of data freshness on your application is harder to quantify than latency or cost, because it depends a lot on your customer’s expectations and what they are doing with the data you produce. But like the kitchen, you get the freshest results when you prepare everything from scratch when the request comes in.</p><p><strong>The general rule of thumb is: The fresher you want your aggregations to be, the more you have to compute at query time.</strong></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="consistency">Consistency<a href="#consistency" class="hash-link" aria-label="Direct link to Consistency" title="Direct link to Consistency">​</a></h3><p>Data transformations often utilize a single record of incoming data in multiple ways. Take for example a banking application that presents a list of transactions, categorized by type, and also offers a monthly spending analysis, broken down by type. If a user modifies the type of a transaction, the spending analysis should automatically update. This is what we mean by data consistency: any changes in the input data should be reflected in all the results computed from that data.</p><p>When we preprocess data, such changes are often not immediately reflected or we cannot guarantee that they are reflected in all preprocessed results at the same time. That can lead to inconsistencies in the data returned by the API. A user may update the transaction type but the spending analysis won’t update until a moment later when the preprocessing stage catches up and adjusts the aggregates.</p><p>Like data freshness, the impact of data inconsistency on your application can be harder to quantify. It typically needs to be evaluated in the context of the entire application, not just the event-driven microservice. For instance, if the front-end application that allows the user to modify the transaction type automatically updates the affected aggregates, the temporary inconsistency of data in the API response may not be problematic.</p><p>Maintaining data consistency is easier when data computations are executed as queries against the database because we compute the results from the original records.</p><p><strong>The general rule of thumb is: The more consistent you want computed results to be across changes in input data, the more computation has to be done in the query stage.</strong></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="practical-advice-for-balancing-preprocessing-and-querying">Practical Advice for Balancing Preprocessing and Querying<a href="#practical-advice-for-balancing-preprocessing-and-querying" class="hash-link" aria-label="Direct link to Practical Advice for Balancing Preprocessing and Querying" title="Direct link to Practical Advice for Balancing Preprocessing and Querying">​</a></h2><p>Choosing between preprocessing and querying isn't a straightforward decision. Rather, it's like a tug-of-war between your application's various requirements that will dictate whether a particular data transformation should be preprocessed or executed at query time. Low latency and cost-effectiveness tend to favor preprocessing, while data freshness and consistency often lean towards querying.</p><p><strong>What makes this decision so challenging is that preprocessing data and querying data are implemented very differently.</strong> Preprocessing data with tools like Kafka and Flink requires a different approach, language, and implementation compared to executing the exact same data transformation with Postgres and an API server at query time. It's common to have different team members handling preprocessing and query stage implementations. Therefore, moving a single transformation from the query to the preprocess stage (or vice versa) is expensive because it requires reimplementing the transformation, coordination within the team, and refactoring the interface between the components of the microservice.</p><p>Because it is so expensive to change what stage we execute our data transformations in, we try to get it right during the planning phase. But we often won’t know what the right balance is until we try it. And, application requirements change over time, of course. That makes event-driven microservice implementations time-consuming and costly.</p><p>But what if we could implement all of our data transformations in one script and have a compiler translate them to preprocess stage or query stage implementations based on application requirements? That would eliminate the problem because we can easily change where data transformations get executed. We could try out different allocations and iterate quickly as requirements change.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="meet-datasqrl-the-compiler-for-event-driven-microservices">Meet DataSQRL: The Compiler for Event-Driven Microservices<a href="#meet-datasqrl-the-compiler-for-event-driven-microservices" class="hash-link" aria-label="Direct link to Meet DataSQRL: The Compiler for Event-Driven Microservices" title="Direct link to Meet DataSQRL: The Compiler for Event-Driven Microservices">​</a></h2><p>That’s exactly what <a href="https://www.datasqrl.com/" target="_blank" rel="noopener noreferrer">DataSQRL</a> does. With DataSQRL, you can write your streaming application's data transformations in SQL. The compiler then translates your SQL into preprocess and query implementations. Specifically, DataSQRL translates SQL into a combination of FlinkSQL and the datastream API for preprocessing, and PostgreSQL statements for querying.</p><p>Let's see how this works using our IoT microservice example:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> datasqrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">example</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sensors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">SensorReading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import sensor data</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">endOfSecond</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import time function</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Aggregate sensor readings to second */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SecReading :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> endOfSecond</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                     </span><span class="token function" style="color:rgb(80, 250, 123)">avg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SensorReading</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Get max temperature in last minute */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SensorMaxTemp :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> maxTemp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SecReading</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> timeSec </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">now</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MINUTE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>First, we import the sensor reading data stream from Kafka. We define a new table <code>SecReading</code> that aggregates the readings per second. Finally, we create <code>SensorMaxTemp</code>, a table that calculates the maximum temperature for each sensor over the last minute.</p><p>DataSQRL's intelligent optimizer automatically determines where to execute the data transformations defined in your SQL script, based on your microservice's API.
For our example, we expose the following GraphQL API that allows users to query the <code>SecReading</code> and <code>SensorMaxTemp</code> tables by <code>sensorid</code>:</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">SecReading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">SecReading</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">SensorMaxTemp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">SensorMaxTemp</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">SecReading</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">temp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Float</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">SensorMaxTemp</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">maxTemp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Float</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Based on the API definition, DataSQRL's optimizer finds the best allocation of data transformations to stages by minimizing the request latency and cost. In our example, this means that the <code>SecReading</code> table is preprocessed as a tumbling time-window, and <code>SensorMaxTemp</code> is preprocessed as a sliding time-window. Both tables are aggregated in Flink during preprocessing, with the results written to Postgres for querying on request.</p><p>But what if data freshness of the <code>SensorMaxTemp</code> table is more important for our application than low latency? No problem, simply tell the optimizer to execute this data transformation in the database:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">/*+ EXEC(database) */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SensorMaxTemp :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> maxTemp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SecReading</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> timeSec </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">now</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MINUTE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>With a simple hint above the table definition, you can direct the optimizer where to process the table, and it compiles the microservice accordingly. What would have required a significant refactor of your entire microservice is now a simple code change.</p><img loading="lazy" src="/img/full_squirrel.svg" alt="DataSQRL >" width="30%" class="img_ev3q"><p>DataSQRL also handles all the data plumbing needed to integrate your event-driven microservice components. There's no need to connect Flink to Kafka, define the Flink table schema, define the Postgres table schema, or map API requests to prepared SQL statements. The DataSQRL compiler does it all for you. You get the same microservice architecture and technologies but with significantly less work.</p><p>Now, that takes the edge off making the right decision on where to compute your data transformations and turns the balancing act into a straightforward process: You can let the DataSQRL optimizer handle it for you, experiment with different allocations, and iterate quickly as your application evolves. DataSQRL makes the implementation of your event-driven microservices and streaming applications faster, easier, and more cost-effective.</p><p>Take a look at the <a href="https://www.datasqrl.com/docs/getting-started/quickstart/" target="_blank" rel="noopener noreferrer">DataSQRL tutorial</a> to learn more about DataSQRL and <a href="/community/">join us on Slack</a> if you need any help. If you're looking to empower your team to build event-driven microservices like the pros, <a href="https://www.datasqrl.com/services/" target="_blank" rel="noopener noreferrer">we're here to help</a> you hit the ground running.</p>]]></content:encoded>
            <category>Kafka</category>
            <category>Flink</category>
            <category>Postgres</category>
            <category>data pipeline</category>
            <category>DataSQRL</category>
        </item>
        <item>
            <title><![CDATA[Why Temporal Join is Stream Processing’s Superpower]]></title>
            <link>https://www.datasqrl.com/blog/temporal-join</link>
            <guid>https://www.datasqrl.com/blog/temporal-join</guid>
            <pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Stream processing technologies like Apache Flink introduce a new type of data transformation that’s very powerful: the temporal join. Temporal joins add context to data streams while being efficient and fast to execute.]]></description>
            <content:encoded><![CDATA[<p>Stream processing technologies like Apache Flink introduce a new type of data transformation that’s very powerful: the temporal join. Temporal joins add context to data streams while being efficient and fast to execute.</p><img loading="lazy" src="/img/dev/temporal_join.svg" alt="Temporal Join >" width="30%" class="img_ev3q"><p>This article introduces the temporal join, compares it to the traditional inner join, explains when to use it, and why it is a secret superpower.</p><p>Table of Contents:</p><ul><li><a href="#review">The Join: A Quick Review</a></li><li><a href="#tempjoin">The Temporal Join: Linking Stream and State</a></li><li><a href="#tempinner">Temporal Join vs Inner Join</a></li><li><a href="#efficient">Why Temporal Joins are Fast and Efficient</a></li><li><a href="#easy">Temporal Joins Made Easy to Use</a></li><li><a href="#summary">Summary</a></li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="review">The Join: A Quick Review<a href="#review" class="hash-link" aria-label="Direct link to The Join: A Quick Review" title="Direct link to The Join: A Quick Review">​</a></h2><p>Let's take a quick detour down memory lane and revisit the good ol' join operation. That trusty sidekick in your SQL utility belt helps you link data from two or more tables based on a related column between them.</p><p>Suppose we are operating a factory with a number of machines that roast and package coffee. We place sensors on each machine to monitor the temperature and detect overheating.</p><p>We keep track of the sensors and machines in two database tables.</p><p>The <code>Sensor</code> table contains the serial number and machine id that the sensor is placed on.</p><table><thead><tr><th>id</th><th>serialNo</th><th>machineid</th></tr></thead><tbody><tr><td>1</td><td>X57-774</td><td>501</td></tr><tr><td>2</td><td>X33-453</td><td>203</td></tr><tr><td>3</td><td>X54-554</td><td>501</td></tr></tbody></table><p>The <code>Machine</code> table contains the name of each machine.</p><table><thead><tr><th>id</th><th>name</th></tr></thead><tbody><tr><td>203</td><td>Iron Roaster</td></tr><tr><td>501</td><td>Gritty Grinder</td></tr></tbody></table><p>To identify all the sensors on the machine “Iron Roaster” we use the following SQL query which joins the <code>Sensor</code> and <code>Machine</code> tables:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">serialNo </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> Sensor s </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Machine m </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid </span><span class="token operator">=</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> “Iron Roaster”</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Why are joins as important as your morning coffee? Without it, your data tables are like islands, isolated and lonely. Joins bring them together, creating meaningful relationships between data, and enriching data records with context to see the bigger picture.</p><p>By default, databases execute joins as <strong>inner</strong> joins which means only matching records are included in the join.</p><p>So, now that we've refreshed our memory about the classic join, let's dive into the exciting world of temporal joins in stream processing systems like Apache Flink.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="tempjoin">The Temporal Join: Linking Stream and State<a href="#tempjoin" class="hash-link" aria-label="Direct link to The Temporal Join: Linking Stream and State" title="Direct link to The Temporal Join: Linking Stream and State">​</a></h2><img loading="lazy" src="/img/blog/delorean.jpeg" alt="Temporal Join DeLorean >" width="40%" class="img_ev3q"><p>Picture this: you're a time traveler. You have the power to access any point in time, past or future, at your will. Now, imagine that your data could do the same. Enter the Temporal Join, the DeLorean of data operations, capable of taking your data on a time-traveling adventure.</p><p>A Temporal Join is like a regular join but with a twist. It allows you to join a stream of data (the time traveler) with a versioned table (the timeline) based on the time attribute of the data stream. This means that for each record in the stream, the join will find the most recent record in the versioned table that is less than or equal to the stream record's time.</p><p>The versioned table is a normal state table where we keep track of data changes over time. That is, we keep older versions of each record around to allow the stream to match the correct version in time. Like time travel, temporal joins can make your head spin a bit. Let’s look at an example to break it down.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="tempinner">Temporal Join vs Inner Join<a href="#tempinner" class="hash-link" aria-label="Direct link to Temporal Join vs Inner Join" title="Direct link to Temporal Join vs Inner Join">​</a></h2><p>Back to our coffee roasting factory, we collect the temperature readings from each sensor in a data stream. </p><table><thead><tr><th>timestamp</th><th>sensorid</th><th>temperature</th></tr></thead><tbody><tr><td>2023-07-10T07:11:08</td><td>1</td><td>105.2</td></tr><tr><td>2023-07-10T07:11:08</td><td>2</td><td>83.1</td></tr><tr><td>...</td><td></td><td></td></tr><tr><td>2023-07-10T13:25:16</td><td>1</td><td>77.8</td></tr><tr><td>2023-07-10T13:25:16</td><td>2</td><td>83.5</td></tr></tbody></table><p>And we want to know the maximum temperature recorded for each machine.</p><p>Easy enough, let’s join the temperature data stream with the Sensors table and aggregate by machine id:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> maxTemp </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SensorReading r </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INNER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Sensor s </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sensorid </span><span class="token operator">=</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>But here is a problem: What if we moved a sensor from one machine to another during the day? With an inner join, all of the sensor’s readings would be linked to the machine it was last placed on. So, if sensor 1 records a high temperature of 105 degrees in the morning and we move the sensor to the “Iron Roaster” machine in the afternoon, then we might see the 105 degrees falsely show up as the maximum temperature for the Iron Roaster. See how time played a trick on our join?</p><p>And this happens whenever we join a data stream with a state table that changes over time, like our sensors that get moved around the factory. What to do? Let’s call the temporal join to our rescue:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> maxTemp </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SensorReading r TEMPORAL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Sensor s </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sensorid </span><span class="token operator">=</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Pretty much the same query, just a different join type. Just a heads-up: the syntax for temporal joins in Flink SQL is more complex - we'll get to that <a href="#easy">later</a>.</p><p>As a temporal join, we are joining each sensor reading with the version of the sensor record at the time of the data stream. In other words, the join not only matches the sensor reading with the sensor record based on the id but also based on the timestamp of the reading to ensure it matches the right version of the sensor record. Pretty neat, right?</p><p>Whenever you join a data stream with a state that changes over time, you want to use the temporal join to make sure your data is lined up correctly in time. Temporal joins are a powerful feature of stream processing engines that would be difficult to implement in a database.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="efficient">Why Temporal Joins are Fast and Efficient<a href="#efficient" class="hash-link" aria-label="Direct link to Why Temporal Joins are Fast and Efficient" title="Direct link to Why Temporal Joins are Fast and Efficient">​</a></h2><img loading="lazy" src="/img/external/flink_logo.svg" alt="Apache Flink >" width="30%" class="img_ev3q"><p>Not only do temporal joins solve the time-alignment problem when joining data streams with changing state, modern stream processors like Apache Flink are also incredibly efficient at executing temporal joins. A powerful feature with great performance? Sounds too good to be true. Let’s peek behind the stream processing curtain to find out why.</p><p>In stream processing, joins are maintained as the underlying data changes over time. That requires the stream engine to hold all the data it needs to update join records when either side of the join changes. This makes inner joins pretty expensive on data streams.</p><p>Consider our max-temperature query with the inner join: When we join a temperature reading with the corresponding sensor record, and that record changes, the engine has to update the result join record. To do so, it has to store all the sensor readings to determine which join results are affected by a change in a sensor record. This can lead to a lot of updates and hence a lot of downstream computation. It can also cause system failure when there are a lot of temperature readings in our data stream because the stream engine has to store all of them.</p><p>Temporal joins, on the other hand, can be executed much more efficiently. The stream engine only needs to store the versions of the sensor table that are within the time bounds of the sensor reading data stream. And it only has to briefly store (if at all) the sensor reading records to ensure they are joined with the most up-to-date sensor records. Moreover, temporal joins don’t require sending out a massive amount of updated join records when sensors change placement since the join is fixed in time.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="easy">Temporal Joins Made Easy to Use<a href="#easy" class="hash-link" aria-label="Direct link to Temporal Joins Made Easy to Use" title="Direct link to Temporal Joins Made Easy to Use">​</a></h2><img loading="lazy" src="/img/full_squirrel.svg" alt="DataSQRL >" width="30%" class="img_ev3q"><p>Because temporal joins are so powerful, we made them easy to use in DataSQRL. DataSQRL is a compiler for Apache Flink that builds integrated data pipelines for your event-driven or streaming applications. DataSQRL supports the simplified temporal join syntax shown in the queries above. In addition, DataSQRL defaults to a temporal join whenever you join a state and a stream table on the state table’s primary key. In that way, DataSQRL helps you pick the right join for your data and makes it easy for developers new to stream processing.</p><p>Apache Flink supports temporal joins in Flink SQL using the following syntax:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> maxTemp </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SensorReading </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> r  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Sensor </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OF</span><span class="token plain"> SensorReading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> s </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sensorid </span><span class="token operator">=</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">machineid</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>You need to be careful that the join column for the state table is the primary key of that table and that you set the timestamp for the SensorReading table. DataSQRL does that for you automatically based on watermark.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="summary">Time to Wrap Up This Temporal Journey<a href="#summary" class="hash-link" aria-label="Direct link to Time to Wrap Up This Temporal Journey" title="Direct link to Time to Wrap Up This Temporal Journey">​</a></h2><p>We've reached the end of our time-traveling adventure through the universe of temporal joins. We've seen how they're like the DeLorean of data operations, zipping us back and forth through time to make sure our data matches up just right. We've also compared them to the good ol' inner join.</p><p>Temporal joins help us avoid the pitfalls of time-alignment problems when joining data streams with changing state. They're also super efficient, making them a great choice for high-volume, real-time data processing.</p><p>And that’s why the temporal join is stream processing's secret superpower.</p><p>DataSQRL makes using temporal joins a breeze. With its simplified syntax and smart defaults, it's like having a personal tour guide leading you through the sometimes bewildering landscape of stream processing. Take a look at our <a href="/docs/getting-started/tutorials/iot/intro/">IoT tutorial</a> to see a complete example of temporal joins in action or take a look at our <a href="/docs/getting-started/tutorials/overview/">other tutorials</a> for a step-by-step guide to stream processing including temporal joins. And we are <a href="/community/">here to help</a> if you have any questions.</p><p>Happy data time-traveling, folks!</p>]]></content:encoded>
            <category>Join</category>
            <category>Flink</category>
            <category>DataSQRL</category>
        </item>
        <item>
            <title><![CDATA[Why Apache Flink and GraphQL Are like Peanut Butter and Jelly]]></title>
            <link>https://www.datasqrl.com/blog/flink-graphql-peanut-butter-jelly</link>
            <guid>https://www.datasqrl.com/blog/flink-graphql-peanut-butter-jelly</guid>
            <pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[In the world of data-driven applications, Apache Flink is a powerful tool that transforms streams of raw data into valuable results. But how do you make these results accessible to users, customers, or consumers of your application? Most often, we found the answer to that question was: GraphQL. GraphQL gives users a flexible way to query for data, makes it easy to ingest events, and supports pushing data updates to the user in real-time.]]></description>
            <content:encoded><![CDATA[<p>In the world of data-driven applications, Apache Flink is a powerful tool that transforms streams of raw data into valuable results. But how do you make these results accessible to users, customers, or consumers of your application? Most often, we found the answer to that question was: GraphQL. GraphQL gives users a flexible way to query for data, makes it easy to ingest events, and supports pushing data updates to the user in real-time.</p><img loading="lazy" src="/img/blog/flink_graphql.svg" alt="Flink hearts GraphQL >" width="40%" class="img_ev3q"><p>In this blog post, we’ll discuss what GraphQL is and why it is a good fit for Flink applications. Like peanut butter and jelly, Flink and GraphQL don’t seem related but the combination is surprisingly good.</p><p>Table of Contents:</p><ul><li><a href="#access">How To Access Flink Results?</a></li><li><a href="#graphql">What is GraphQL?</a></li><li><a href="#benefit1">Benefit #1: Flexible Access for Data APIs</a></li><li><a href="#benefit2">Benefit #2: Realtime Data Updates with GraphQL Subscriptions</a></li><li><a href="#benefit3">Benefit #3: Simplify Event Ingestion with GraphQL Mutations</a></li><li><a href="#downsides">Downsides of using GraphQL in Flink Applications</a></li><li><a href="#howto">How to Build GraphQL APIs with Flink</a></li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="access">How To Access Flink Results?<a href="#access" class="hash-link" aria-label="Direct link to How To Access Flink Results?" title="Direct link to How To Access Flink Results?">​</a></h2><p>Quick background before we dive into the details. <a href="https://flink.apache.org/" target="_blank" rel="noopener noreferrer">Apache Flink</a> is a scalable stream processor that can ingest data from multiple sources, integrate, transform, and analyze the data, and produce results in real time. Apache Flink is the brain of your data processing operations.</p><img loading="lazy" src="/img/external/flink_logo.svg" alt="Flink Logo >" width="30%" class="img_ev3q"><p>But Apache Flink cannot make the processed results accessible to users of your application. Flink has an API, but that API is only for administering and monitoring Flink jobs. It doesn’t give outside users access to the result data. In other words, Flink is a brain without a mouth to communicate results externally.</p><p>To make results accessible, you have to write them somewhere and expose them through an interface. But how? We have built a number of Flink applications and in most cases, the answer was: write the results to a database or Kafka and expose them through an API. Over the years, our default choice for the API has become GraphQL. Here’s why.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="graphql">What is GraphQL?<a href="#graphql" class="hash-link" aria-label="Direct link to What is GraphQL?" title="Direct link to What is GraphQL?">​</a></h2><p>First, let's talk about <a href="https://graphql.org/" target="_blank" rel="noopener noreferrer">GraphQL</a>, the data query and manipulation language that's been shaking up the API world.</p><img loading="lazy" src="/img/external/graphql_logo.svg" alt="GraphQL Logo >" width="30%" class="img_ev3q"><p>GraphQL is a robust alternative to REST for building APIs. It provides a complete and understandable description of your data in the API and gives clients the power to ask for exactly what they need. This means no more over-fetching of data or making multiple requests, which can be an issue with REST.</p><p>In contrast to REST, which uses a separate URL for each resource, GraphQL operates over a single endpoint using HTTP. This simplifies the process of making API calls, as you don't have to construct multiple URLs. Furthermore, GraphQL uses a type system to describe data, which can make your API calls more predictable and easier to debug.</p><p>Like REST, GraphQL is mature and well-established with lots of tooling and client libraries for pretty much all programming languages under the sun.</p><p>Okay, so GraphQL allows you to build APIs that are easy to use. But what makes it such a good match for Apache Flink applications? GraphQL has three key features that unlock the power of Flink’s data processing: flexible queries, subscriptions, and mutations. Let’s look at those in more detail.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="benefit1">1. Flexible Access for Data APIs<a href="#benefit1" class="hash-link" aria-label="Direct link to 1. Flexible Access for Data APIs" title="Direct link to 1. Flexible Access for Data APIs">​</a></h2><p>Imagine you're at an all-you-can-eat buffet, but instead of food, it's data. You can pick and choose exactly what you want and how much of it you need. That's what GraphQL does for you when it comes to exposing data APIs. Developers can query the processed streaming data however they need it, without being constrained by predefined endpoints.</p><p>Other API standards like REST or gRPC are like ordering dishes from a menu. You can only access the endpoints that are defined in the API. That’s fine for many applications, but for data APIs, there are many possible combinations of data records users want to query. So, you either define a lot of endpoints or shift the burden onto the user to stitch their desired result sets together. In the end, it’s more work and a lot of food gets wasted.</p><p>But wait, there's more! GraphQL not only allows you to select the data you need, but it also simplifies the process of combining data from multiple sources. This means you can easily merge data from various microservices and databases, creating a seamless and unified experience for developers.</p><p>In a nutshell, GraphQL empowers developers by providing a flexible, powerful, and efficient way to access the data they need.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="benefit2">2. Realtime Data Updates with GraphQL Subscriptions<a href="#benefit2" class="hash-link" aria-label="Direct link to 2. Realtime Data Updates with GraphQL Subscriptions" title="Direct link to 2. Realtime Data Updates with GraphQL Subscriptions">​</a></h2><p>Remember those walkie-talkies you used to play with as a kid? You'd press the button, and your voice would be magically transmitted to your friend's walkie-talkie in real-time. Well, GraphQL subscriptions are kind of like that, but for data.</p><p>Subscriptions allow Flink developers to push real-time data updates to consumers of the API, ensuring that everyone is always up-to-date with the latest information. It's like having a walkie-talkie channel dedicated to data updates, so you never miss a beat.</p><p>But that's not all! GraphQL subscriptions also enable developers to filter and control the data they receive. This means that each consumer can specify exactly what data they want to be updated on, reducing the amount of unnecessary information being transmitted.</p><p>In essence, GraphQL subscriptions provide a powerful and efficient way to keep everyone in the loop with real-time data updates. It's like having a personal news ticker, tailored specifically to your needs.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="benefit3">3. Simplify Event Ingestion with GraphQL Mutations<a href="#benefit3" class="hash-link" aria-label="Direct link to 3. Simplify Event Ingestion with GraphQL Mutations" title="Direct link to 3. Simplify Event Ingestion with GraphQL Mutations">​</a></h2><p>Mutations make it simple to ingest events into your system, streamlining the process and keeping everything running smoothly. GraphQL mutations support complex event payloads that are defined as input types directly in the API specification, making it easy for users of your API to submit data.</p><p>GraphQL was developed with mobile applications in mind, where the connection between phone and server can be spotty. GraphQL mutations follow an event-centric model to separate the state of the mobile device from the state on the server and ensure continued operations when the connection between the two gets interrupted. That’s a perfect match for data pipelines and event-driven microservices which follow the same model for decoupling.</p><p>And unlike REST, you don’t have to worry about HTTP methods and state management which add a level of complexity you don’t need for event-driven applications.</p><p>In summary, GraphQL mutations structure data ingestion and updates as events - just like Flink.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="downsides">Downsides of using GraphQL in Flink Applications<a href="#downsides" class="hash-link" aria-label="Direct link to Downsides of using GraphQL in Flink Applications" title="Direct link to Downsides of using GraphQL in Flink Applications">​</a></h2><p>Despite the many advantages of using GraphQL with Apache Flink, it's important to note that there are some potential downsides to this approach. Like any technology, GraphQL is not a silver bullet and there are certain challenges that developers may encounter when implementing it in their Flink applications.</p><p>Firstly, the very flexibility that makes GraphQL so appealing can also present challenges. The ability to request exactly the data you need means that your backend has to be prepared to handle a potentially wide variety of query structures. This can be difficult to implement, and it requires careful consideration of how your database is structured and indexed. We’ve seen a single missing index bring down an entire application because the database became overloaded. Not a fun day.</p><p>Secondly, while GraphQL's subscription model is a powerful tool for delivering real-time updates, it can also be difficult to implement with low latency. Especially in high-volume data environments like those typically handled by Flink. Ensuring that updates are delivered to clients as quickly as possible, without overwhelming the server or the network, can be a complex task that requires careful planning and optimization.</p><p>However, these challenges are not insurmountable. With the right tooling and a thoughtful approach to implementation, it's possible to take full advantage of the power and flexibility that GraphQL offers without optimizing the implementation by hand. DataSQRL is a tool that optimizes your Flink jobs as well as generates the database schema and index structures for your GraphQL API, compiling an entire data pipeline and saving you a ton of work. More on DataSQRL below.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="howto">How to Build GraphQL APIs with Flink<a href="#howto" class="hash-link" aria-label="Direct link to How to Build GraphQL APIs with Flink" title="Direct link to How to Build GraphQL APIs with Flink">​</a></h2><p>To sum up, the combination of Apache Flink and GraphQL provides a potent solution for data-driven applications. Like peanut butter and jelly, these two distinct technologies complement each other and create a powerful synergy. Flink processes the data, and GraphQL exposes it in a flexible, efficient, and real-time manner. GraphQL’s ability to provide flexible access to data APIs, push real-time data updates through subscriptions, and simplify event ingestion with mutations, makes it an ideal interface for Flink's data processing capabilities. Together, they enable developers to build robust, efficient, and user-friendly data applications. So, whether you're building a real-time analytics platform, a complex event-driven application, or a data-intensive microservice, consider the Flink-GraphQL combo.</p><img loading="lazy" src="/img/full_squirrel.svg" alt="DataSQRL >" width="30%" class="img_ev3q"><p>By now you are probably hungry to learn more. It may just be all the food analogies, but if you want to learn how to build GraphQL APIs with Flink then read on.</p><p>You can build Flink+GraphQL data pipelines by hand, but like many things in software, it gets a lot easier with the appropriate tooling. We recommend using <a href="/">DataSQRL</a> for building GraphQL APIs on top of Flink. DataSQRL compiles the SQL that defines your data processing in Flink and your GraphQL API into a complete data pipeline that integrates these components efficiently.</p><p>Of course, we are biased.  But, who knows, DataSQRL might save you a ton of work, time, and frustration.</p><p>Check out the <a href="/docs/getting-started/quickstart/">Quickstart tutorial</a> for a quick overview of how to consume and ingest data, process it with Flink, and expose the results through a GraphQL API.
Looking for a step-by-step guide to building your own GraphQL APIs with Flink? Check out the <a href="/docs/getting-started/quickstart/">quickstart tutorial</a> for a detailed explanation of all the steps.</p><p>And if you run into any issues or have questions, don’t hesitate to <a href="/community/">reach out</a>.</p><p>Happy coding, and may the match of GraphQL and Apache Flink turn your application into a delicious experience for your users!</p>]]></content:encoded>
            <category>GraphQL</category>
            <category>Flink</category>
            <category>DataSQRL</category>
        </item>
        <item>
            <title><![CDATA[Simplifying Apache Flink Application Development with DataSQRL]]></title>
            <link>https://www.datasqrl.com/blog/simplifying-flink-app-development</link>
            <guid>https://www.datasqrl.com/blog/simplifying-flink-app-development</guid>
            <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Apache Flink is an incredibly powerful stream processor. But to build a complete application with Flink you need to integrate multiple complex technologies which requires a significant amount of custom code.]]></description>
            <content:encoded><![CDATA[<p>Apache Flink is an incredibly powerful stream processor. But to build a complete application with Flink you need to integrate multiple complex technologies which requires a significant amount of custom code.
DataSQRL is an open-source tool that simplifies this process by compiling SQL into a data pipeline that integrates Flink, Kafka, Postgres, and API layer. </p><div style="float:right;width:40%"><iframe width="100%" height="100%" src="https://www.youtube.com/embed/mf5q-IdbVQY" title="DataSQRL Introduction" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div><p>DataSQRL allows you to focus on your application logic without getting bogged down in the details of how to execute your data transformations efficiently across multiple technologies.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-challenge-of-building-applications-with-flink">The Challenge of Building Applications with Flink<a href="#the-challenge-of-building-applications-with-flink" class="hash-link" aria-label="Direct link to The Challenge of Building Applications with Flink" title="Direct link to The Challenge of Building Applications with Flink">​</a></h2><p>We have built several applications in Flink: recommendation engines, data mesh endpoints, monitoring dashboards, Customer 360 APIs, smart IoT apps, and more. Across those use cases, Flink proved to be versatile and powerful in its ability to instantly analyze and aggregate data from multiple sources. But we found it quite difficult and time-consuming to build applications with Flink.</p><img loading="lazy" src="/img/reference/compiledMicroservice.svg" alt="DataSQRL compiled data pipeline >" width="50%" class="img_ev3q"><p>To start, you need to learn Flink: the table and datastream API, watermarking, windowing, and all the other stream processing concepts. Flink alone gets our heads spinning. And Flink is just one component of the application.</p><p>To build a complete data pipeline, you need Kafka to hold your streaming data and a database like Postgres to query the processed data. On top, you need an API layer that captures input data and provides access to the processed data. Your team must learn, implement, and integrate multiple complex technologies. It takes a village to build a Flink app.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="introducing-datasqrl-a-solution-for-streamlined-flink-development">Introducing DataSQRL: A Solution for Streamlined Flink Development<a href="#introducing-datasqrl-a-solution-for-streamlined-flink-development" class="hash-link" aria-label="Direct link to Introducing DataSQRL: A Solution for Streamlined Flink Development" title="Direct link to Introducing DataSQRL: A Solution for Streamlined Flink Development">​</a></h2><img loading="lazy" src="/img/full_squirrel.svg" alt="DataSQRL >" width="20%" class="img_ev3q"><p>That’s why we built <a href="/">DataSQRL</a>. DataSQRL compiles the SQL that defines your data processing into an integrated data pipeline that orchestrates Flink, Kafka, Postgres, and API - saving us a ton of time and headache in the process. Why not let the computer do all the hard work?</p><p>Let me show you how DataSQRL works by building an IoT monitoring service.</p><p>First, we implement the data processing for our monitoring service in SQL.</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">metrics.sqrl</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> datasqrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">example</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sensors</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">SensorReading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import sensor data</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">endOfSecond</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import time function</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Aggregate sensor readings to second */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SecReading :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> endOfSecond</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                     </span><span class="token function" style="color:rgb(80, 250, 123)">avg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">temperature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SensorReading</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Get max temperature in last minute */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SensorMaxTemp :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">temp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> maxTemp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> SecReading</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> timeSec </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">now</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MINUTE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>This script imports the metrics stream which are temperature readings collected by sensors. DataSQRL treats external data sources like software dependencies that you import, allowing the compiler to handle configuration management, data mapping, and schema evolution for us. We also import a time function we’ll use for aggregation.</p><p>Then we define two tables: The first table smoothes out the readings by taking the average temperature each second. The SensorMaxTemp table computes the maximum temperature for each sensor over the last minute.</p><p>Next, we are going to define the API for our monitoring service.  That’s how users of our service query the data.
The API is defined by a GraphQL schema. It specifies the query endpoints and result types.</p><div class="language-graphql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_Ktv7">metricsapi.graphqls</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-graphql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">Query</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">SecReading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">SecReading</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">SensorMaxTemp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token class-name">SensorMaxTemp</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">SecReading</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">timeSec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">String</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">temp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Float</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">SensorMaxTemp</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">sensorid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Int</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attr-name" style="color:rgb(241, 250, 140)">maxTemp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token scalar">Float</span><span class="token operator">!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Note, how the tables map to query endpoints and types. That’s how it all fits together.</p><p>And don’t worry, you don’t have to write this schema - DataSQRL can generate it for you from the SQL script.</p><p>DataSQRL compiles the script and GraphQL schema into an integrated data pipeline with the following command:</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run --rm -v </span><span class="token environment constant" style="color:rgb(189, 147, 249)">$PWD</span><span class="token plain">:/build datasqrl/cmd compile metrics.sqrl metricsapi.graphqls</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>It also generates a docker-compose template to stand up the entire service.</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cd build/deploy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> compose up</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We can now interact with the API and try it out by opening <a href="http://localhost:8888/graphiql/?query=query%20MaxTemp%20%7B%0A%20%20SensorMaxTemp%20%7B%0A%20%20%20%20sensorid%0A%20%20%20%20maxTemp%0A%20%20%7D%0A%7D%0A&amp;operationName=MaxTemp" target="_blank" rel="noopener noreferrer">http://localhost:8888/graphiql/</a>.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="datasqrl-does-the-work-for-you">DataSQRL Does the Work for You<a href="#datasqrl-does-the-work-for-you" class="hash-link" aria-label="Direct link to DataSQRL Does the Work for You" title="Direct link to DataSQRL Does the Work for You">​</a></h2><p>Pretty simple, right? And the best part is that DataSQRL compiles deployment artifacts for each component that you can inspect and deploy anywhere. There is no magic or black box.
For example, DataSQRL compiles a Flink jar you can execute on an existing Flink cluster or Flink managed service.</p><img loading="lazy" src="/img/generic/undraw_launch.svg" alt="DataSQRL Does the Work >" width="30%" class="img_ev3q"><p>Most importantly, consider all the work we didn’t have to do. No data source configuration, watermark setting, Kafka integration, database schema definition, index structure selection, API implementation, and so on. DataSQRL compiles all that for you.</p><p>DataSQRL also supports inserts to ingest events, subscriptions to push data updates in real-time to the client, and exporting data to Kafka topics or downstream systems. Take a look at the <a href="/docs/getting-started/quickstart/">Quickstart tutorial</a> which shows you how to do that - it only takes a few minutes.</p><p>DataSQRL is an <a href="https://github.com/DataSQRL/sqrl" target="_blank" rel="noopener noreferrer">open-source project</a>. If you like the idea of DataSQRL, have questions, or need help building your streaming application, <a href="/community/">don’t hesitate to reach out</a>.</p><p>To sum up, DataSQRL is a tool for simplifying the development of Apache Flink applications by automating the integration of various technologies and allowing developers to focus on their application logic. It makes Flink accessible to lazy developers like us.</p><p>Have fun building applications with Flink!</p>]]></content:encoded>
            <category>DataSQRL</category>
            <category>Flink</category>
        </item>
        <item>
            <title><![CDATA[SQRL: Enhancing SQL to a High-Level Data Language]]></title>
            <link>https://www.datasqrl.com/blog/sqrl-high-level-data-language-sql</link>
            <guid>https://www.datasqrl.com/blog/sqrl-high-level-data-language-sql</guid>
            <pubDate>Mon, 22 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[When creating data-intensive applications or services, your data logic (i.e. the code that defines how to process the data) gets fragmented across multiple data systems, languages, and mental models. This makes data-driven applications difficult to implement and hard to maintain.]]></description>
            <content:encoded><![CDATA[<p>When creating data-intensive applications or services, your data logic (i.e. the code that defines how to process the data) gets fragmented across multiple data systems, languages, and mental models. This makes data-driven applications difficult to implement and hard to maintain.</p><p>SQRL is a high-level data programming language that compiles into executables for all your data systems, so you can implement your data logic in one place. SQRL adds support for data streams and relationships to SQL while maintaining its familiar syntax and semantics.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-do-we-need-sqrl">Why Do We Need SQRL?<a href="#why-do-we-need-sqrl" class="hash-link" aria-label="Direct link to Why Do We Need SQRL?" title="Direct link to Why Do We Need SQRL?">​</a></h2><img loading="lazy" src="/img/reference/reactive_data_layer.svg" alt="Data Layer of data-driven application >" width="30%" class="img_ev3q"><p>The data layer of a data-driven application comprises multiple components: There’s the good ol’ database for data storage and queries, a server for handling incoming data and translating API requests into database queries, a queue/log for asynchronous data processing, and a stream processor for pre-processing and writing new data to the database. Consequently, your data processing code becomes fragmented across various systems, technologies, and languages.</p><p>For example, consider a project I was once working on. We were building a data product integrating customer data from various silos into a data API for a mobile app. The objective was to provide customers with an integrated view of their service and billing history, support requests, profile information, etc. This is a typical "Customer 360°" challenge many large organizations face when customer data is spread across numerous operational systems.</p><p>The data layer of that service consisted of a bunch of scripts ingesting customer data from CDC (change-data-capture) streams, a SQL database for data persistence, and a Java-based API server.</p><p>The data logic of this application was trivial: some translation of billing codes and aggregation of service items, but mostly it was straight-forward mapping of data. Yet, it took the team multiple months to build a prototype because of all the code fragmentation and glue code we had to write to stitch the components together. Integration testing was a big pain. And a simple sprint ticket to add a customer field took a week to implement and test.</p><img loading="lazy" src="/img/blog/tower-of-babel.jpg" alt="The Tower of Babel >|" width="35%" class="img_ev3q"><p>The worst part was all the miscommunication. See, each component of the data layer has a different mental model.
For the data ingestion and pre-processing, the developers thought in terms of events and streams. For the database modeling and querying, the developers thought in terms of rows and tables. And for the API implementation, the developers thought in terms of objects and classes.</p><p>It felt like we were building the Tower of Babel. Everybody was speaking a different language and we couldn’t understand each other. But with a twist: We thought we understood each other until it was time to integrate the components and we discovered a mismatch in how we represented the data. That’s a type of punishment not even a jealous God will dish out.</p><p>To save ourselves from this tedious work and mental gymnastics, we built SQRL as a high-level data programming language for implementing the data logic of your application in one place.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="introducing-sqrl">Introducing SQRL<a href="#introducing-sqrl" class="hash-link" aria-label="Direct link to Introducing SQRL" title="Direct link to Introducing SQRL">​</a></h2><p>SQRL enhances SQL. If you’ve used SQL before, we hope that you find it easy to pick up SQRL. And if not, there is always ChatGPT 😜.</p><p>Let’s take a look at a SQRL script implementing a Customer 360° API that integrates and aggregates customer and order data:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> datasqrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">seedshop</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> datasqrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">seedshop</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Clean orders data and compute subtotals */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">discount0 :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">coalesce</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">discount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">0.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">total    :</span><span class="token operator">=</span><span class="token plain"> quantity </span><span class="token operator">*</span><span class="token plain"> unit_price </span><span class="token operator">-</span><span class="token plain"> discount0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">totals         :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">total</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> price</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                         </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">discount0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> saving </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Deduplicate customer CDC stream */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Customers :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> Customers </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Create relationship between Customers and Orders */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">purchases :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Orders </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">customerid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Aggregate customer spending by state */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">spending :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">price</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> spend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                             </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">saving</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> saved</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.purchases.totals</span><span class="token plain"> t </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>This script imports customer data and order streams. It processes data in multiple steps, culminating in an aggregated spending analysis by state.</p><p>And that’s all you have to implement to get a functioning customer 360° API. DataSQRL compiles this script into executables for all your data systems and handles data mapping and schema synchronization between them.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="sqrl-features-overview">SQRL Features Overview<a href="#sqrl-features-overview" class="hash-link" aria-label="Direct link to SQRL Features Overview" title="Direct link to SQRL Features Overview">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="simple-syntax">Simple Syntax<a href="#simple-syntax" class="hash-link" aria-label="Direct link to Simple Syntax" title="Direct link to Simple Syntax">​</a></h3><p>The first thing you notice is the syntactic sugar that SQRL adds to SQL.</p><p>It allows you to define the data sources that you are importing into your script so that a package manager can handle data access configuration and schema management.</p><p>It uses the <code>:=</code> assignment operator to define new tables and allows incremental column definitions.</p><p>The goal is to make SQRL feel a little more like a development language where you build your data logic as a sequence of small, incremental steps instead of writing one massive query.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="nested-data">Nested Data<a href="#nested-data" class="hash-link" aria-label="Direct link to Nested Data" title="Direct link to Nested Data">​</a></h3><p>Nested data, like JSON documents, is ubiquitous in data-driven applications. It’s how we exchange data. It’s how we expose data in APIs.</p><p>SQRL provides native support for nested data by representing it as child tables, accessed through the familiar "." dot notation.</p><p>In the example, we sum up the price and saving for all items in an order:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">totals :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">total</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> price</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">discount0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> saving </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>There are a couple of things happening here:</p><ul><li>We define a new nested table in <code>Orders</code> called <code>totals</code> that contains the aggregates</li><li>The <code>FROM</code> clause <code>@.items</code> selects the items from <strong>each</strong> order. The special table handle <code>@</code> refers to the parent table in the local context, i.e. <code>Orders</code> in this example.</li></ul><p>Being able to write queries within a nested context makes it possible to process tree-structured data within SQL.</p><p>For example, when we define the <code>totals</code> column for each item in an order, we can refer to the other columns of <code>items</code> within the local context:</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">total :</span><span class="token operator">=</span><span class="token plain"> quantity </span><span class="token operator">*</span><span class="token plain"> unit_price </span><span class="token operator">-</span><span class="token plain"> discount0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Nested data support simplifies data consumption from external sources and result data mapping to API calls, eliminating a significant amount of mapping and data transformation code.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="relationships">Relationships<a href="#relationships" class="hash-link" aria-label="Direct link to Relationships" title="Direct link to Relationships">​</a></h3><p>SQRL adds relationships to SQL. You can define relationship columns on tables that relate to rows in other tables using the familiar JOIN syntax.</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">purchases :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Orders </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> Orders</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">customerid </span><span class="token operator">=</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">@.id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Making relationships explicit in SQL simplifies joins and adds structure to the data that is exposed in the API without separate mapping logic.</p><p>For example, the <code>FROM</code> clause of the spending analysis query uses the relationship expression <code>@.purchases.totals</code> to select from the nested <code>totals</code> table of the purchase orders for each customer. It eliminates a double-join and makes the query easier to read.</p><p>Support for relationships and nested data makes it convenient to handle inter-related data and bridges the gap between the relational data model and the tree or object-relationship structure we use in our APIs and applications.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="stream-processing">Stream Processing<a href="#stream-processing" class="hash-link" aria-label="Direct link to Stream Processing" title="Direct link to Stream Processing">​</a></h3><img loading="lazy" src="/img/blog/data_stream.jpg" alt="Matrix Data Stream >|" width="40%" class="img_ev3q"><p>SQRL introduces support for stream tables to ingest external data streams and react to data changes. Data streams are an important part of data-driven applications. It’s how we consume data from other systems or applications and communicate changes in data to subscribers.</p><p>Unlike normal SQL tables where records can change over time, a stream table has immutable records that are fixed in time. As we saw with the orders stream in our example, SQRL makes it easy to process stream data in steps.</p><p>SQRL has operators to convert between stream tables and state tables. Our customer 360° script uses the <code>DISTINCT</code> operator to convert a CDC stream into a state table. The <code>STREAM</code> operator creates a change stream from a state table, so you can react to changes in state.</p><p>In addition, SQRL overloads the <code>JOIN</code> operator to support time-consistent joins between state and stream tables. For example, consider the join between the <code>Customers</code> and <code>Orders</code> tables in the spending analysis query. We want to join the <code>Orders</code> stream with the state of the <code>Customers</code> table <strong>at the time</strong> of a particular order, so that we aggregate by the state that the customer lived in when the order was placed. If we had used an <code>INNER JOIN</code>, the state would update every time the customer moved and the query would aggregate all orders under the state the customer currently lives in.</p><p>Making stream tables a first-class citizen in SQL allows us to process stream data, react to changes in data, and bridge the mental model between the set semantics of the relational world and the event orientation of streams.</p><p>Take a look at the <a href="/docs/intro/">documentation</a> for a more detailed rundown of all the features SQRL adds to SQL.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="help-us-design-sqrl">Help Us Design SQRL<a href="#help-us-design-sqrl" class="hash-link" aria-label="Direct link to Help Us Design SQRL" title="Direct link to Help Us Design SQRL">​</a></h2><p>To take SQRL for a spin and learn how to build data-driven applications, we recommend you start with the <a href="/docs/getting-started/quickstart/">Quickstart tutorial</a>. If you have questions, we are happy to answer them on <a href="/community/">Slack</a>.</p><p>SQRL is still young, and we would love to hear <a href="/community/">your feedback</a> on the language to shape its future.</p>]]></content:encoded>
            <category>SQRL</category>
            <category>community</category>
        </item>
        <item>
            <title><![CDATA[Let's Uplevel Our Database Game: Meet DataSQRL]]></title>
            <link>https://www.datasqrl.com/blog/lets-uplevel-database-datasqrl</link>
            <guid>https://www.datasqrl.com/blog/lets-uplevel-database-datasqrl</guid>
            <pubDate>Mon, 15 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[We need to make it easier to build data-driven applications. Databases are great if all your application needs is storing and retrieving data. But if you want to build anything more interesting with data - like serving users recommendations based on the pages they are visiting, detecting fraudulent transactions on your site, or computing real-time features for your machine learning model - you end up building a ton of custom code and infrastructure around the database.]]></description>
            <content:encoded><![CDATA[<p><strong>We need to make it easier to build data-driven applications.</strong> Databases are great if all your application needs is storing and retrieving data. But if you want to build anything more interesting with data - like serving users recommendations based on the pages they are visiting, detecting fraudulent transactions on your site, or computing real-time features for your machine learning model - you end up building a ton of custom code and infrastructure around the database.</p><a href="https://www.youtube.com/watch?v=m5uYtBFSmUs&amp;ab_channel=DataSQRL" target="_blank" rel="noopener noreferrer"><img loading="lazy" src="/img/blog/uplevel_play_image.jpg" alt="Watch the video version >|" width="50%" class="img_ev3q"></a><p>You need a queue like Kafka to hold your events, a stream processor like Flink to process data, a database like Postgres to store and query the result data, and an API layer to tie it all together.</p><p>And that’s just the price of admission. To get a functioning data layer, you need to make sure that all these components talk to each other and that data flows smoothly between them. Schema synchronization, data model tuning, index selection, query batching … all that fun stuff.</p><p>The point is, you need to do a ton of data plumbing if you want to build a data-driven application. All that data plumbing code is time-consuming to develop, hard to maintain, and expensive to operate.</p><p>We need to make building with data easier. That’s why we are sending out this call to action to uplevel our database game. <strong>Join us in figuring out how to simplify the data layer.</strong></p><p>We have an idea to get us started: Meet DataSQRL.</p><img loading="lazy" src="/img/full_squirrel.svg" alt="DataSQRL >" width="30%" class="img_ev3q"><h2 class="anchor anchorWithStickyNavbar_LWe7" id="introducing-datasqrl">Introducing DataSQRL<a href="#introducing-datasqrl" class="hash-link" aria-label="Direct link to Introducing DataSQRL" title="Direct link to Introducing DataSQRL">​</a></h2><p>DataSQRL is a build tool that compiles your application’s data layer from a high-level data development language, dubbed SQRL.</p><p>Our goal is to create a new abstraction layer above the low-level languages often used in data layers, allowing a compiler to handle the tedious tasks of data plumbing, infrastructure assembly, and configuration management.</p><p>Much like how you use high-level languages such as Javascript, Python, or Java instead of Assembly for software development, we believe a similar approach should be used for data. </p><p>SQRL is designed to be a developer-friendly version of SQL, maintaining familiar syntax while adding features necessary for building data-driven applications, like support for nested data and data streams.</p><p>Check out this simple SQRL script to build a recommendation engine from clickstream data.</p><div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IMPORT</span><span class="token plain"> datasqrl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">example</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">clickstream</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Click</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Import data</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Find next page visits within 10 minutes */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VisitAfter :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">url </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> beforeURL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">url </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> afterURL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                     a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> Click b </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> Click a </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid</span><span class="token operator">=</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">userid </span><span class="token operator">AND</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token operator">&lt;=</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token operator">AND</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">timestamp</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MINUTE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/* Recommend pages that are visited shortly after */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Recommendation :</span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> beforeURL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> afterURL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> rec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                         </span><span class="token function" style="color:rgb(80, 250, 123)">count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> frequency </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> VisitAfter</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rec </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> url </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ASC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> frequency </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>This little SQRL script imports clickstream data, identifies pairs of URLs visited within a 10-minute interval, and compiles these pairs into a set of recommendations, ordered by the frequency of co-visits.</p><img loading="lazy" src="/img/reference/reactive_data_layer.svg" alt="Reactive Data Layer Compiled by DataSQRL >" width="30%" class="img_ev3q"><p>DataSQRL then takes this script and compiles it into an integrated data layer, complete with all necessary data plumbing pre-installed. It configures access to the clickstream. It generates an executable for the stream processor that ingests, validates, joins, and aggregates the clickstream data. It creates the data model and writes the aggregated data to the database. It synchronizes timestamps and schemas between all the components. And it compiles a server executable that queries the database and exposes the computed recommendations through a GraphQL API.</p><p><strong>The bottom line: These 9 lines of SQRL code can replace hundreds of lines of complex data plumbing code and save hours of infrastructure setup.</strong></p><p>We believe that all this low-level data plumbing work should be done by a compiler since it is tedious, time-consuming, and error-prone. Let’s uplevel our data game, so we can focus on <strong>what</strong> we are trying to build with data and less on the <strong>how</strong>.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="join-us-on-this-journey">Join Us on this Journey<a href="#join-us-on-this-journey" class="hash-link" aria-label="Direct link to Join Us on this Journey" title="Direct link to Join Us on this Journey">​</a></h2><img loading="lazy" src="/img/blog/undraw_collaboration.svg" alt="Join DataSQRL Community >" width="50%" class="img_ev3q"><p>We have the ambitious goal of designing a higher level of abstraction for data to enable millions of developers to build data-driven applications.</p><p>We <a href="https://github.com/DataSQRL/sqrl/releases/tag/v0.1.0" target="_blank" rel="noopener noreferrer">just released</a> the first version of DataSQRL, and we recognize that we are at the beginning of a long, long road. We need your help. If you are a data nerd, like building with data, or wish it was easier, please <a href="/community/">join us on this journey</a>. DataSQRL is an open-source project, and all development activity is transparent.</p><p>Here are some ideas for how you can contribute:</p><ul><li>Share your thoughts: Do you have ideas on how we can improve the SQRL language or the DataSQRL compiler? Jump into <a href="/community/">our slack community</a> and let us know!</li><li>Test the waters: Do you like playing with new technologies? Try out <a href="/docs/getting-started/quickstart/">DataSQRL</a> and let us know if you find any bugs or missing features.</li><li>Spread the word: Think DataSQRL has potential? Share this blog post and <a href="https://github.com/DataSQRL/sqrl" target="_blank" rel="noopener noreferrer">star</a> DataSQRL on <a href="https://github.com/DataSQRL/sqrl" target="_blank" rel="noopener noreferrer">Github</a>. Your support can help us reach more like-minded individuals.</li><li>Code with us: Do you enjoy contributing to open-source projects? Dive into <a href="https://github.com/DataSQRL/sqrl" target="_blank" rel="noopener noreferrer">the code</a> with us and pick up a <a href="https://github.com/DataSQRL/sqrl/issues" target="_blank" rel="noopener noreferrer">ticket</a>.</li></ul><p>Let’s uplevel our database game. With your help, we can make building with data fun and productive.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="more-information">More Information<a href="#more-information" class="hash-link" aria-label="Direct link to More Information" title="Direct link to More Information">​</a></h2><p>You probably have a ton of questions now. How do I import my own data? How do I customize the API? How do I deploy SQRL scripts to production? How do I import functions from my favorite programming language?</p><p>Those are all great questions. Check out <a href="/">datasqrl.com</a> for answers, <a href="/community/">join the community</a> to ask us, or wait for a future blog post where we dive into all of those topics.</p>]]></content:encoded>
            <category>DataSQRL</category>
            <category>community</category>
        </item>
        <item>
            <title><![CDATA[DataSQRL 0.1: A SQRL is born]]></title>
            <link>https://www.datasqrl.com/blog/datasqrl-01-release</link>
            <guid>https://www.datasqrl.com/blog/datasqrl-01-release</guid>
            <pubDate>Thu, 11 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[After two long years of research, development, and teamwork, we're excited to announce DataSQRL 0.1! DataSQRL is a tool for building APIs from your data streams and datasets by defining your use case in an SQL.]]></description>
            <content:encoded><![CDATA[<p>After two long years of research, development, and teamwork, we're excited to announce DataSQRL 0.1! <a href="/">DataSQRL</a> is a tool for building APIs from your data streams and datasets by defining your use case in an SQL.</p><img loading="lazy" src="/img/blog/datasqrlv0.1.jpeg" alt="DataSQRL v0.1 release: A SQRL is Born >" width="40%" class="img_ev3q"><p>This is our first “official” release of DataSQRL after many months of testing and bug-fixing. <br>
Check out the <a href="https://github.com/DataSQRL/sqrl/releases/tag/v0.1.0" target="_blank" rel="noopener noreferrer">release notes</a> on GitHub for a rundown of all the features.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="our-vision">Our Vision<a href="#our-vision" class="hash-link" aria-label="Direct link to Our Vision" title="Direct link to Our Vision">​</a></h2><p>Every time we wanted to build a new use case for our application and expose a data API, we found ourselves getting distracted. Distracted by all the orchestration, the technology choices, all the micro-decisions, and the 'plumbing' that goes into the modern data layer. So we up-leveled the abstraction and kept it simple. We <a href="/docs/getting-started/concepts/why-datasqrl/">designed DataSQRL</a> to handle those nitty-gritty details, so you could stay focused on what truly mattered - building cool things.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="simplicity-through-sql">Simplicity Through SQL<a href="#simplicity-through-sql" class="hash-link" aria-label="Direct link to Simplicity Through SQL" title="Direct link to Simplicity Through SQL">​</a></h2><p>We've kept <a href="/docs/getting-started/concepts/datasqrl/">DataSQRL true to SQL</a>, so it feels familiar and easy to use. We enhanced and modernized the language while maintaining the simplicity of SQL queries. No more wrestling with subqueries, window functions, or repetitive joins - just straightforward SQL.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="flexible-apis">Flexible APIs<a href="#flexible-apis" class="hash-link" aria-label="Direct link to Flexible APIs" title="Direct link to Flexible APIs">​</a></h2><p>One size doesn't fit all when it comes to APIs. We made DataSQRL non-opinionated, giving you the freedom to use your preferred GraphQL schema and customize your query patterns with SQRL scripts.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="our-road-ahead">Our Road Ahead<a href="#our-road-ahead" class="hash-link" aria-label="Direct link to Our Road Ahead" title="Direct link to Our Road Ahead">​</a></h2><p>We're seeking <a href="/community/">your feedback</a> to help shape the future of DataSQRL. Our current architecture supports a range of platforms, and we're working on making it more extensible and useful. Your input is invaluable as we continue to refine and expand DataSQRL's capabilities.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2><p>The only danger now is that your boss might think he can start coding again. <a href="/community/">Join us</a> as we explore the story behind DataSQRL, its impact on the world of data processing, and the exciting possibilities it holds for the future.</p>]]></content:encoded>
            <category>DataSQRL</category>
            <category>release</category>
        </item>
        <item>
            <title><![CDATA[The Two Core Data Problems for Developers: Transactional & Reactive]]></title>
            <link>https://www.datasqrl.com/blog/types-of-data-problems-transactional-reactive</link>
            <guid>https://www.datasqrl.com/blog/types-of-data-problems-transactional-reactive</guid>
            <pubDate>Mon, 01 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Introduction]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithStickyNavbar_LWe7" id="introduction">Introduction<a href="#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction">​</a></h2><p>Every developer, whether you build applications or backend services, encounters two distinct types of data problems: transactional and reactive. The need to store and retrieve application state is a quintessential example of a transactional data problem. Conversely, when you're processing events or consuming data from external sources, you're confronted with a reactive data problem. </p><p>Knowing which problem you're up against is crucial to selecting the right tools from your developer's kit. It’s important to determine what type of data problem you are dealing with to choose the right tools and approaches for implementing a solution. After all, using a hammer for a screw job can leave you with more than a few cracks to mend.</p><p>In this post, we'll guide you on how to differentiate between transactional and reactive data problems and pick the right tools and strategies to solve each of them.</p><p>Table of Contents:</p><ul><li><a href="#pitfall">The Pitfall of Misinterpreting Reactive Problems as Transactional</a></li><li><a href="#what-tx">What are Transactional Data Problems?</a></li><li><a href="#solve-tx">Solving Transactional Data Problems</a></li><li><a href="#what-rx">What are Reactive Data Problems?</a></li><li><a href="#solve-rx">Solving Reactive Data Problems</a></li><li><a href="#conclusion">Conclusion</a></li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="pitfall">The Pitfall of Misinterpreting Reactive Problems as Transactional<a href="#pitfall" class="hash-link" aria-label="Direct link to The Pitfall of Misinterpreting Reactive Problems as Transactional" title="Direct link to The Pitfall of Misinterpreting Reactive Problems as Transactional">​</a></h2><img loading="lazy" src="/img/blog/arrived_logo.png" alt="Arrived Logo >" width="30%" class="img_ev3q"><p>Let's kick things off with an anecdote from my career. An episode where I mistakenly treated a reactive data problem as a transactional one, resulting in a full-blown application meltdown. Definitely not a shining moment of my career.</p><p>Flashback to 2011, I was the backend developer for a sprouting startup named "Arrived". Our vision was to connect people in the real world by using their phone’s location data. That was the time when smartphones started to support geo-fencing and folks thought Foursquare was going to become the next Facebook. Fun times.</p><p>We built an iPhone app that allowed users to establish geo-fences and automatically check-in, alerting their connections upon entry. For a brief overview of the app, check out <a href="https://techcrunch.com/2011/11/10/i-am-a-passenger-and-i-arrive-and-arrived/" target="_blank" rel="noopener noreferrer">this brutally hilarious review</a> from our TechCrunch Disrupt final presentation. While it was soul-crushing at the time, it's quite a fun read in retrospect.</p><p>I implemented the backend of the application as a Java web service, complete with a REST API for user creation, user connection management, and alert dispatch. The API primarily dealt with storing and retrieving user states, connections, geo-fences, and more. These are typical transactional data problems: how to maintain your application state in a durable, reliable, and consistent way. To tackle this, I used MySQL as the database and an object-relational mapping library to translate my Java objects to database rows.</p><p>All was sailing smoothly until we decided to implement a social feature to enhance the onboarding experience and boost the app's "virality". This feature uploaded a user’s phone contacts to check if any of their contacts were already using Arrived, suggesting them as potential connections.</p><img loading="lazy" src="/img/blog/arrived_app.png" alt="Arrived Mobile App Screenshot >|" width="30%" class="img_ev3q"><p>That looked like another transactional data problem to me. Or rather, I was oblivious to the existence of other types of data problems and defaulted to it being transactional.</p><p>Thus, I embarked on a path that would eventually lead to disaster. The "upload contacts" API call I set up did the following: </p><ul><li>stored all contact entries in the database, </li><li>ran a for-loop to match any entry hashes already in the database, </li><li>added a "potential connection" record to another table in case of a match.</li></ul><p>To my credit, the feature worked as intended. I even had a passing test case. But once we launched the feature in production, our database froze.</p><p>As it turned out, some users had an expansive social circle with over a thousand contacts. Running a transaction that writes thousands of records and fires off as many read queries on your primary operational database, which also services all your API requests, is a recipe for disaster. Needless to say, the database was not a fan of this idea and promptly crashed.</p><p>But my mistake was not a coding error. The code worked fine. The mistake was failing to realize that the “contacts matching feature” was a reactive data problem, not a transactional one. We were ingesting data from an external source - a user’s contact list - and reacting to it by comparing matches against our existing user base.</p><p>In the upcoming sections, we'll delve deeper into the differences between transactional and reactive data problems and how to solve them. We'll also revisit my reactive data problem and explore how a more informed approach could have saved me from a full-blown, hair-on-fire database crisis.</p><p>We will discuss how I could have solved my reactive data problem better and avoided a hair-on-fire database resurrection after we explore transactional and reactive data problems in more detail and how to distinguish between them.</p><p>As for "Arrived", we learned that our most active users were over-vigilant parents monitoring their children, which was not our target audience. Consequently, we had to close shop in less than two years.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-tx">What are Transactional Data Problems?<a href="#what-tx" class="hash-link" aria-label="Direct link to What are Transactional Data Problems?" title="Direct link to What are Transactional Data Problems?">​</a></h2><p>Transactional data problems arise when you need to store and retrieve data concurrently while maintaining consistency. Here, "concurrently" refers to the simultaneous reading and writing of data by multiple threads or users. The trick is to ensure that data remains consistent throughout this flurry of updates.</p><p>There are two forms of inconsistencies we need to avoid. The first relates to upholding application constraints. For instance, if a username is required to be unique, we cannot allow two user records with identical usernames. This would be inconsistent with our application's unique username constraint. We may have several such constraints, like "account balances can't be negative," or "each product id in the orders table must correspond to an existing row in the product table."</p><p>The second inconsistency type relates to multiple updates triggered by a single request. We want to dodge situations where only some updates are stored. It's an all-or-nothing game - we either want all updates to be stored or none at all. For example, a request to transfer $100 from account A to account B requires updating both account balances. If only account A's balance is updated while account B's update fails, we've got a magical disappearing act of money.</p><p>Ensuring data consistency while managing concurrent user updates can be quite a challenge. You might encounter scenarios where two users try to register with the same username simultaneously or two users attempt to withdraw from the same account, causing the balance to plummet below zero. Situations like these are why data storage and retrieval often turn into a "problem" for developers.</p><p>Transactional data problems typically surface when storing state for applications that multiple users can access concurrently, or when building CRUD APIs.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="solve-tx">Solving Transactional Data Problems<a href="#solve-tx" class="hash-link" aria-label="Direct link to Solving Transactional Data Problems" title="Direct link to Solving Transactional Data Problems">​</a></h2><p>The panacea for transactional data problems? Databases. Developers harness the power of databases to efficiently handle the concurrency and consistency issues associated with transactional data problems.</p><p>However, databases aren't one-size-fits-all. They differ in the types of consistency and concurrency guarantees they offer. If you're using any of the popular relational databases (like Postgres, MySQL, SqlServer, Oracle, and Aurora), breathe easy. They're likely equipped with all the support you need. For other databases, it's worth checking what exactly they support to avoid surprises down the line.</p><p>Alongside the choice of database, you'll also want to equip yourself with a tool that simplifies interactions with the database from your programming language. Wrestling with databases directly can be cumbersome, requiring the use of drivers, query string writing, and data mapping. If you're working in an object-oriented programming language, an object-relational mapping layer (or ORM for short) can be your best friend, translating seamlessly between your application and the database. If not, seek out an SDK or database abstraction layer that's compatible with your chosen database.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-rx">What are Reactive Data Problems?<a href="#what-rx" class="hash-link" aria-label="Direct link to What are Reactive Data Problems?" title="Direct link to What are Reactive Data Problems?">​</a></h2><p>You have a Reactive data problem when your data source is outside your application or service's control, and you're required to respond to the data quickly. Let’s break this down.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="unconstrained-data">Unconstrained Data<a href="#unconstrained-data" class="hash-link" aria-label="Direct link to Unconstrained Data" title="Direct link to Unconstrained Data">​</a></h3><p>When the data originates from an external source or isn't subject to any application constraints, your application does not control the data source. External data sources could include other systems like logs, message queues, files, external databases, or applications. Here, the data pre-exists independently of your control. For instance, a user's contact list is an external data source.</p><p>Moreover, data could be uncontrolled even within your application, provided it's free of any constraints. This includes events that occur organically within your application, such as a user clicking a button or visiting a webpage. These events aren't within your direct control - they just happen.</p><p>This is a stark contrast to transactional data problems, where the key challenge lies in maintaining data consistency amidst concurrent updates.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-need-for-speed">The Need for Speed<a href="#the-need-for-speed" class="hash-link" aria-label="Direct link to The Need for Speed" title="Direct link to The Need for Speed">​</a></h3><p>Another characteristic of reactive data problems is the necessity for swift data processing and result generation. This quick reaction is twofold: it must occur shortly after receiving the data, and it involves computational processing of that data.</p><p>Take the contacts matching feature in Arrived, for instance. The goal was to encourage users to establish connections during the signup process. Consequently, we had to compute the matches within a few seconds - before the user completed the signup and exited the app.</p><p>How swift does this reaction need to be to qualify as "quick"? It varies according to your use case. Customer-facing use cases typically demand reactions within seconds to minutes, tops. For use cases like fraud detection, system automation, or financial transactions, you may need to respond within milliseconds. If the results can wait for hours or even days, it wouldn't qualify as quick.</p><p>The "reaction" element primarily involves generating a response to incoming data, which could either serve back to the user or trigger an action. This could mean processing a user's shopping cart to suggest other products they might like, analyzing system metrics to detect potential overload, feeding user activity into a machine learning model for a personalized journey, or evaluating if a transaction request is fraudulent. In each instance, we take a piece of data, evaluate it within the application context, and produce a useful response.</p><p>In essence, reactive data problems call for quick, efficient responses to one or multiple data sources. The challenges arise from the need to carry out data-intensive computations rapidly, efficiently, and robustly.</p><p>Reactive data problems commonly crop up in use cases such as:</p><ul><li>Personalization or recommendation engines</li><li>User experience features</li><li>Metrics or time-series analysis</li><li>Machine learning features</li><li>Fraud detection</li><li>Cybersecurity and intrusion detection</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="solve-rx">How do you solve Reactive Data Problems?<a href="#solve-rx" class="hash-link" aria-label="Direct link to How do you solve Reactive Data Problems?" title="Direct link to How do you solve Reactive Data Problems?">​</a></h2><p>Let's circle back to the reactive data problem of the contacts matching feature. My initial solution involved splitting the transaction into several parts, moving some computation to a background thread, fine-tuning the database schema, and writing a hefty amount of SQL. This strategy was time-consuming, fragile, and a nightmare to maintain. A colleague shrugged it off with “I’ve no idea what’s happening here, but I guess it works…”</p><p>The problem? When all you have is a hammer, everything looks like a nail. So, I tried hammering that screw into the wall. As expected, it was neither pretty nor productive.</p><img loading="lazy" src="/img/blog/reactive_data_layer_arrived.svg" alt="A Reactive Data Layer Architecture >" width="40%" class="img_ev3q"><p>To solve reactive data problems more effectively, we need to reimagine our data layer architecture. Let’s give our database some company by introducing additional components that make it easier to process data reactively:</p><ul><li><strong>Queue</strong>: Introducing a persisted message queue (or log) into our architecture can ease asynchronous data processing. This robust, scalable tool allows you to write incoming data to the queue and process it when resources are available, significantly simplifying multi-step data processing.</li><li><strong>Stream Processor</strong>: This is a framework dedicated to managing consecutive data processing steps, from data ingestion (reading data off the queue or from external systems) to data transformation, to writing the results to the database. This framework handles all task scheduling and execution, allowing you to focus on the actual processing logic.</li><li><strong>Server</strong>: This component accepts incoming data, writes it to the queue, and serves the processed data from the database. Acting as the entry and exit point for the reactive data layer, the server brings everything together. You can integrate this functionality into an existing API server implementation or create a standalone server to isolate the reactive data use case into its own backend service.</li></ul><p>To improve the contact matching feature, we finally adopted a reactive data architecture as illustrated in the diagram above. Here's how the data flowed:</p><ol><li>The server received the submitted contacts and wrote the entire data blob to a persisted messaging queue.</li><li>Three asynchronous tasks ran in the processing framework:<ol><li><em>Splitter</em>: This task read an entire contacts list from the queue, divided it into chunks of a maximum of 50 contacts, and wrote the resulting chunks back to the queue under a different topic.</li><li><em>Storer</em>: This task read contact chunks from the queue and wrote the contact entries as individual records to the database.</li><li><em>Matcher</em>: This task read contact chunks, matched the contact entries against the user table, and wrote the found matches to the database.</li></ol></li><li>The database stored contact entries and contact matches in a separate logical database, isolated from the main operational database serving the CRUD API of the Arrived app.</li><li>The server responded to "recommended contacts" requests during the signup process by running a query against the database that combined all pre-computed matches for the user with matches from checking the user's phone number against previously stored contact entries.</li></ol><p>This solution was not only efficient and robust but also easier to maintain. Above all, it allowed us to concentrate on enhancing the feature instead of hacking around the database.</p><p>So, here's the key takeaway: instead of grappling with reactive data problems using a database alone, build a reactive data layer. It will save you considerable time and frustration.</p><p>If building a custom data layer seems intimidating, consider checking out DataSQRL. It's a tool that constructs reactive data layers for you. We've been developing DataSQRL to assist developers in resolving reactive data problems quickly and efficiently. We would love to hear your feedback!</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2><table><thead><tr><th>Type of Data Problem</th><th>Transactional</th><th>Reactive</th></tr></thead><tbody><tr><td>Response time expectation</td><td>Milliseconds</td><td>Milliseconds to minutes</td></tr><tr><td>Main challenge</td><td>Consistency under concurrency</td><td>Quick Reactions</td></tr><tr><td>Source of Data</td><td>Maintained by application</td><td>External or events</td></tr><tr><td>Consistency Requirements</td><td>Data constraints &amp; atomicity</td><td>Synchronization in time</td></tr><tr><td><strong>Data Layer Solution</strong></td><td><strong>Database + ORM</strong></td><td><strong>Queue + Processor + Database + Server</strong></td></tr></tbody></table><p>Transactional data problems arise when your application requires concurrent storage and retrieval of consistent data. On the other hand, reactive data problems occur when your application needs to quickly respond to data from external sources or events. Recognizing the distinction between these two types of data issues is crucial to implementing the most effective solution.</p><p>For transactional data problems, a data layer comprising a database and an Object-Relational Mapping (ORM) tool is often the best solution. On the contrary, reactive data problems are more efficiently addressed with a data layer that includes a queue, stream processor, database, and server. Understanding these distinctions and applying the appropriate solutions can significantly improve the efficiency and robustness of your data layer.</p>]]></content:encoded>
            <category>data</category>
        </item>
        <item>
            <title><![CDATA[Hello, World!]]></title>
            <link>https://www.datasqrl.com/blog/welcome</link>
            <guid>https://www.datasqrl.com/blog/welcome</guid>
            <pubDate>Thu, 26 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[" width="40%"/>]]></description>
            <content:encoded><![CDATA[<img loading="lazy" src="/img/generic/undraw_launch.svg" alt="Launching DataSQRL >" width="40%" class="img_ev3q"><p>We are excited to launch <a href="/about/">DataSQRL</a> with the mission to help developers and organizations build with data.</p><p>Collectively, we have spent decades building or helping others build data products. We have seen many struggles, failures, and piles of money being thrown out the window and figured that there must be a better way. We started DataSQRL to find it.</p><p>We believe that the technologies used to build data products are too complex and that the engineering processes used to build them are broken. Here is how we plan to fix these issues.</p><p>We developed <a href="/">DataSQRL</a> which  compiles a developer-friendly version of SQL into a fully integrated and optimized data pipeline and API server. It takes care of all the laborious plumbing, data massaging, and stitching together of technologies that makes building data products so harrowing. Check out <a href="/docs/getting-started/quickstart/">this short tutorial</a> to see how it works - it only takes a few minutes to build an end-to-end data product.</p><p>In addition, we are refining a value-focused process for implementing data products that we have developed over the years while working with development teams and organizations. The basic idea is to apply the same software engineering principles that have proven to be successful to implementing data products. That means you don't need a dedicated team of specialists to implement data products and can keep your customers and stakeholders in the feedback loop. <a href="/docs/process/intro/">Click here</a> to learn more about our process.</p><p>That's our starting point for enabling developers to build successful data products quickly and efficiently. We think we got some good ideas, but have been building data technologies long enough to realize that there is a fine line between innovation and wishful thinking.<br>
We hope that you will join the <a href="/community/">DataSQRL community</a> to share your experience, insights, and opinions to help set us straight.</p><p>If you are trying to enable your organization to turn data into valuable data products, consider <a href="/services/">working with us</a> and <a href="/contact/">get in touch</a>.</p><p>We are excited to be on this journey and hope you will join us. Let's build with data together.</p>]]></content:encoded>
            <category>DataSQRL</category>
        </item>
    </channel>
</rss>