<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Towards Data Engineering - Medium]]></title>
        <description><![CDATA[Explore top articles on data engineering, cloud architecture, agentic AI, and MLOps. Join our thriving Medium community of data professionals to discover curated insights, share your expertise, and build the future of AI and data-driven ecosystems. - Medium]]></description>
        <link>https://medium.com/towards-data-engineering?source=rss----37f58dd42be7---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Towards Data Engineering - Medium</title>
            <link>https://medium.com/towards-data-engineering?source=rss----37f58dd42be7---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 11 Nov 2026 01:38:36 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/towards-data-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Sub Queries and NOT IN Trap in MySQL]]></title>
            <link>https://medium.com/towards-data-engineering/sub-queries-and-not-in-trap-in-mysql-779738a7ccdd?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/779738a7ccdd</guid>
            <category><![CDATA[subquery]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[not-in]]></category>
            <category><![CDATA[mysql]]></category>
            <dc:creator><![CDATA[Sahil Phougat]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 17:47:38 GMT</pubDate>
            <atom:updated>2026-11-10T17:47:36.886Z</atom:updated>
            <content:encoded><![CDATA[<p>A <strong>subquery</strong> is simply a SELECT statement written inside another SQL statement.</p><ol><li>Outer query = the main query you run.</li><li>Inner query = the SELECT written inside it.</li><li>SQL evaluates the inner query first, then uses its result to finish the outer query.</li></ol><pre>CREATE TABLE users (<br>  user_id INT PRIMARY KEY,<br>  name    VARCHAR(50) NULL<br>) ENGINE=InnoDB;<br><br>CREATE TABLE products (<br>  product_id INT PRIMARY KEY,<br>  product    VARCHAR(50) NULL<br>) ENGINE=InnoDB;<br><br>CREATE TABLE orders (<br>  order_id   INT PRIMARY KEY,<br>  user_id    INT NOT NULL,<br>  product_id INT NULL,<br>  amount     DECIMAL(10,2) NULL,<br>  CONSTRAINT fk_orders_user<br>    FOREIGN KEY (user_id)<br>    REFERENCES users(user_id),<br>  CONSTRAINT fk_orders_product<br>    FOREIGN KEY (product_id)<br>    REFERENCES products(product_id)<br>) ENGINE=InnoDB;<br><br>INSERT INTO users (user_id, name) VALUES<br>(1, &#39;Ayaan&#39;),<br>(2, &#39;Sneha&#39;),<br>(3, &#39;Rohit&#39;),<br>(4, &#39;Kavya&#39;),<br>(5, &#39;Zoya&#39;),<br>(6, &#39;Arjun&#39;),<br>(7, &#39;&#39;),<br>(8, NULL);<br><br>INSERT INTO products (product_id, product) VALUES<br>(10, &#39;Tablet&#39;),<br>(11, &#39;Headphones&#39;),<br>(12, &#39;Keyboard&#39;);<br><br>INSERT INTO orders (order_id, user_id, product_id, amount) VALUES<br>(101, 1, 10, 1200.00),<br>(102, 1, 11,  300.00),<br>(103, 1, 12,  150.00),<br>(104, 3, 10, 1100.00),<br>(105, 3, 11,  350.00),<br>(106, 4, 11,  400.00),<br>(107, 1, 11,  250.00),<br>(108, 1, 12,  NULL),<br>(109, 3, 11,  349.00),<br>(110, 3, 11,   50.00),<br>(111, 4, NULL,  50.00),<br>(112, 4, 11,   10.00);</pre><p>So, here is a basic example on Subquery how to write it and read it</p><p>Question: Fetch each order along with the user’s name</p><pre>SELECT<br>  o.order_id,<br>    (SELECT u.name <br>     FROM users u <br>     WHERE u.user_id = o.user_id) as name<br>From orders o</pre><p>1) Outer query picks orders</p><p>The outer query reads each row from the orders table (o) and outputs:</p><ul><li>o.order_id</li></ul><p>So it produces one output row per order.</p><p>2) Subquery fetches user name for that order</p><p>For every order row, this subquery runs:</p><pre>SELECT u.name<br>FROM users u<br>WHERE u.user_id = o.user_id</pre><ul><li>It uses the current order’s o.user_id.</li><li>It finds the matching row in users.</li><li>It returns the name of that user.</li></ul><p>Question 2: Find users who bought all products</p><pre>SELECT<br>  u.user_id,<br>  u.name<br>FROM users u<br>WHERE NOT EXISTS (<br>  SELECT 1<br>  FROM products p<br>  WHERE NOT EXISTS (<br>    SELECT 1<br>    FROM orders o<br>    WHERE o.user_id = u.user_id<br>      AND o.product_id = p.product_id<br>  )<br>);</pre><p>For each user, the query checks every product in products. If the user is missing even one product (no order for it), the user is removed. Only users who have bought every product remain.</p><p>Now you may be thinking is it the only way to write down query?<br>But No, this query can be written down in many forms</p><h4>1. Standard Inner Join (Recommended)</h4><p>This is the most common, readable, and generally highest-performing way to write this query. It fetches orders that have a matching user.</p><pre>SELECT <br>    o.order_id, <br>    u.name <br>FROM orders o<br>JOIN users u ON o.user_id = u.user_id;</pre><h4>2. Left Join (Preserves all orders)</h4><p>If your original query could return NULL for the name when an order has no matching user (like a LEFT JOIN), use this to ensure all orders are still listed even if the user record is missing.</p><pre>SELECT <br>    o.order_id, <br>    u.name <br>FROM orders o<br>LEFT JOIN users u ON o.user_id = u.user_id;</pre><h4>3. Using the USING Clause</h4><p>If both tables share the exact same column name (user_id), you can shorten the ON condition using USING.</p><pre>SELECT <br>    o.order_id, <br>    u.name <br>FROM orders o<br>JOIN users u USING (user_id);</pre><h4>4. Implicit Join (Legacy Syntax)</h4><p>An older, comma-separated syntax (though explicit JOIN is modern industry standard).</p><pre>SELECT <br>    o.order_id, <br>    u.name <br>FROM orders o, users u<br>WHERE o.user_id = u.user_id;</pre><h3>The <strong>NOT IN </strong>Trap</h3><p>So, I have a question what will this query return based on below table</p><pre>SELECT name<br>FROM employees<br>WHERE salary NOT IN (<br> SELECT salary<br> FROM employees<br> WHERE dept_id = 10<br>);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/392/1*f-AAwCBUh4SUeOe7X8cwJw.png" /><figcaption>Table: To query NOT IN</figcaption></figure><p>If you says: Neha, Pooja and Karan then you are wrong. So, let’s understand how <strong>NOT IN </strong>actually work</p><p>The output of this query depends entirely on whether any employee in dept_id = 10 has a NULL salary.</p><p>Because of how SQL handles NULL values with NOT IN, this query can behave in two completely different ways:</p><h4>Scenario A: If there are NO NULL salaries in Department 10</h4><p>If all employees in dept_id = 10 have valid, non-null salary values (e.g., 50000, 60000), the query will successfully return:</p><ul><li><strong>The names of all employees whose salary does NOT match any salary in department 10.</strong></li></ul><h4>Scenario B: If even ONE salary in Department 10 is NULL (The NOT IN Trap)</h4><p>If the subquery returns <strong>even a single </strong><strong>NULL value</strong>, the query will return <strong>0 rows (an empty result set)</strong>, no matter what.</p><h4>Why does this happen?</h4><p>When SQL evaluates NOT IN, it translates to a series of AND conditions with != (not equal):</p><pre>-- Conceptual evaluation for an employee with a salary of 70000, <br>-- if department 10 has salaries (50000, NULL):<br>WHERE salary != 50000 AND salary != NULL</pre><ul><li>70000 != 50000 evaluates to <strong>TRUE</strong>.</li><li>70000 != NULL evaluates to <strong>UNKNOWN</strong> (because comparisons with NULL in SQL always result in unknown/null).</li><li>TRUE AND UNKNOWN evaluates to <strong>UNKNOWN</strong>.</li></ul><p>Since a WHERE clause only keeps rows where the condition evaluates to strictly TRUE, an UNKNOWN result causes the row to be discarded. One single NULL breaks the entire query for every row.</p><h4>How to Write It Safely</h4><p>To avoid the NULL trap entirely, it is much safer to use <strong>NOT EXISTS</strong> (which you looked at previously) or a LEFT JOIN:</p><pre>SELECT e1.name<br>FROM employees e1<br>WHERE NOT EXISTS (<br>    SELECT 1 <br>    FROM employees e2 <br>    WHERE e2.dept_id = 10 <br>      AND e2.salary = e1.salary<br>);</pre><p>NOT EXISTS handles NULL values safely because it checks for the existence of rows rather than performing a list comparison.</p><h3>Performance and Safety</h3><p>In many database systems, <strong>EXISTS</strong> is faster than <strong>IN</strong>. Why? Because <strong>IN</strong> requires the database to build a complete list of all possible values from the inner query first. <strong>EXISTS</strong> just needs to find one single match and can stop looking immediately. This “short-circuit” behavior makes it very efficient.</p><p>Even more importantly, <strong>NOT EXISTS</strong> is much safer than <strong>NOT IN</strong>. If a subquery used with <strong>NOT IN</strong> returns even one NULL value, the entire query can fail or return zero rows. <strong>NOT EXISTS</strong> does not have this problem and handles NULLs logically.</p><h3>The Hidden Cost</h3><p>Correlated subqueries are incredibly flexible, but they come at a high performance cost. Because they run in a loop (once for every row of the outer table), they can be extremely slow on large datasets.</p><ul><li>If your outer table has 1,000 rows, the inner query runs 1,000 times.</li><li>If your outer table has 1,000,000 rows, the inner query runs 1,000,000 times.</li></ul><p>In many modern databases, the optimizer is smart enough to rewrite these as a faster <strong>JOIN</strong>. However, in older systems or very complex queries, they can become a major bottleneck. Always test your query performance.</p><h3>Common Mistakes to Avoid</h3><ul><li><strong>Selecting Columns in EXISTS:</strong> Beginners often write SELECT * or SELECT column inside an <strong>EXISTS</strong> clause. This is a waste of resources. The database only cares if a row is returned, not what is in it. Always use <strong>SELECT 1</strong> for better performance.</li><li><strong>The NOT IN Trap:</strong> As mentioned, if the subquery for <strong>NOT IN</strong> contains a NULL, your query will break. This is the number one reason developers prefer <strong>NOT EXISTS</strong> for finding missing relationships.</li><li><strong>Forgetting the Correlation:</strong> The subquery inside <strong>EXISTS</strong> must be “Correlated,” meaning it must link back to the outer query (e.g., WHERE o.user_id = u.user_id). If you forget this, the subquery will check the same thing for every row, giving you the wrong answer.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aQoIQoJ0PRaoC7o2737yvw.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=779738a7ccdd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/sub-queries-and-not-in-trap-in-mysql-779738a7ccdd">Sub Queries and NOT IN Trap in MySQL</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Contaminated Fuel Trap: When AI Hallucinations Are Data Contract Failures]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/towards-data-engineering/the-contaminated-fuel-trap-when-ai-hallucinations-are-data-contract-failures-55cca4d41377?source=rss----37f58dd42be7---4"><img src="https://cdn-images-1.medium.com/max/1400/1*Gh9uhoeaqPIF1Lgx3QaY0Q.png" width="1400"></a></p><p class="medium-feed-snippet">A renamed column can pass every test and still feed your RAG pipeline the wrong number. DDIA&#x2019;s schema rules show where to stop it.</p><p class="medium-feed-link"><a href="https://medium.com/towards-data-engineering/the-contaminated-fuel-trap-when-ai-hallucinations-are-data-contract-failures-55cca4d41377?source=rss----37f58dd42be7---4">Continue reading on Towards Data Engineering »</a></p></div>]]></description>
            <link>https://medium.com/towards-data-engineering/the-contaminated-fuel-trap-when-ai-hallucinations-are-data-contract-failures-55cca4d41377?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/55cca4d41377</guid>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[data-governance]]></category>
            <dc:creator><![CDATA[Abivarma]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 17:47:34 GMT</pubDate>
            <atom:updated>2026-11-10T17:47:33.209Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[I Built a Real-Time Data Pipeline That Detects Database Changes and Processes Them Automatically]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/towards-data-engineering/i-built-a-real-time-data-pipeline-that-detects-database-changes-and-processes-them-automatically-54fc70f1a9c4?source=rss----37f58dd42be7---4"><img src="https://cdn-images-1.medium.com/max/1376/1*lxrKv_DcGCVcN4aLCrYFUw.jpeg" width="1376"></a></p><p class="medium-feed-snippet">I combined PostgreSQL CDC, Kafka, Flink, and an analytical storage layer to capture database changes and deliver them to downstream&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/towards-data-engineering/i-built-a-real-time-data-pipeline-that-detects-database-changes-and-processes-them-automatically-54fc70f1a9c4?source=rss----37f58dd42be7---4">Continue reading on Towards Data Engineering »</a></p></div>]]></description>
            <link>https://medium.com/towards-data-engineering/i-built-a-real-time-data-pipeline-that-detects-database-changes-and-processes-them-automatically-54fc70f1a9c4?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/54fc70f1a9c4</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[data-pipeline]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Dr.Michael]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 17:47:29 GMT</pubDate>
            <atom:updated>2026-11-10T17:47:28.027Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Batch vs. Streaming: The Real-Time Trap Data Engineers Need to Understand]]></title>
            <link>https://medium.com/towards-data-engineering/batch-vs-streaming-the-real-time-trap-data-engineers-need-to-understand-69b9e1473077?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/69b9e1473077</guid>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[apache-kafka]]></category>
            <category><![CDATA[big-data]]></category>
            <dc:creator><![CDATA[Vishnu Vardhan Kothapalli]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 17:47:14 GMT</pubDate>
            <atom:updated>2026-11-10T17:47:13.000Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QlZnDDcy0SpUuAWY6vCTwQ.png" /></figure><h4>DATA ENGINEERING</h4><h4>Why lower latency isn’t always better, when batch processing wins, and how to choose the right architecture.</h4><h3>1. Does Every Data Pipeline Really Need Real-Time Processing?</h3><p>Imagine two companies.</p><p>The first runs a payment platform. When a suspicious transaction occurs, its fraud detection system may need to identify it within seconds. Waiting until tomorrow’s report could mean discovering fraud after the damage is done.</p><p>The second runs an online retailer. Every night, it calculates total sales, identifies best-selling products, and prepares revenue reports. Does it need to process every sale the instant it happens?</p><p>Probably not.</p><p>Yet real-time processing is often treated as the definition of modern data engineering. Lower latency sounds automatically better, as if processing an event in milliseconds must be superior to processing it in minutes or hours.</p><p>Streaming can introduce additional infrastructure costs, operational complexity, monitoring demands, and failure scenarios. If the business gains little from receiving results sooner, those costs may deliver little value.</p><p>A good data engineer doesn’t choose streaming simply because the technology is available. They understand the problem, evaluate the trade-offs, and select an architecture that meets the requirement without unnecessary complexity.</p><p>That decision starts with understanding how batch and streaming actually work. That is the choice this article examines.</p><blockquote>“The real question isn’t how quickly we can process data. It’s how quickly the business needs the results.”</blockquote><h3>2. Batch vs. Streaming: How Do They Actually Work?</h3><h4>2.1 Batch Processing: Process Data in Groups</h4><p>Batch processing collects data over a period or from a bounded dataset, then processes that collection as a job.</p><p>Imagine an e-commerce company calculating yesterday’s sales. Its pipeline might:</p><ul><li>Read order records from a database.</li><li>Validate and clean the data.</li><li>Calculate total revenue and best-selling products.</li><li>Write the results to a data warehouse.</li></ul><p>The job runs, processes the available data, and finishes. The next scheduled run handles newly collected records.</p><p>Batch processing works well for daily reports, historical analysis, payroll calculations, and large-scale data transformations. Its defining characteristic is bounded input: the job processes a specific collection of data.</p><p>Batch doesn’t automatically mean slow; a well-designed job can process millions of records efficiently.</p><h4>2.2 Streaming: Process Data as It Arrives</h4><p>Streaming processing handles incoming data continuously, allowing systems to update results without waiting for a scheduled batch.</p><p>Consider a payment platform publishing transaction events to Apache Kafka. A streaming engine can consume those events, validate transactions, and update metrics as new data arrives. Instead of waiting until midnight, the system can identify suspicious activity much sooner.</p><p>Streaming is useful when a business needs fresh information for fraud detection, application monitoring, live dashboards, or operational alerts.</p><p>Its defining characteristic is unbounded input: new events may continue arriving indefinitely, so the pipeline must manage ongoing processing and recovery.</p><pre><br>from pyspark.sql import SparkSession<br><br>spark = SparkSession.builder \<br>    .appName(&quot;TransactionStream&quot;) \<br>    .getOrCreate()<br><br>transactions = (<br>    spark.readStream<br>    .format(&quot;kafka&quot;)<br>    .option(&quot;kafka.bootstrap.servers&quot;, &quot;localhost:9092&quot;)<br>    .option(&quot;subscribe&quot;, &quot;transactions&quot;)<br>    .load()<br>)<br><br>results = transactions.selectExpr(<br>    &quot;CAST(value AS STRING) AS transaction_event&quot;<br>)<br><br>query = (<br>    results.writeStream<br>    .format(&quot;console&quot;)<br>    .outputMode(&quot;append&quot;)<br>    .start()<br>)<br><br>query.awaitTermination()</pre><p>This example demonstrates event ingestion, not fraud detection itself. It requires a running Kafka broker, the specified topic, and a compatible Spark Kafka connector.</p><h4>2.3 Does Streaming Mean Instant Processing?</h4><p>Not necessarily.</p><p>Streaming systems can buffer events or process them in small groups called micro-batches. For example, Spark Structured Streaming commonly uses micro-batch execution by default.</p><p>Some events may also arrive late, so the system must handle event time, state, and recovery correctly.</p><p>Both approaches need validation, error handling, monitoring, and duplicate management. Batch pipelines must also handle schedules, reruns, and backfills.</p><p>The simplest way to remember the difference:</p><ul><li>Batch: What should we calculate from the data collected for this period?</li><li>Streaming: How can we update results as new events arrive?</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FfuE3PcstpTfqUTmFTLKxw.png" /></figure><p><strong><em>Figure 1: Batch vs. Streaming Processing: How Data Is Processed, Stored, and Used</em></strong></p><p>Neither approach is universally better. The right choice depends on how fresh the results need to be and what the business gains from receiving them sooner.</p><h3>3. Latency, Cost and Complexity: The Real Trade-Offs</h3><p>Choosing between batch and streaming means balancing three things: how quickly results are needed, how much they cost, and how difficult the pipeline is to maintain.</p><h4>3.1. Latency: How Quickly Do Results Matter?</h4><p>Batch pipelines process data on a schedule, while streaming pipelines can make results available much sooner.</p><p>For fraud detection, seconds may matter. For monthly financial reports, waiting until the next scheduled run may be perfectly acceptable.</p><p>Lower latency is valuable only when the business benefits from acting sooner.</p><h4>3.2. Cost: Are You Paying for Unnecessary Speed?</h4><p>Streaming may require continuously running processing resources, monitoring, and infrastructure to handle incoming events. Batch jobs can often use compute resources only when processing is needed.</p><p>However, neither approach is automatically cheaper. Costs depend on data volume, workload, infrastructure, and operational requirements.</p><p>The goal isn’t to minimize cost at any price. It’s to avoid paying for capabilities the business doesn’t need.</p><h4>3.3. Complexity: What Must You Keep Under Control?</h4><p>Batch pipelines must handle failed jobs, reruns, and historical backfills. Streaming adds challenges such as late-arriving events, state management, duplicate handling, and continuous monitoring.</p><p>These problems are solvable, but they demand careful engineering.</p><h4>The Real Trade-Off</h4><p>Before choosing streaming, ask yourself:</p><ul><li>How quickly must the result be available?</li><li>What value does that speed create?</li><li>Can the team manage the additional complexity?</li></ul><p>The best architecture isn’t necessarily the fastest. It’s the one that delivers the required freshness at a justified cost and manageable complexity.</p><blockquote>“The best architecture isn’t necessarily the fastest.”</blockquote><h3>4. Practical Examples: When Each Approach Wins</h3><p>The best way to understand batch and streaming is to see how businesses use them.</p><h4>Example 1: E-Commerce Sales Analytics — Batch Wins</h4><p>An online retailer wants to calculate daily revenue, identify best-selling products, and compare regional sales.</p><p>A batch pipeline can process the day’s orders overnight, clean and transform the records, aggregate sales, and load the results into a data warehouse.</p><p>Because daily reports don’t require instant updates, continuous processing may introduce unnecessary infrastructure and operational complexity.</p><p><strong>Why batch?</strong> Scheduled reporting and efficient processing of accumulated data.</p><h4>Example 2: Payment Fraud Detection — Streaming Wins</h4><p>A payment platform receives thousands of transactions continuously. Waiting until the end of the day to identify suspicious activity could expose the business to financial losses.</p><p>A streaming pipeline can consume transaction events from Apache Kafka, process them using Spark Structured Streaming, and evaluate patterns such as repeated transactions or unusual activity within short time windows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*olZLzLxvTRWauCPhbgL1UA.png" /></figure><p><strong><em>Figure 2: Real-Time Fraud Detection Pipeline Using Apache Kafka and Spark Structured Streaming.</em></strong></p><p>Suspicious events can then be sent to a detection system for further investigation. Actual detection speed depends on the processing logic and supporting services.</p><p><strong>Why streaming?</strong> Faster detection enables earlier action.</p><h4>Example 3: Historical Data Analysis — Batch Wins</h4><p>Suppose a company wants to analyse five years of customer purchases to identify seasonal trends and changes in buying behaviour.</p><p>Since the historical data already exists, the company can process the bounded dataset using PySpark, transform millions of records, and generate analytical datasets for reporting or machine learning.</p><p>The pipeline can also be rerun when business rules change or historical data needs correction.</p><p><strong>Why batch?</strong> Efficient processing of historical datasets with support for repeatable analysis.</p><h4>Example 4: Application Monitoring — Streaming Wins</h4><p>A production application generates logs and metrics continuously. If error rates suddenly increase, engineers need to identify the problem before it affects more users.</p><p>A streaming pipeline can calculate error rates over short time windows and trigger alerts when predefined thresholds are exceeded.</p><p>Instead of discovering the issue in a report the next morning, the engineering team can investigate while the incident is unfolding.</p><p><strong>Why streaming?</strong> Faster detection supports quicker incident response.</p><h4>What Happens in Real Production Systems?</h4><p>Many organisations combine both approaches.</p><p>A retailer might stream incoming orders to update operational dashboards while running nightly batch jobs to reconcile transactions and produce financial reports.</p><p>The same data can support different workloads, each with its own freshness requirements.</p><p><strong>The lesson is simple: choose the processing model based on the business problem, not the popularity of the technology.</strong></p><h3>5. Why Modern Data Pipelines Often Combine Both</h3><p>Batch and streaming aren’t competing approaches where one must replace the other. In many production systems, they work together to meet different business requirements.</p><h4>One System, Different Processing Needs</h4><p>Consider a logistics company tracking thousands of shipments across multiple locations.</p><p>Its operations team needs timely shipment updates, while management needs daily reports on delivery performance, delays, and operational costs.</p><p>A streaming pipeline can process incoming shipment events and update tracking dashboards as new events arrive. Meanwhile, a batch pipeline can analyse historical records to calculate average delivery times, identify recurring delays, and generate performance reports.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XeSXdynwMUqnhzGvBsASow.png" /></figure><p><strong><em>Figure 3: Hybrid Data Architecture — Combining Real-Time Processing and Batch Analytics for Logistics.</em></strong></p><p>Streaming supports timely operational decisions, while batch supports historical analysis and reconciliation.</p><h4>The Challenge: Keeping Results Consistent</h4><p>Combining both approaches introduces an important engineering challenge: ensuring that their results remain consistent.</p><p>For example, a shipment might initially appear delayed because its delivery confirmation hasn’t arrived. When that event eventually arrives, the system must update its records.</p><p>Engineers need clear rules for handling late events, duplicate records, corrections, and reconciliation. Without them, operational dashboards and historical reports may show conflicting results.</p><p>Running two pipelines isn’t enough. Their processing rules and data quality checks must work together.</p><h4>The Bigger Picture</h4><p>Modern data engineering isn’t about choosing batch or streaming everywhere. It’s about matching processing patterns to business requirements.</p><p>Some workloads need fresh information. Others need complete historical datasets. Many organisations need both.</p><blockquote>“The goal isn’t to make every dataset real-time. It’s to deliver reliable information at the right time for the decision being made.”</blockquote><h3>6. How to Choose Between Batch and Streaming</h3><h4>6.1 Start With Business Requirements</h4><p>Ask two questions: How quickly are results needed, and what happens if they’re delayed?</p><p>A daily sales report can usually wait for a scheduled batch job. A fraud detection system may need to identify suspicious transactions within seconds.</p><p>The acceptable delay should determine the required processing speed — not the popularity of a technology.</p><h4>6.2 Evaluate Data and Operational Complexity</h4><p>Consider whether you’re processing a fixed historical dataset or continuously arriving events. Batch naturally suits bounded datasets, while streaming supports ongoing event processing.</p><p>Then evaluate what your team can reliably operate. Streaming requires attention to continuous processing, late-arriving events, recovery, and monitoring. Batch also needs scheduling, retries, and backfills.</p><h4>6.3 Balance Cost Against Value</h4><p>Compare infrastructure, compute resources, monitoring, and maintenance costs against the benefits of faster results.</p><p>If streaming enables earlier decisions that prevent losses or improve operations, the additional investment may be worthwhile. If the business gains little from lower latency, batch may be the more practical choice.</p><p>When different workloads have different freshness requirements, combining both approaches may work best.</p><p>The principle is simple: choose the architecture that meets business requirements reliably, at a justified cost — not the one that merely sounds more advanced.</p><h3>7. Final Takeaway: Build for Business Needs, Not Just Speed</h3><p>Batch and streaming aren’t competing technologies where one must replace the other. They are different approaches designed to solve different data processing problems.</p><p>Batch helps process accumulated data efficiently. Streaming enables faster insights from continuously arriving events. Combining them can provide both timely updates and reliable historical analysis when the business needs both.</p><p>As a data engineer, resist the temptation to choose an architecture simply because it sounds modern. Understand the required freshness, evaluate the costs, and consider the complexity your team can manage.</p><blockquote>“The best data pipeline isn’t the one that processes data the fastest. It’s the one that delivers the right information, at the right time, with the right level of reliability.”</blockquote><p>That is the difference between building a technically impressive pipeline and building one that creates real business value.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=69b9e1473077" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/batch-vs-streaming-the-real-time-trap-data-engineers-need-to-understand-69b9e1473077">Batch vs. Streaming: The Real-Time Trap Data Engineers Need to Understand</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Automatically Discover Tables in Messy Excel and CSV Files with Python]]></title>
            <link>https://medium.com/towards-data-engineering/stop-telling-python-where-your-excel-tables-are-0e0a8ece8c55?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/0e0a8ece8c55</guid>
            <category><![CDATA[python]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[csv]]></category>
            <category><![CDATA[excel]]></category>
            <dc:creator><![CDATA[Marcus Steinbach]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 17:46:52 GMT</pubDate>
            <atom:updated>2026-11-10T17:46:51.692Z</atom:updated>
            <content:encoded><![CDATA[<p>Automatically find multiple tables across worksheets, even when headers move, columns shift, and spreadsheet layouts change.</p><p>The Excel file arrives on Monday. It has a title, a few explanatory rows, two tables next to each other, and a third table farther down. One table even has a note immediately below its header.</p><p>Next month, somebody moves everything two columns to the right.</p><p>If your ingestion code depends on skiprows=3, usecols=&quot;C:D&quot;, or sheet_name=0, the layout is now part of your application&#39;s logic. Every change to the report can mean another code change.</p><p>The tricky part isn’t reading the cells. <strong>It’s finding the actual tables and deciding which rows belong to them.</strong></p><p>That’s the problem I built <a href="https://github.com/chooong-group/chooong.datasift">chooong.datasift</a> to address. Its core capability is <em>automatic table discovery</em>, backed by a native Rust processing engine and exposed through Python.</p><h3>One workbook. Several tables. No fixed coordinates.</h3><p>Here is our sample workbook, messy-sales.xlsx, on the <strong>Operations</strong> worksheet:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5thU12usw16JvAqHoAuIjA.png" /><figcaption>Operations worksheet: three tables, an offset header, and a note immediately beneath the first header</figcaption></figure><p><em>The workbook contains three tables on Operations: two adjacent tables starting in row 4, and a third table starting in row 10. The orange row is a note, not a transaction.</em></p><p>The same workbook also contains a <strong>Regional</strong> worksheet, where another Customer/Amount table starts at a different position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KlLyb2ZT_m0CkfXk68gJzg.png" /><figcaption>A separate Regional worksheet with a table at a different position</figcaption></figure><p><em>The fourth table is on another worksheet. Our extraction code won’t refer to either worksheet by name or index.</em></p><p>A conventional extraction might hardcode a starting row and column range, then repeat the process for each region. That works for stable templates, but it is brittle when spreadsheet layouts change.</p><p>DataSift lets us describe the columns we recognize instead of their physical coordinates.</p><h3>Discover tables across all worksheets</h3><pre>from chooong.datasift import iter_matching_rows</pre><pre>mask = {<br>    &quot;sheet&quot;: None,<br>    &quot;header&quot;: {<br>        &quot;anchors&quot;: [&quot;Customer&quot;, &quot;Amount&quot;]<br>    },<br>    &quot;row_policy&quot;: {<br>        &quot;rules&quot;: [{<br>            &quot;classification&quot;: &quot;comment&quot;,<br>            &quot;where&quot;: {<br>                &quot;column&quot;: &quot;Customer&quot;,<br>                &quot;op&quot;: &quot;starts_with&quot;,<br>                &quot;value&quot;: &quot;NOTE:&quot;<br>            }<br>        }]<br>    },<br>    &quot;select&quot;: [&quot;Customer&quot;, &quot;Amount&quot;]<br>}<br>rows = [<br>    dict(row)<br>    for row in iter_matching_rows(&quot;messy-sales.xlsx&quot;, mask)<br>]<br>print(rows)</pre><p>For the workbook shown above, the expected matching rows are:</p><pre>[<br>    {&quot;Customer&quot;: &quot;Bob&quot;, &quot;Amount&quot;: 240},<br>    {&quot;Customer&quot;: &quot;Alice&quot;, &quot;Amount&quot;: 120},<br>    {&quot;Customer&quot;: &quot;Charlie&quot;, &quot;Amount&quot;: 360},<br>    {&quot;Customer&quot;: &quot;Dana&quot;, &quot;Amount&quot;: 480},<br>]</pre><p>There are <strong>no header row numbers, no </strong><strong>A1 assumptions, no column letters, and no sheet index</strong> in that mask.</p><p>Three things are happening:</p><p><strong>1. Named-column Discovery finds the table regions.</strong> The anchors identify complete Customer/Amount header groups even when they appear side by side or farther down the worksheet.</p><p><strong>2. </strong><strong>sheet=None scans every worksheet.</strong> DataSift processes both Operations and Regional without us specifying either name or position. This also includes hidden worksheets, so choose a sheet explicitly when that&#39;s not what you want.</p><p><strong>3. The note is excluded by a content-based rule.</strong> The line NOTE: preliminary figures sits directly under the first header. The row_policy rule classifies it as a comment, so it isn&#39;t returned as a matching transaction. Importantly, DataSift doesn&#39;t guess that the English sentence is a comment: <strong>we define how to recognize it</strong>. The rule is based on content, not on row 5.</p><p>This combination is more useful than simply reading all worksheets. The important difference is discovering several independent table regions <em>within</em> those worksheets.</p><h3>Inspect what the engine actually found</h3><p>DataSift can report the discovered schemas and scan statistics. This is useful when debugging unpredictable files:</p><pre>from chooong.datasift import iter_matching_batches</pre><pre>with iter_matching_batches(&quot;messy-sales.xlsx&quot;, mask) as scan:<br>    rows = [dict(row) for batch in scan for row in batch]<br>    stats = scan.statistics<br>assert stats is not None and stats.complete<br>assert len(stats.tables) == 4</pre><p>The scan must complete before its final statistics can be trusted. A partial iterator result isn’t proof that the whole workbook was processed successfully.</p><h3>Unknown column names? Free Discovery.</h3><p>What if you don’t even know the schema ahead of time?</p><p>DataSift also supports <strong>Free Discovery</strong>: no column anchors, no filter expression, no table coordinates. Here’s a CSV file with two neighboring tables and a third table below them:</p><pre>Sales report<br>,Customer,Amount,,Code,Count<br>,Alice,120,,X-01,2<br>,,,,,<br>,,City,Population<br>,,Berlin,3</pre><p>Read it without describing any columns:</p><pre>from chooong.datasift import FilterMask, iter_matching_rows</pre><pre>rows = [<br>    dict(row)<br>    for row in iter_matching_rows(<br>        &quot;discovery.csv&quot;, FilterMask(sheet=None)<br>    )<br>]<br>print(rows)</pre><p>Expected result:</p><pre>[<br>    {&quot;Customer&quot;: &quot;Alice&quot;, &quot;Amount&quot;: &quot;120&quot;},<br>    {&quot;Code&quot;: &quot;X-01&quot;, &quot;Count&quot;: &quot;2&quot;},<br>    {&quot;City&quot;: &quot;Berlin&quot;, &quot;Population&quot;: &quot;3&quot;},<br>]</pre><p>Free Discovery uses structural evidence to confirm candidate headers. In this example, the immediately following records contain numeric text, which helps confirm each region. This isn’t semantic AI inference: purely textual tables and ambiguous layouts can require named anchors or explicit sources. An explicit empty-field record (,,,,,) separates the two CSV regions; a blank physical line is not equivalent.</p><p>Notice that CSV numeric values remain strings. DataSift doesn’t silently convert them just because the discovery engine recognized their structure.</p><h3>CSV isn’t a second-class input</h3><p>The same rule system works with CSV files, including files where the header starts after a preamble. For example, given transactions.csv:</p><pre>Monthly transactions<br>,Country,Amount,Reference<br>,DE,1500,INV-001<br>,AT,2000,INV-002<br>,DE,900,INV-003<br>,DE,2500,INV-004</pre><p>We can discover the shifted table, convert Amount to an integer, filter German transactions worth at least 1,000, and select only two output columns:</p><pre>from chooong.datasift import iter_matching_rows</pre><pre>mask = {<br>    &quot;sheet&quot;: None,<br>    &quot;header&quot;: {&quot;anchors&quot;: [&quot;Country&quot;, &quot;Amount&quot;]},<br>    &quot;types&quot;: {<br>        &quot;Amount&quot;: {&quot;type&quot;: &quot;integer&quot;, &quot;convert&quot;: True}<br>    },<br>    &quot;where&quot;: {&quot;all&quot;: [<br>        {&quot;column&quot;: &quot;Country&quot;, &quot;op&quot;: &quot;eq&quot;, &quot;value&quot;: &quot;DE&quot;},<br>        {&quot;column&quot;: &quot;Amount&quot;, &quot;op&quot;: &quot;gte&quot;, &quot;value&quot;: 1000}<br>    ]},<br>    &quot;select&quot;: [&quot;Reference&quot;, &quot;Amount&quot;]<br>}<br>rows = [dict(row) for row in iter_matching_rows(&quot;transactions.csv&quot;, mask)]<br>print(rows)</pre><p>Result:</p><pre>[<br>    {&quot;Reference&quot;: &quot;INV-001&quot;, &quot;Amount&quot;: 1500},<br>    {&quot;Reference&quot;: &quot;INV-004&quot;, &quot;Amount&quot;: 2500},<br>]</pre><p>CSV supports configurable delimiters, encodings, quoted fields, and embedded newlines. Delimiters and encodings are configured explicitly rather than guessed.</p><h3>Where Rust fits in</h3><p>Discovery is the reason to look at DataSift. The Rust core takes care of parsing, row filtering, and native NDJSON/Parquet export; the Python API provides typed configuration and streaming iteration. DataSift doesn’t build a full pandas DataFrame to run this pipeline.</p><p>That doesn’t mean every Excel structure can be processed in constant memory: workbook metadata and shared strings still consume memory. And performance claims should be based on reproducible benchmarks, not the choice of programming language alone.</p><p>The aim is to make <strong>messy input discovery and validation predictable</strong>, before pandas, Polars, or other tools handle downstream analysis and transformation.</p><h3>Where automatic discovery stops</h3><p>DataSift doesn’t understand the business meaning of arbitrary prose. A comment directly below a header can be excluded through a rule, as shown above, but it isn’t magically identified by its wording.</p><p>Free Discovery needs structural evidence. Complex text-only sheets, ambiguous regions, and some multirow headers require anchors, explicit configuration, or manual table sources. XLS/XLSB, encrypted workbooks, and formula calculation are not supported.</p><p>If a layout cannot be identified reliably, reporting ambiguity is preferable to silently extracting the wrong data.</p><h3>Try chooong.datasift</h3><pre>pip install chooong.datasift</pre><p>DataSift v0.1.0 is open source under Apache 2.0 and supports CPython 3.12–3.14.</p><ul><li><a href="https://github.com/chooong-group/chooong.datasift">GitHub repository</a></li><li><a href="https://chooong-group.github.io/chooong.datasift/">Documentation</a></li><li><a href="https://github.com/chooong-group/chooong.datasift/blob/main/docs/guides/automatic-table-discovery.md">Automatic table discovery guide</a></li><li><a href="https://pypi.org/project/chooong.datasift/">PyPI</a></li></ul><p>If you have a particularly awkward Excel or CSV export, I’d like to hear about it — especially layouts that the current discovery modes cannot handle yet.</p><p>Thanks for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0e0a8ece8c55" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/stop-telling-python-where-your-excel-tables-are-0e0a8ece8c55">How to Automatically Discover Tables in Messy Excel and CSV Files with Python</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Journey Practice: Building a Databricks Practice Around the Customer’s Journey, Not the First…]]></title>
            <link>https://medium.com/towards-data-engineering/the-journey-practice-building-a-databricks-practice-around-the-customers-journey-not-the-first-cba6eb434510?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/cba6eb434510</guid>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[strategy]]></category>
            <category><![CDATA[leadership]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[THE BRICK LEARNING]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 16:29:23 GMT</pubDate>
            <atom:updated>2026-11-10T16:29:24.574Z</atom:updated>
            <content:encoded><![CDATA[<h3>The Journey Practice: Building a Databricks Practice Around the Customer’s Journey, Not the First Project</h3><h4>Now available: the e-book</h4><p><strong>I have just published an e-book, <em>The Journey Practice: Building an AI-Native, Repeatable Databricks Delivery Engine</em>.</strong> It runs to 95 pages with 24 diagrams, and it is the long version of this article.</p><p>Download it here:</p><p><a href="https://topmate.io/akash_wadhankar/2400145">The art of Building... with Akash A Wadhankar | Topmate</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/778/1*cu6129CaTx9tvDF_1Gdfkw.png" /></figure><p>Inside you will find:</p><ul><li>The blue ocean logic for a Databricks services practice, and the operating model behind it</li><li>Eight steps in three acts, followed through one composite customer, each with architecture, implementation and measurable outcomes</li><li>An in-depth chapter on the governed lakehouse, including a control catalogue</li><li>A delivery playbook: lifecycle, roles, a day-one kit, quality gates, estimating and week-by-week plans</li></ul><h3>Every Databricks partner looks the same</h3><p>Open the websites of ten Databricks partners and you will find one story: a partner tier, a count of certified engineers, a list of accelerators and photographs from the same conferences. Customers struggle to tell the partners apart, so the decision falls to price, relationships or whoever can staff the team first.</p><p>That is a red ocean. Everyone is fighting for the same thing, the first project, which is the most contested and most commoditised stage of the relationship. Conference budgets and demo-grade accelerators become an arms race with no clear return.</p><h3>The customer already told us the answer</h3><p>Here is the observation that changed how I think about the practice. Customers judge a partner by the foundation work done for them. If it was solid, they ask the same partner to lead the next stage.</p><p>The first project is the audition. The journey is the business. Yet very few partners are built deliberately for the second and third act: they win the foundation, deliver it and leave.</p><p>A customer’s data and AI programme moves through recognisable stages: a platform they can trust, data that is understood, answers in the hands of business users, and data products that move a number the business cares about. Each question can only be answered once the previous one has been. That dependency is what turns separate projects into one journey.</p><h3>Four choices that create a blue ocean</h3><p>Blue ocean strategy asks which accepted factors of competition to eliminate, reduce, raise and create. For a Databricks practice, my answers are:</p><ul><li><strong>Eliminate</strong> the conference-and-accelerator arms race, and pitching on certified headcount alone. Competitors match both.</li><li><strong>Reduce</strong> one-off projects sold as ends in themselves, and delivery that depends on a few heroes.</li><li><strong>Raise</strong> the quality and visibility of the foundation, so it becomes an auditable asset with scores, standards and a reference architecture the customer owns.</li><li><strong>Create</strong> productised journey offerings, AI-native delivery, and commercial terms tied to stages and outcomes.</li></ul><p>A caution: “journey partner” is already on many websites. It only becomes a blue ocean when it is concrete and measurable, with defined stages, artefacts, outcomes and terms.</p><h3>Three acts, eight steps</h3><p>The book follows one composite customer through eight steps in three acts. Each act hands a named artefact to the next, so skipping ahead is how pilots fail to reach production.</p><p><strong>Act I, Trusted Foundation:</strong> can we trust our platform, data and costs?</p><ol><li><strong>AI-led assessment.</strong> Agents collect evidence and score the estate; consultants validate it. The result is a baseline, not an opinion.</li><li><strong>Governed lakehouse.</strong> Every asset has an owner, a classification and an access policy, all created through code.</li><li><strong>Ingestion engine.</strong> Standard, monitored patterns for batch, change capture and streaming, chosen by business need.</li><li><strong>AI-driven FinOps and administration.</strong> Agents observe, attribute and recommend; people approve every change.</li></ol><p><strong>Act II, Answer Engine:</strong> can people simply ask, and trust the answer?</p><ol><li><strong>Context engine.</strong> Business definitions, metrics and relationships, so the platform understands the business.</li><li><strong>Genie and chatbots.</strong> Self-service answers, released only after accuracy is proven on questions the business wrote.</li></ol><p><strong>Act III, Proven Value:</strong> is any of this moving a business number?</p><ol><li><strong>Data products.</strong> Governed data with a named owner, service levels and a metric it must move.</li><li><strong>Experience layer in Databricks Apps.</strong> The product, in front of the person who acts on it, inside the platform’s governance.</li></ol><h3>Half-ready on day one, and AI-native by design</h3><p>The operating principle is simple to state. When a new customer signs, at least half of the work that is normally invented from scratch should already exist as a tested asset, a trained person or a standard process. That is what “50% ready on day one” means, and it is measured, not felt.</p><p>The readiness comes from five layers: blueprints, reusable code and automation, delivery playbooks, people, and commercial and governance assets. Each asset is scored from Level 0 (nothing exists) to Level 3 (proven on two or more engagements). A practice becomes “day-one ready” when half its kit sits at Level 2 or above.</p><p>AI-native means two things. AI is part of what you deliver, and it is part of how you deliver. The mechanism is a library of skills: narrow, versioned, tested units of work that agents and people run together. Each skill has an owner, evaluation tests and an autonomy level (assistive, supervised or autonomous), and moves up only on evidence. If you cannot explain to a customer how an AI-generated deliverable was checked, it is not ready to be delivered.</p><h3>Why the governed lakehouse is the centre of gravity</h3><p>Everything in Acts II and III depends on it. An assistant or a data product can never be more trustworthy than the foundation beneath it. So the book gives this step the most depth, and the idea behind it is <strong>govern once, apply everywhere</strong>.</p><p>On Databricks, the control point is Unity Catalog. Data Classification finds sensitive data and proposes tags, stewards approve them as governed tags, and attribute-based access control policies apply row filters and column masks to every matching column, including data that does not exist yet. Access is evaluated at query time against the identity of the person asking, and the evidence lands in system tables, so an audit becomes a query and not a three-week project.</p><p>The second half of the idea is everything as code. Account infrastructure goes through Terraform, workspace resources through Declarative Automation Bundles (formerly Databricks Asset Bundles), and every production change through CI/CD. That is what makes a foundation repeatable for the second region, the second business unit or the second customer.</p><p>A practical note: some of these capabilities are still in Preview, so build the controls your security team relies on on generally available features, and check each name and status against current Databricks documentation.</p><h3>Measure the journey, not the project</h3><p>Each step in the book ends with a table of measures: baseline, target, result and how it is measured. The customer’s own CFO, CISO and head of data platforms then read one scorecard. These figures come from the book’s composite customer, “Meridian Manufacturing”. They are illustrative, to show how a result is defined and how large it can plausibly be, not claims about a real engagement.</p><p>The habit matters more than the numbers: agree the baseline before work starts, name an owner for every line, and let finance validate any benefit you report.</p><h3>What to do on Monday</h3><p>You do not need the whole model to start. Five moves will change the conversation with your next customer:</p><ul><li>Look at your last three engagements and list what you rebuilt every time. That is your first library.</li><li>Score your readiness honestly across the five layers, and pick the three assets that would save the most effort.</li><li>Turn your assessment into a fixed-scope, evidence-based front door, and repeat it every six to twelve months.</li><li>Give every engagement a named hand-off artefact that makes the next step obvious.</li><li>Reward expansion, reuse and customer outcomes, not only new logos and project revenue.</li></ul><p>The red ocean will not go away, and conferences and accelerators still have their place. But the practice that is more ready, more repeatable and more accountable for what happens next is the one customers take along on the journey.</p><p>The full model, the diagrams, the control catalogue and the playbook are in the e-book.</p><p>Download <em>The Journey Practice</em> here:</p><p><a href="https://topmate.io/akash_wadhankar/2400145">The art of Building... with Akash A Wadhankar | Topmate</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/778/1*cu6129CaTx9tvDF_1Gdfkw.png" /></figure><p>I would be glad to hear what you build with it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cba6eb434510" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/the-journey-practice-building-a-databricks-practice-around-the-customers-journey-not-the-first-cba6eb434510">The Journey Practice: Building a Databricks Practice Around the Customer’s Journey, Not the First…</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adding “House” to “Lake” Changes the Entire Price Tag: DW vs Lake vs Lakehouse vs Delta Lake]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/towards-data-engineering/adding-house-to-lake-changes-the-entire-price-tag-dw-vs-lake-vs-lakehouse-vs-delta-lake-b0b6905aeb30?source=rss----37f58dd42be7---4"><img src="https://cdn-images-1.medium.com/max/2600/1*565nUn0aYbW6rXk-dJ1fRw.png" width="2752"></a></p><p class="medium-feed-snippet">For the longest time, I was someone who used &#x201C;Data Lake&#x201D; and &#x201C;Delta Lake&#x201D; completely interchangeably. I just assumed they were the exact&#x2026;</p><p class="medium-feed-link"><a href="https://medium.com/towards-data-engineering/adding-house-to-lake-changes-the-entire-price-tag-dw-vs-lake-vs-lakehouse-vs-delta-lake-b0b6905aeb30?source=rss----37f58dd42be7---4">Continue reading on Towards Data Engineering »</a></p></div>]]></description>
            <link>https://medium.com/towards-data-engineering/adding-house-to-lake-changes-the-entire-price-tag-dw-vs-lake-vs-lakehouse-vs-delta-lake-b0b6905aeb30?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/b0b6905aeb30</guid>
            <category><![CDATA[data]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[delta-lake]]></category>
            <category><![CDATA[data-architecture]]></category>
            <category><![CDATA[data-warehouse]]></category>
            <dc:creator><![CDATA[Likith Raj Gollu]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 15:21:36 GMT</pubDate>
            <atom:updated>2026-11-10T15:21:35.993Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Feast vs Tecton: What Changes When You Run a Feature Store in Production?]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/towards-data-engineering/feast-vs-tecton-what-changes-when-you-run-a-feature-store-in-production-de6233bd069f?source=rss----37f58dd42be7---4"><img src="https://cdn-images-1.medium.com/max/1536/1*zL3HFK6QoHbRY-V0Sru56g.png" width="1536"></a></p><p class="medium-feed-snippet">An engineering comparison of feature computation, point-in-time correctness, streaming, online serving, and operational ownership.</p><p class="medium-feed-link"><a href="https://medium.com/towards-data-engineering/feast-vs-tecton-what-changes-when-you-run-a-feature-store-in-production-de6233bd069f?source=rss----37f58dd42be7---4">Continue reading on Towards Data Engineering »</a></p></div>]]></description>
            <link>https://medium.com/towards-data-engineering/feast-vs-tecton-what-changes-when-you-run-a-feature-store-in-production-de6233bd069f?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/de6233bd069f</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[feature-engineering]]></category>
            <category><![CDATA[mlops]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Think Data]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 10:42:38 GMT</pubDate>
            <atom:updated>2026-11-10T10:42:37.603Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Using .me: A Tree You Can Read, Write, and Teach Rules]]></title>
            <link>https://medium.com/towards-data-engineering/syntax-of-me-4c5f73658310?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/4c5f73658310</guid>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[neurons.me]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 08:01:42 GMT</pubDate>
            <atom:updated>2026-11-10T08:01:41.388Z</atom:updated>
            <content:encoded><![CDATA[<h4><em>An introduction to paths, relationships, and reactive values.</em></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CL0t4d4UaiYkQVQYm2wp5Q.png" /><figcaption>.me Syntax</figcaption></figure><p>A profile has a <strong>name</strong>. An order has a <strong>total</strong>. A person has <strong>friends</strong>.</p><p>We naturally describe information through relationships. .me gives those relationships a consistent shape in code: paths hold values, links connect branches, and formulas describe how values depend on one another.</p><p><strong>Start with a path:</strong></p><pre>me.profile.name(&quot;Ana&quot;);</pre><p><strong>Read it back:</strong></p><pre>me.profile.name(); // &quot;Ana&quot;</pre><p>That is the starting point: one tree, with a small grammar for working with it.</p><h3>Start with your first branch</h3><p>Here is a minimal example using the JavaScript package:</p><pre>import Me from &quot;this.me&quot;;</pre><pre>const me = Me();</pre><pre>me.profile.name(&quot;Ana&quot;);<br>me.profile.bio(&quot;Building the semantic web.&quot;);</pre><pre>console.log(me(&quot;profile.name&quot;)); // &quot;Ana&quot;</pre><p>You build the hierarchy as you write. Here, profile is a branch, and name and bio are values beneath it.</p><p>The labels are yours. .me does not need a built-in definition of “profile” to organize that information.</p><p>You can read through the property interface or pass a path to the kernel:</p><pre>me.profile.name();<br>me(&quot;profile.name&quot;);</pre><p>Both address the same value.</p><h3>Give the tree a rule</h3><p>Some values should be calculated rather than entered manually.</p><p>Consider an order:</p><pre>me.order.price(100);<br>me.order.quantity(5);</pre><pre>me.order[&quot;=&quot;](&quot;total&quot;, &quot;price * quantity&quot;);</pre><pre>console.log(me(&quot;order.total&quot;)); // 500</pre><p>The = operator declares a derived value. The expression uses the other values in that branch.</p><p>Now change the price:</p><pre>me.order.price(200);</pre><pre>console.log(me(&quot;order.total&quot;)); // 1000</pre><p>You update the input. The total follows its declared relationship.</p><p>The formula remains available for inspection:</p><pre>const explanation = me.explain(&quot;order.total&quot;);</pre><pre>console.log(explanation.expr);<br>// &quot;price * quantity&quot;</pre><pre>console.log(explanation.meta.dependsOn);<br>// [&quot;order.price&quot;, &quot;order.quantity&quot;]</pre><p>That gives the result context: you can inspect both its value and the dependencies behind it.</p><p><a href="https://github.com/neurons-me/.me">GitHub - neurons-me/.me: Here we&#39;re codependently creating .me while it concurrently creates us.</a></p><h3>Connect information without copying it</h3><p>A tree gives information a location. Links let you refer to it from another location.</p><pre>me.users.ana.name(&quot;Ana&quot;);</pre><pre>me.friends.ana[&quot;-&gt;&quot;](&quot;users.ana&quot;);</pre><pre>console.log(me(&quot;friends.ana.name&quot;)); // &quot;Ana&quot;</pre><p>Here, friends.ana points to users.ana. You have expressed a relationship instead of manually maintaining another copy of Ana’s profile.</p><p>This is how a hierarchy can also describe a graph: branches organize information, while pointers connect it.</p><h3>Apply a rule across a collection</h3><p>The same grammar extends to multiple members.</p><pre>me.people.ana.age(24);<br>me.people.pablo.age(17);</pre><pre>me.people[&quot;[i]&quot;][&quot;=&quot;](&quot;isAdult&quot;, &quot;age &gt;= 18&quot;);</pre><pre>console.log(me(&quot;people.ana.isAdult&quot;));   // true<br>console.log(me(&quot;people.pablo.isAdult&quot;)); // false</pre><p>[i] broadcasts the declaration across the collection. Each member evaluates the rule in its own context.</p><p>You describe the relationship once, rather than writing a separate formula for every person.</p><h3>The labels do not have to be English</h3><p>These examples express the same calculation:</p><pre>me.shop.price(100);<br>me.shop[&quot;=&quot;](&quot;total&quot;, &quot;price * 1.16&quot;);</pre><pre>me.tienda.precio(100);<br>me.tienda[&quot;=&quot;](&quot;total&quot;, &quot;precio * 1.16&quot;);</pre><p>The kernel evaluates paths and expressions. It does not require English labels, nor does it automatically translate between them. You choose the vocabulary and use it consistently.</p><h3>A small grammar to remember</h3><p>You can begin with just five ideas:</p><ul><li><strong>.</strong> builds a hierarchy.</li><li><strong>()</strong> reads or writes a value.</li><li><strong>[&quot;=&quot;]</strong> declares a derived value.</li><li><strong>[&quot;-&gt;&quot;]</strong> creates a pointer.</li><li><strong>[&quot;[i]&quot;]</strong> broadcasts across members.</li></ul><p>The operators appear in brackets because this is JavaScript: names such as = and -&gt; cannot use ordinary dot notation.</p><p>You do not need to start with a visual editor, a network deployment, or a shared ledger. Those integrations can build on the same tree; persistence and network access require their own runtime configuration.</p><p>Start with something familiar — a profile, an order, a collection of people. Give it values. Connect its branches. Add a rule.</p><pre>me.order[&quot;=&quot;](&quot;total&quot;, &quot;price * quantity&quot;);</pre><p>Now your data carries more than a result. It carries a relationship the program can evaluate and you can inspect.</p><p><a href="https://suign.github.io/MeLoveYou.html">me.love(you)</a></p><p><em>MIT License · Made in Veracruz, Mexico.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c5f73658310" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/syntax-of-me-4c5f73658310">Using .me: A Tree You Can Read, Write, and Teach Rules</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Context Engineering: Introducing the dbt Semantic Layer — Engineering Reusable Business Metrics…]]></title>
            <link>https://medium.com/towards-data-engineering/context-engineering-introducing-the-dbt-semantic-layer-engineering-reusable-business-metrics-7161effa40f0?source=rss----37f58dd42be7---4</link>
            <guid isPermaLink="false">https://medium.com/p/7161effa40f0</guid>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[context-engineering]]></category>
            <dc:creator><![CDATA[THE BRICK LEARNING]]></dc:creator>
            <pubDate>Tue, 10 Nov 2026 07:05:02 GMT</pubDate>
            <atom:updated>2026-11-10T07:05:04.023Z</atom:updated>
            <content:encoded><![CDATA[<h3>Context Engineering: Introducing the dbt Semantic Layer — Engineering Reusable Business Metrics with MetricFlow</h3><h4>Why enterprises need a centralized metric definition, and how dbt is changing the way we engineer business semantics.</h4><p>We have built data warehouses. We have built lakehouses. We have built transformation pipelines and enterprise data models.</p><p>But one problem continues to exist across organizations.</p><p><strong>Why does the same business metric produce different answers in different reporting systems?</strong></p><p>Imagine an organization calculating revenue across five different dashboards.</p><p>Each dashboard has its own SQL logic. Each team applies slightly different filters. Some include cancelled orders, others exclude them. Some calculate gross revenue, while others calculate net revenue. Now introduce AI into this environment.</p><p>How can an AI agent reliably answer business questions if the enterprise itself has not standardized its metrics?</p><p>This is where the Semantic Layer becomes critical.</p><h3>Introducing our new white paper: The dbt Semantic Layer</h3><p>We have added a detailed white paper exploring:</p><p><strong>The dbt Semantic Layer: Engineering Reusable Business Metrics with MetricFlow</strong></p><p>The objective is to understand how enterprises can define metrics once and make them reusable across analytics tools, applications and AI experiences.</p><h3>What does the white paper cover?</h3><p><strong>1. Understanding the dbt Semantic Layer</strong></p><p>Explore how dbt separates business metric definitions from individual reporting queries.</p><p><strong>2. Understanding MetricFlow</strong></p><p>Learn how MetricFlow understands semantic models, entities, dimensions and metrics, and generates SQL for business questions.</p><p><strong>3. Building semantic models</strong></p><p>Explore how to define:</p><ul><li>Semantic models</li><li>Entities and relationships</li><li>Dimensions</li><li>Measures</li><li>Metrics</li></ul><p><strong>4. Practical retail implementation</strong></p><p>We have included a practical example demonstrating how to build reusable Net Sales metrics on top of retail Gold-layer models.</p><p><strong>5. dbt Semantic Layer vs Snowflake Semantic Views</strong></p><p>One of the most interesting parts of this white paper is the comparison between the two approaches.</p><p>We explore:</p><ul><li>Where semantic definitions are maintained.</li><li>How metrics are queried.</li><li>How joins and relationships are resolved.</li><li>How the two approaches support AI.</li><li>Cross-platform portability.</li><li>Governance and deployment.</li></ul><p>We also explore an interesting third option: managing Snowflake Native Semantic Views through a dbt project and the Snowflake Semantic View dbt package.</p><p><strong>6. AI and MCP integration</strong></p><p>Understand how governed metric definitions can be exposed to AI applications and agents through supported dbt interfaces and MCP integrations.</p><h3>Why should Data Engineers pay attention?</h3><p>The role of the Data Engineer is changing.</p><p>Building pipelines and Gold tables is no longer the complete responsibility. We need to understand business definitions, engineer reusable metrics, establish semantic relationships and make business meaning accessible to AI.</p><p>This is the evolution:</p><p><strong>Data Engineering → Analytics Engineering → Metric Engineering → Context Engineering</strong></p><p>The dbt Semantic Layer is an important part of this journey.</p><h3>The bigger picture</h3><p>Our Context Engineering initiative is exploring how to combine:</p><ul><li>Data Engineering</li><li>Business Ontology</li><li>Knowledge Graphs</li><li>Semantic Layers</li><li>Metric Engineering</li><li>Context Intelligence</li><li>AI Agents</li></ul><p>The objective is to build an enterprise architecture where every dashboard, analyst and AI agent works with consistent business definitions.</p><h3>Continue learning</h3><p>The new dbt Semantic Layer white paper is now part of our Context Engineering learning collection on Topmate.</p><p>Explore the complete learning material here:</p><p><a href="https://topmate.io/akash_wadhankar/2301672">Databricks AI Context... with Akash A Wadhankar | Topmate</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5GWzI07mpUOEzx3hUej7Wg.png" /></figure><p>We will continue adding practical implementation guides, architecture papers and enterprise use cases.</p><p><strong>Stop building metrics separately in every dashboard. Start engineering reusable business meaning.</strong></p><p>#dbt #MetricFlow #dbtSemanticLayer #ContextEngineering #Snowflake #SemanticLayer #AnalyticsEngineering #DataEngineering #EnterpriseAI #MCP</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7161effa40f0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/towards-data-engineering/context-engineering-introducing-the-dbt-semantic-layer-engineering-reusable-business-metrics-7161effa40f0">Context Engineering: Introducing the dbt Semantic Layer — Engineering Reusable Business Metrics…</a> was originally published in <a href="https://medium.com/towards-data-engineering">Towards Data Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>