<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on BareSQL.com</title>
    <link>https://baresql.com/posts/</link>
    <description>Recent content in Posts on BareSQL.com</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 26 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://baresql.com/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A Backslash in LIKE Escapes Percent and Underscore Everywhere Except SQLite</title>
      <link>https://baresql.com/a-backslash-in-like-escapes-percent-and-underscore-everywhere-except-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/a-backslash-in-like-escapes-percent-and-underscore-everywhere-except-sqlite/</guid>
      <description>&lt;p&gt;Searching for the text 50_off with &lt;code&gt;LIKE &#39;50\_off&#39;&lt;/code&gt; works on PostgreSQL, MySQL and MariaDB, where the backslash escapes the underscore. SQLite treats the backslash as an ordinary character, and the search finds nothing.&lt;/p&gt;&#xA;&lt;p&gt;&#39;a_b&#39; LIKE &#39;a\_b&#39; is true on PostgreSQL, MySQL and MariaDB, where \_ matches an underscore, and false on SQLite, where the pattern asks for a backslash followed by any one character.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-like-escape.png&#34; alt=&#34;SELECT &#39;a_b&#39; LIKE &#39;a\_b&#39;, run on four engines: SQLite 3.45.1 0; PostgreSQL 16.15 true; MySQL 8.0.46 1; MariaDB 10.11.14 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>A Backslash Is an Escape Character in MySQL Strings and a Plain Character in PostgreSQL</title>
      <link>https://baresql.com/a-backslash-is-an-escape-character-in-mysql-strings-and-a-plain-character-in-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/a-backslash-is-an-escape-character-in-mysql-strings-and-a-plain-character-in-postgresql/</guid>
      <description>&lt;p&gt;A Windows path written as &lt;code&gt;&#39;C:\temp\new&#39;&lt;/code&gt; reads back on MySQL and MariaDB with a tab and a line break in it. PostgreSQL and SQLite keep the backslashes as typed.&lt;/p&gt;&#xA;&lt;p&gt;&#39;C:\\temp&#39; is C:\\temp, two backslashes, on SQLite and PostgreSQL and C:\temp on MySQL and MariaDB. &#39;\t&#39; is a tab and &#39;\&#39;&#39; a quote there, while PostgreSQL needs its E&#39;...&#39; strings for escapes, which no other engine reads. MySQL’s NO_BACKSLASH_ESCAPES mode makes it read backslashes like the others.&lt;/p&gt;</description>
    </item>
    <item>
      <title>A Quarterly Access Review Finds a New Table of Bank Accounts That Every Role Can Read</title>
      <link>https://baresql.com/a-quarterly-access-review-finds-a-new-table-of-bank-accounts-that-every-role-can-read/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/a-quarterly-access-review-finds-a-new-table-of-bank-accounts-that-every-role-can-read/</guid>
      <description>&lt;p&gt;The shop in this case study is made up: it’s the sample shop database that BareSQL’s tests build, and the outputs are what BareSQL printed. It reviews database access once a quarter. The first review with BareSQL Audit covers every login and ends with a file kept for next time, &lt;code&gt;audit.json&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Three months later the review runs again against that file. The reviewer then reads what changed, with no need to compare two long reports by eye.&lt;/p&gt;</description>
    </item>
    <item>
      <title>A Support View Showed Card Numbers to Every Support Login</title>
      <link>https://baresql.com/a-support-view-showed-card-numbers-to-every-support-login/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/a-support-view-showed-card-numbers-to-every-support-login/</guid>
      <description>&lt;p&gt;This case study runs on the sample shop database that BareSQL’s tests build, so the shop is made up, and the outputs are what BareSQL printed. Its support team looks customers up through one view, &lt;code&gt;support.customer_lookup&lt;/code&gt;, and has no grant on the customers table itself, which sounds like the careful way to do it.&lt;/p&gt;&#xA;&lt;p&gt;The view was meant to show a name, an email address and a phone number, enough to help someone who calls in. At some point a column was added to it. The team has two logins, &lt;code&gt;shop_alice&lt;/code&gt; and &lt;code&gt;shop_bob&lt;/code&gt;. Bob is in the team without inheriting its rights, a setup often taken to mean he can’t read what the team reads.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding 1 to a Date Gives the Next Day on PostgreSQL and a Number on MySQL and SQLite</title>
      <link>https://baresql.com/adding-1-to-a-date-gives-the-next-day-on-postgresql-and-a-number-on-mysql-and-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/adding-1-to-a-date-gives-the-next-day-on-postgresql-and-a-number-on-mysql-and-sqlite/</guid>
      <description>&lt;p&gt;&lt;code&gt;due_date + 1&lt;/code&gt; reads like the day after, and on PostgreSQL that is what it gives. On MySQL, MariaDB and SQLite the answer is a plain number.&lt;/p&gt;&#xA;&lt;p&gt;MySQL and MariaDB turn the date into the number 20240131 and add 1. SQLite turns the text into the number 2024. PostgreSQL adds a day. Subtracting two dates goes the same way: PostgreSQL counts days and MySQL and MariaDB subtract the numbers. INTERVAL arithmetic works on PostgreSQL, MySQL and MariaDB, with different result types, and SQLite has no INTERVAL. PostgreSQL’s usual INTERVAL &#39;1 day&#39;, with the unit inside the quotes, works on PostgreSQL only.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Backticks Around Names Work on MySQL and SQLite and Fail on PostgreSQL</title>
      <link>https://baresql.com/backticks-around-names-work-on-mysql-and-sqlite-and-fail-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/backticks-around-names-work-on-mysql-and-sqlite-and-fail-on-postgresql/</guid>
      <description>&lt;p&gt;MySQL examples often quote every name in backticks, as in &lt;code&gt;SELECT `name` FROM `people`&lt;/code&gt;. Copied into a PostgreSQL project, the query fails.&lt;/p&gt;&#xA;&lt;p&gt;`name` is MySQL’s way of quoting a name, and SQLite and MariaDB accept it too. PostgreSQL reads the backtick as a syntax error.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-backticks.png&#34; alt=&#34;SELECT `name` FROM people, run on four engines: SQLite 3.45.1 &#39;Ann&#39;; PostgreSQL 16.15 error: syntax error at or near &amp;quot;FROM&amp;quot;; MySQL 8.0.46 &#39;Ann&#39;; MariaDB 10.11.14 &#39;Ann&#39;.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>BareSQL Launches With an Access Audit for PostgreSQL and a Portability Check for SQL</title>
      <link>https://baresql.com/baresql-launches-with-an-access-audit-for-postgresql-and-a-portability-check-for-sql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/baresql-launches-with-an-access-audit-for-postgresql-and-a-portability-check-for-sql/</guid>
      <description>&lt;p&gt;BareSQL is a free, open-source command-line program with two tools in it. BareSQL Audit lists every column each PostgreSQL login can read, with the route that gets it there, and marks the ones that hold personal data. BareSQL Portable finds the SQL that fails or answers differently on SQLite, PostgreSQL, MySQL and MariaDB. Version 0.1.0 is the first release.&lt;/p&gt;&#xA;&lt;p&gt;Who can read the customers&amp;rsquo; email addresses? In a PostgreSQL database the answer is spread over role memberships, grants made years ago, views that read tables with their owner’s rights, default privileges and row security policies. Working it out by hand means an afternoon with the system catalogs, and the answer is out of date the next time someone runs a GRANT. BareSQL Audit logs in, reads the catalogs inside a read-only transaction and writes the answer down: a report for people and a JSON file for the next audit to be compared with.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CAST to CHAR Cuts a Value to One Character on PostgreSQL</title>
      <link>https://baresql.com/cast-to-char-cuts-a-value-to-one-character-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/cast-to-char-cuts-a-value-to-one-character-on-postgresql/</guid>
      <description>&lt;p&gt;&lt;code&gt;CAST(order_id AS CHAR)&lt;/code&gt; looks like a safe way to turn a number into text. On PostgreSQL CHAR without a length means one character, so order 123 comes back as &lt;code&gt;&#39;1&#39;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL reads CHAR without a length as CHAR(1) and cuts the value to fit. With a length, CHAR(10), it pads the value with spaces. MySQL accepts CHAR in CAST but neither VARCHAR nor TEXT, and MariaDB accepts VARCHAR but not TEXT.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CAST to DATE Returns the Number 2024 on SQLite Because It Has No Date Type</title>
      <link>https://baresql.com/cast-to-date-returns-the-number-2024-on-sqlite-because-it-has-no-date-type/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/cast-to-date-returns-the-number-2024-on-sqlite-because-it-has-no-date-type/</guid>
      <description>&lt;p&gt;Filtering orders with &lt;code&gt;CAST(created_at AS DATE) = &#39;2024-05-06&#39;&lt;/code&gt; works on PostgreSQL, MySQL and MariaDB. SQLite has no date type, turns the CAST into a number and keeps only the year, so the filter never matches.&lt;/p&gt;&#xA;&lt;p&gt;SQLite keeps dates as text or numbers and reads a CAST to DATE, DATETIME or TIMESTAMP as a conversion to a number, which keeps only the leading digits. MySQL and MariaDB also refuse CAST(x AS TIMESTAMP), and PostgreSQL refuses DATETIME.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CAST to INTEGER Fails on MySQL and Rounds Differently on SQLite and PostgreSQL</title>
      <link>https://baresql.com/cast-to-integer-fails-on-mysql-and-rounds-differently-on-sqlite-and-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/cast-to-integer-fails-on-mysql-and-rounds-differently-on-sqlite-and-postgresql/</guid>
      <description>&lt;p&gt;Turning a price into a whole number with &lt;code&gt;CAST(5.7 AS INTEGER)&lt;/code&gt; gives 5 on SQLite and 6 on PostgreSQL and MariaDB. MySQL doesn’t run it at all.&lt;/p&gt;&#xA;&lt;p&gt;SQLite drops the fraction and PostgreSQL and MariaDB round it. MySQL takes only SIGNED and UNSIGNED as integer types in CAST, and PostgreSQL refuses both. Decimals differ as well: SQLite ignores the scale of CAST(x AS DECIMAL(10, 1)) and keeps every digit, and MySQL and MariaDB read DECIMAL without a precision as DECIMAL(10, 0), a whole number.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CHAR Columns Come Back Padded With Spaces on PostgreSQL</title>
      <link>https://baresql.com/char-columns-come-back-padded-with-spaces-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/char-columns-come-back-padded-with-spaces-on-postgresql/</guid>
      <description>&lt;p&gt;An order status kept in a &lt;code&gt;CHAR(10)&lt;/code&gt; column compares as expected in SQL on every engine. In the application code, though, a check for paid orders fails on PostgreSQL alone.&lt;/p&gt;&#xA;&lt;p&gt;A CHAR(5) column holding &#39;a&#39; returns &#39;a    &#39; on PostgreSQL. Comparisons there ignore the padding, so WHERE c = &#39;a&#39; still matches, but the spaces reach the application and CONCAT. MySQL and MariaDB strip trailing spaces when they read CHAR values, and SQLite does not apply the length at all.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Column Types Like DATETIME and BYTEA Exist on Some SQL Engines Only</title>
      <link>https://baresql.com/column-types-like-datetime-and-bytea-exist-on-some-sql-engines-only/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/column-types-like-datetime-and-bytea-exist-on-some-sql-engines-only/</guid>
      <description>&lt;p&gt;A schema with DATETIME and JSONB columns runs on SQLite without complaint. Both types fail on at least one of the other engines.&lt;/p&gt;&#xA;&lt;p&gt;SQLite accepts nearly any type name, so a schema that works there can still fail elsewhere. Each type in the table was used in a CREATE TABLE on all four engines.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-type-not-everywhere.png&#34; alt=&#34;CREATE TABLE t (d DATETIME); SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 0; PostgreSQL 16.15 error: type &amp;quot;datetime&amp;quot; does not exist; MySQL 8.0.46 0; MariaDB 10.11.14 0.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Columns Missing From GROUP BY Work on SQLite and MariaDB and Fail on PostgreSQL and MySQL</title>
      <link>https://baresql.com/columns-missing-from-group-by-work-on-sqlite-and-mariadb-and-fail-on-postgresql-and-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/columns-missing-from-group-by-work-on-sqlite-and-mariadb-and-fail-on-postgresql-and-mysql/</guid>
      <description>&lt;p&gt;&lt;code&gt;SELECT customer_id, name, SUM(total) FROM orders GROUP BY customer_id&lt;/code&gt; is the kind of query that runs for years on SQLite or MariaDB. Point the same code at PostgreSQL or MySQL and it stops with an error, because name is neither grouped nor summed.&lt;/p&gt;&#xA;&lt;p&gt;SELECT a, b FROM t GROUP BY a asks for one b per group without saying which. SQLite and MariaDB pick one (MariaDB’s default mode leaves out ONLY_FULL_GROUP_BY). PostgreSQL and MySQL stop with an error, unless the grouped column is the table’s primary key, which fixes every other column of that table.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Comparing a Boolean With 1 Works on MySQL and SQLite and Fails on PostgreSQL</title>
      <link>https://baresql.com/comparing-a-boolean-with-1-works-on-mysql-and-sqlite-and-fails-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/comparing-a-boolean-with-1-works-on-mysql-and-sqlite-and-fails-on-postgresql/</guid>
      <description>&lt;p&gt;&lt;code&gt;WHERE active = 1&lt;/code&gt; is the usual way to test a flag on MySQL, and SQLite and MariaDB read it the same way. Moved to PostgreSQL, the query stops with an error.&lt;/p&gt;&#xA;&lt;p&gt;On SQLite, MySQL and MariaDB a boolean is the number 0 or 1, and TRUE is another way to write 1. PostgreSQL has a real boolean type with no automatic conversion from integers, so code written for MySQL that compares flags with 1 stops there.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Comparing a Text Column With the Number 0 Matches Codes Like abc on MySQL and Fails on PostgreSQL</title>
      <link>https://baresql.com/comparing-a-text-column-with-the-number-0-matches-codes-like-abc-on-mysql-and-fails-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/comparing-a-text-column-with-the-number-0-matches-codes-like-abc-on-mysql-and-fails-on-postgresql/</guid>
      <description>&lt;p&gt;Product codes are text, and a query that forgets the quotes, &lt;code&gt;WHERE code = 0&lt;/code&gt;, still runs on MySQL and MariaDB. There it returns codes that have nothing to do with 0.&lt;/p&gt;&#xA;&lt;p&gt;MySQL and MariaDB turn the text into a number, and text that does not start with digits becomes 0, so WHERE code = 0 finds every code like &#39;abc&#39;. SQLite compares the number as text and finds nothing. PostgreSQL refuses to compare character varying with integer. &#39;1&#39; = 1 trips over the same thing in another way: false on SQLite, true on the other three.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CONCAT With a NULL Argument Returns NULL on MySQL and Skips It on PostgreSQL</title>
      <link>https://baresql.com/concat-with-a-null-argument-returns-null-on-mysql-and-skips-it-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/concat-with-a-null-argument-returns-null-on-mysql-and-skips-it-on-postgresql/</guid>
      <description>&lt;p&gt;A customer list built with &lt;code&gt;CONCAT(first_name, &#39; &#39;, last_name)&lt;/code&gt; looks complete on PostgreSQL and SQLite. Moved to MySQL or MariaDB, the same query returns NULL for some customers.&lt;/p&gt;&#xA;&lt;p&gt;A person without a last name comes out as &#39;Ann &#39; on SQLite and PostgreSQL and as NULL on MySQL and MariaDB, so a list of names quietly loses entries there. CONCAT_WS skips NULL arguments on all four engines.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-concat-null.png&#34; alt=&#34;SELECT CONCAT(first_name, &#39; &#39;, last_name) FROM people, run on four engines: SQLite 3.45.1 &#39;Ann &#39;; PostgreSQL 16.15 &#39;Ann &#39;; MySQL 8.0.46 NULL; MariaDB 10.11.14 NULL.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>COUNT DISTINCT With Two Columns Works on MySQL and Fails on PostgreSQL and SQLite</title>
      <link>https://baresql.com/count-distinct-with-two-columns-works-on-mysql-and-fails-on-postgresql-and-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/count-distinct-with-two-columns-works-on-mysql-and-fails-on-postgresql-and-sqlite/</guid>
      <description>&lt;p&gt;&lt;code&gt;COUNT(DISTINCT customer_id, product_id)&lt;/code&gt; counts the distinct pairs on MySQL and MariaDB. SQLite and PostgreSQL take a single expression in &lt;code&gt;COUNT(DISTINCT ...)&lt;/code&gt; and refuse it.&lt;/p&gt;&#xA;&lt;p&gt;SQLite reports the wrong number of arguments and PostgreSQL a missing function.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-count-distinct-columns.png&#34; alt=&#34;SELECT COUNT(DISTINCT a, b) FROM t, run on four engines: SQLite 3.45.1 error: wrong number of arguments to function COUNT(); PostgreSQL 16.15 error: function count(integer, integer) does not exist; MySQL 8.0.46 2; MariaDB 10.11.14 2.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>DISTINCT ON Works on PostgreSQL Only</title>
      <link>https://baresql.com/distinct-on-works-on-postgresql-only/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/distinct-on-works-on-postgresql-only/</guid>
      <description>&lt;p&gt;&lt;code&gt;DISTINCT ON (customer_id)&lt;/code&gt; is a neat way to keep each customer’s latest order on PostgreSQL, and none of the other three engines has it.&lt;/p&gt;&#xA;&lt;p&gt;SQLite, MySQL and MariaDB report a syntax error. Window functions say the same thing on all four (SQLite 3.25, MySQL 8.0 and MariaDB 10.2 and later).&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-distinct-on.png&#34; alt=&#34;SELECT DISTINCT ON (a) a, b FROM t ORDER BY a, b, run on four engines: SQLite 3.45.1 error: near &amp;quot;ON&amp;quot;: syntax error; PostgreSQL 16.15 1, 3; 2, 4; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;ON (a) a, b FROM t ORDER BY a, b&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;ON (a) a, b FROM t ORDER BY a, b&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Dividing Decimals Gives Six Decimal Places on MySQL and Sixteen on PostgreSQL</title>
      <link>https://baresql.com/dividing-decimals-gives-six-decimal-places-on-mysql-and-sixteen-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/dividing-decimals-gives-six-decimal-places-on-mysql-and-sixteen-on-postgresql/</guid>
      <description>&lt;p&gt;Split an invoice of 80.50 three ways and MySQL and MariaDB hand back 26.833333, PostgreSQL 26.8333333333333333 and SQLite a floating point number. Most of the time nobody notices. Then a report that adds the shares back up gets a different total on MySQL than on PostgreSQL.&lt;/p&gt;&#xA;&lt;p&gt;MySQL and MariaDB give the result of a decimal division four more decimal places than the dividend has (the div_precision_increment setting) and cut it there. PostgreSQL keeps at least 16 significant digits, and SQLite divides as floating point numbers. Shares of an amount and averages of a DECIMAL column then differ in their last digits.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Division by Zero Is NULL on SQLite and MySQL and an Error on PostgreSQL</title>
      <link>https://baresql.com/division-by-zero-is-null-on-sqlite-and-mysql-and-an-error-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/division-by-zero-is-null-on-sqlite-and-mysql-and-an-error-on-postgresql/</guid>
      <description>&lt;p&gt;A report that divides revenue by visits fails on PostgreSQL the first time a page has no visits. On SQLite, MySQL and MariaDB the same row quietly shows NULL.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL stops the whole statement. SQLite, MySQL and MariaDB answer NULL, MySQL and MariaDB with a warning, and in INSERT and UPDATE with an error when the strict mode includes ERROR_FOR_DIVISION_BY_ZERO (the default). The remainder operator % behaves the same way.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Double Dash Comments Need a Space After Them on MySQL and MariaDB</title>
      <link>https://baresql.com/double-dash-comments-need-a-space-after-them-on-mysql-and-mariadb/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/double-dash-comments-need-a-space-after-them-on-mysql-and-mariadb/</guid>
      <description>&lt;p&gt;In a line like &lt;code&gt;SELECT price --discount&lt;/code&gt;, left over from debugging, SQLite and PostgreSQL read &lt;code&gt;--discount&lt;/code&gt; as a comment. MySQL and MariaDB add the discount to the price instead.&lt;/p&gt;&#xA;&lt;p&gt;SQLite and PostgreSQL treat every -- as the start of a comment. MySQL and MariaDB need a space, a tab or a line break after the dashes; otherwise they are two minus signs.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-dash-comment.png&#34; alt=&#34;SELECT 1 --1, run on four engines: SQLite 3.45.1 1; PostgreSQL 16.15 1; MySQL 8.0.46 2; MariaDB 10.11.14 2.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Double Quotes Name a Column on PostgreSQL and Make a String on MySQL</title>
      <link>https://baresql.com/double-quotes-name-a-column-on-postgresql-and-make-a-string-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/double-quotes-name-a-column-on-postgresql-and-make-a-string-on-mysql/</guid>
      <description>&lt;p&gt;A query copied from PostgreSQL that puts a column name in double quotes runs on MySQL without an error, and never reads the column.&lt;/p&gt;&#xA;&lt;p&gt;Standard SQL quotes names with double quotes, and SQLite and PostgreSQL follow it. MySQL and MariaDB read &amp;quot;...&amp;quot; like &#39;...&#39; unless the ANSI_QUOTES mode is on: the query still runs and returns the same text on every row. SQLite also turns a double-quoted word into a string when no column has that name.&lt;/p&gt;</description>
    </item>
    <item>
      <title>FETCH FIRST ROWS ONLY Works on PostgreSQL and MariaDB and Fails on MySQL and SQLite</title>
      <link>https://baresql.com/fetch-first-rows-only-works-on-postgresql-and-mariadb-and-fails-on-mysql-and-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/fetch-first-rows-only-works-on-postgresql-and-mariadb-and-fails-on-mysql-and-sqlite/</guid>
      <description>&lt;p&gt;&lt;code&gt;FETCH FIRST 10 ROWS ONLY&lt;/code&gt; is the standard way to ask for the top ten, and PostgreSQL and MariaDB accept it. SQLite and MySQL refuse the query.&lt;/p&gt;&#xA;&lt;p&gt;MariaDB added FETCH FIRST in version 10.6. SQLite and MySQL know only LIMIT, and they refuse OFFSET n ROWS for the same reason.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-fetch-first.png&#34; alt=&#34;SELECT x FROM t ORDER BY x FETCH FIRST 2 ROWS ONLY, run on four engines: SQLite 3.45.1 error: near &amp;quot;FETCH&amp;quot;: syntax error; PostgreSQL 16.15 1; 2; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;FETCH FIRST 2 ROWS ONLY&#39; at line 1; MariaDB 10.11.14 1; 2.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>FULL OUTER JOIN Works on PostgreSQL and SQLite and Fails on MySQL and MariaDB</title>
      <link>https://baresql.com/full-outer-join-works-on-postgresql-and-sqlite-and-fails-on-mysql-and-mariadb/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/full-outer-join-works-on-postgresql-and-sqlite-and-fails-on-mysql-and-mariadb/</guid>
      <description>&lt;p&gt;Matching two lists, such as invoices and payments, to see what is missing on either side calls for a FULL OUTER JOIN. PostgreSQL and SQLite run it, and MySQL and MariaDB refuse it.&lt;/p&gt;&#xA;&lt;p&gt;MySQL and MariaDB have LEFT and RIGHT joins only. SQLite added RIGHT and FULL joins in version 3.39.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-full-outer-join.png&#34; alt=&#34;SELECT a.x, b.x FROM a FULL OUTER JOIN b ON a.x = b.x, run on four engines: SQLite 3.45.1 1, NULL; 2, 2; NULL, 3; PostgreSQL 16.15 1, NULL; 2, 2; NULL, 3; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;OUTER JOIN b ON a.x = b.x&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;OUTER JOIN b ON a.x = b.x&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>GREATEST Skips NULL on PostgreSQL and Returns NULL on MySQL</title>
      <link>https://baresql.com/greatest-skips-null-on-postgresql-and-returns-null-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/greatest-skips-null-on-postgresql-and-returns-null-on-mysql/</guid>
      <description>&lt;p&gt;&lt;code&gt;GREATEST(sale_price, list_price)&lt;/code&gt; picks the larger of two prices. When one of them is NULL, PostgreSQL still picks the other price, and no other engine does.&lt;/p&gt;&#xA;&lt;p&gt;GREATEST(1, NULL) is 1 on PostgreSQL and NULL on MySQL and MariaDB. SQLite has the same idea under the names MAX(a, b) and MIN(a, b) with two or more arguments; they return NULL when any argument is NULL, and the other engines read MAX and MIN as aggregates.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Hash Comments Work on MySQL and MariaDB and Become an Operator on PostgreSQL</title>
      <link>https://baresql.com/hash-comments-work-on-mysql-and-mariadb-and-become-an-operator-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/hash-comments-work-on-mysql-and-mariadb-and-become-an-operator-on-postgresql/</guid>
      <description>&lt;p&gt;MySQL and MariaDB skip everything from a &lt;code&gt;#&lt;/code&gt; to the end of the line. On SQLite and PostgreSQL that &lt;code&gt;#&lt;/code&gt; is still part of the statement.&lt;/p&gt;&#xA;&lt;p&gt;Scripts written for the mysql client often use # comments. SQLite stops with a syntax error. On PostgreSQL # is a bitwise exclusive or, so the rest of the line becomes part of the expression.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-hash-comment.png&#34; alt=&#34;SELECT 1 # one, run on four engines: SQLite 3.45.1 error: unrecognized token: &amp;quot;#&amp;quot;; PostgreSQL 16.15 error: column &amp;quot;one&amp;quot; does not exist; MySQL 8.0.46 1; MariaDB 10.11.14 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How BareSQL Audit Finds Every Column a PostgreSQL Login Can Read</title>
      <link>https://baresql.com/how-baresql-audit-finds-every-column-a-postgresql-login-can-read/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/how-baresql-audit-finds-every-column-a-postgresql-login-can-read/</guid>
      <description>&lt;p&gt;PostgreSQL keeps the answer to “who can read this column?” in many places. Grants sit in access lists on tables and on single columns. Role memberships nest, and since PostgreSQL 16 each one says separately whether its rights are inherited and whether SET ROLE is allowed. Views read their tables with their owner’s rights unless they say otherwise, row security policies filter what a table hands out, and default privileges hand out tables that don’t exist yet.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How BareSQL Audit Was Tested Against Planted Leaks on Six PostgreSQL Versions</title>
      <link>https://baresql.com/how-baresql-audit-was-tested-against-planted-leaks-on-six-postgresql-versions/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/how-baresql-audit-was-tested-against-planted-leaks-on-six-postgresql-versions/</guid>
      <description>&lt;p&gt;An access audit can be wrong in two directions. If it misses a route, a leak goes unnoticed because the report said all was well. If it invents one, people spend a day chasing access that was never there and stop trusting the report. So BareSQL Audit is tested against databases where every right answer is known in advance, and against the one judge that can’t be argued with, the PostgreSQL server itself.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How BareSQL Portable Finds SQL That Answers Differently on Four Engines</title>
      <link>https://baresql.com/how-baresql-portable-finds-sql-that-answers-differently-on-four-engines/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/how-baresql-portable-finds-sql-that-answers-differently-on-four-engines/</guid>
      <description>&lt;p&gt;Most SQL that fails on another engine fails loudly, with a syntax error on the first run. The expensive kind runs everywhere and answers differently. On the sample shop’s products, &lt;code&gt;SUM(stock) / COUNT(*)&lt;/code&gt; is 3 on SQLite and PostgreSQL and 3.3333 on MySQL. &lt;code&gt;ORDER BY last_order&lt;/code&gt; puts the customers who never ordered first on three engines and last on PostgreSQL. A search with LIKE finds Ann on SQLite and misses her on PostgreSQL, and &lt;code&gt;&#39;1&#39; = 1&lt;/code&gt; is false on SQLite and true on the other three. None of these raise an error.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How BareSQL Portable Was Tested on Four Real Database Engines</title>
      <link>https://baresql.com/how-baresql-portable-was-tested-on-four-real-database-engines/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/how-baresql-portable-was-tested-on-four-real-database-engines/</guid>
      <description>&lt;p&gt;Every message &lt;code&gt;baresql lint&lt;/code&gt; prints is a claim about four database engines. “GROUP_CONCAT() works on SQLite, MySQL and MariaDB; PostgreSQL refuses it.” Claims like that go stale, since engines add and drop functions between versions. Written from memory, a catalog of differences would be wrong in places on the day it shipped. So the answers BareSQL Portable quotes are the ones the engines gave when the catalog ran. A few messages also name the release in which an engine changed, such as SQLite 3.44 for &lt;code&gt;CONCAT()&lt;/code&gt;, and those come from the engines&amp;rsquo; release notes.&lt;/p&gt;</description>
    </item>
    <item>
      <title>INSERT IGNORE Becomes INSERT OR IGNORE on SQLite and ON CONFLICT DO NOTHING on PostgreSQL</title>
      <link>https://baresql.com/insert-ignore-becomes-insert-or-ignore-on-sqlite-and-on-conflict-do-nothing-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/insert-ignore-becomes-insert-or-ignore-on-sqlite-and-on-conflict-do-nothing-on-postgresql/</guid>
      <description>&lt;p&gt;Loading a file of events that may hold rows you already have calls for an insert that skips duplicates. MySQL and MariaDB spell it &lt;code&gt;INSERT IGNORE&lt;/code&gt; and SQLite &lt;code&gt;INSERT OR IGNORE&lt;/code&gt;, while PostgreSQL and SQLite take &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Each form skips rows that would break a unique key, and each fails on the engines that use another one. INSERT IGNORE also hides other errors on MySQL and MariaDB, such as values that do not fit the column.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Integer Division Drops the Fraction on SQLite and PostgreSQL and Keeps It on MySQL</title>
      <link>https://baresql.com/integer-division-drops-the-fraction-on-sqlite-and-postgresql-and-keeps-it-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/integer-division-drops-the-fraction-on-sqlite-and-postgresql-and-keeps-it-on-mysql/</guid>
      <description>&lt;p&gt;You write an average as &lt;code&gt;SUM(stock) / COUNT(*)&lt;/code&gt;, try it on SQLite and get 3. MySQL gives 3.3333 for the same rows. Both are doing what they were built to do, and they disagree about what dividing two integers means.&lt;/p&gt;&#xA;&lt;p&gt;SQLite and PostgreSQL give an integer result when both sides of / are integers, so 7 / 2 is 3. MySQL and MariaDB always divide as decimals and answer 3.5000. Averages written as SUM(x) / COUNT(*) are where this usually bites: the sum of an integer column is an integer on SQLite and PostgreSQL, so the average loses its fraction there.&lt;/p&gt;</description>
    </item>
    <item>
      <title>LENGTH Counts Bytes on MySQL and MariaDB and Characters on PostgreSQL and SQLite</title>
      <link>https://baresql.com/length-counts-bytes-on-mysql-and-mariadb-and-characters-on-postgresql-and-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/length-counts-bytes-on-mysql-and-mariadb-and-characters-on-postgresql-and-sqlite/</guid>
      <description>&lt;p&gt;A check that a name is at most 20 characters long, written with &lt;code&gt;LENGTH(name)&lt;/code&gt;, lets 20 letters through on SQLite and PostgreSQL. MySQL and MariaDB count bytes instead, so a name with accents reaches the limit sooner.&lt;/p&gt;&#xA;&lt;p&gt;In UTF-8 a letter outside A to Z takes two to four bytes. MySQL and MariaDB count characters with CHAR_LENGTH, which SQLite lacks. Length checks and cut-offs drift apart on names and addresses with accents.&lt;/p&gt;</description>
    </item>
    <item>
      <title>LIKE Ignores Letter Case on SQLite and MySQL and Respects It on PostgreSQL</title>
      <link>https://baresql.com/like-ignores-letter-case-on-sqlite-and-mysql-and-respects-it-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/like-ignores-letter-case-on-sqlite-and-mysql-and-respects-it-on-postgresql/</guid>
      <description>&lt;p&gt;A search box that runs &lt;code&gt;WHERE name LIKE &#39;ann%&#39;&lt;/code&gt; finds Ann on SQLite, MySQL and MariaDB. On PostgreSQL it misses her, because LIKE there compares letters exactly.&lt;/p&gt;&#xA;&lt;p&gt;SQLite’s LIKE ignores case for the letters A to Z only. MySQL and MariaDB follow the collation, which ignores case by default. PostgreSQL’s LIKE is exact; its ILIKE ignores case, and exists on PostgreSQL only.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-like-case.png&#34; alt=&#34;SELECT &#39;abc&#39; LIKE &#39;ABC&#39;, run on four engines: SQLite 3.45.1 1; PostgreSQL 16.15 false; MySQL 8.0.46 1; MariaDB 10.11.14 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>LIMIT With a Comma Works on MySQL and SQLite and Fails on PostgreSQL</title>
      <link>https://baresql.com/limit-with-a-comma-works-on-mysql-and-sqlite-and-fails-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/limit-with-a-comma-works-on-mysql-and-sqlite-and-fails-on-postgresql/</guid>
      <description>&lt;p&gt;Paging code written for MySQL often says &lt;code&gt;LIMIT 20, 10&lt;/code&gt;: skip 20 rows, return 10. SQLite and MariaDB read it the same way. PostgreSQL refuses it.&lt;/p&gt;&#xA;&lt;p&gt;In LIMIT a, b the first number is the offset and the second the count, the reverse of the order in which LIMIT ... OFFSET reads.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-limit-comma.png&#34; alt=&#34;SELECT x FROM t ORDER BY x LIMIT 1, 2, run on four engines: SQLite 3.45.1 2; 3; PostgreSQL 16.15 error: LIMIT #,# syntax is not supported; MySQL 8.0.46 2; 3; MariaDB 10.11.14 2; 3.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>LOG With One Argument Is Base 10 on PostgreSQL and the Natural Logarithm on MySQL</title>
      <link>https://baresql.com/log-with-one-argument-is-base-10-on-postgresql-and-the-natural-logarithm-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/log-with-one-argument-is-base-10-on-postgresql-and-the-natural-logarithm-on-mysql/</guid>
      <description>&lt;p&gt;A score or a scale built on &lt;code&gt;LOG()&lt;/code&gt; shifts when the code moves to another engine, because &lt;code&gt;LOG(100)&lt;/code&gt; doesn’t mean the same logarithm everywhere.&lt;/p&gt;&#xA;&lt;p&gt;LOG(100) is 2 on SQLite and PostgreSQL and 4.605 on MySQL and MariaDB. With two arguments, LOG(b, x) is the logarithm of x to base b on all four. SQLite has LOG, LN, LOG10 and POWER only when it is built with its math functions: the sqlite3 program is by default since version 3.35, and some systems ship it without them.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL and MariaDB on Linux Treat Table Names as Case Sensitive</title>
      <link>https://baresql.com/mysql-and-mariadb-on-linux-treat-table-names-as-case-sensitive/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-and-mariadb-on-linux-treat-table-names-as-case-sensitive/</guid>
      <description>&lt;p&gt;Code that creates a table as Users and later reads users works on SQLite and PostgreSQL. On MySQL or MariaDB running on Linux it fails, since users is another table that doesn’t exist, while the same code on a Mac works, which makes the bug easy to miss.&lt;/p&gt;&#xA;&lt;p&gt;MySQL and MariaDB keep each table in files named after it, and file names on Linux are case-sensitive (lower_case_table_names = 0, the default there). On Windows and macOS the default makes names case-insensitive. SQLite and PostgreSQL ignore case in unquoted names.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL and MariaDB Refuse LIMIT Inside an IN Subquery</title>
      <link>https://baresql.com/mysql-and-mariadb-refuse-limit-inside-an-in-subquery/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-and-mariadb-refuse-limit-inside-an-in-subquery/</guid>
      <description>&lt;p&gt;Finding the customers behind the ten latest orders with &lt;code&gt;WHERE id IN (SELECT customer_id FROM orders ORDER BY created_at DESC LIMIT 10)&lt;/code&gt; works on SQLite and PostgreSQL. MySQL and MariaDB refuse it.&lt;/p&gt;&#xA;&lt;p&gt;Both report that this version doesn’t yet support &#39;LIMIT &amp;amp; IN/ALL/ANY/SOME subquery&#39;. LIMIT is fine in a subquery used as a table and in one that returns a single value.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-limit-in-subquery.png&#34; alt=&#34;SELECT x FROM t WHERE x IN (SELECT x FROM t ORDER BY x LIMIT 1), run on four engines: SQLite 3.45.1 1; PostgreSQL 16.15 1; MySQL 8.0.46 error: This version of MySQL doesn&#39;t yet support &#39;LIMIT &amp;amp; IN/ALL/ANY/SOME subquery&#39;; MariaDB 10.11.14 error: This version of MariaDB doesn&#39;t yet support &#39;LIMIT &amp;amp; IN/ALL/ANY/SOME subquery&#39;.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Compares Text Without Regard to Letter Case and PostgreSQL Compares It Exactly</title>
      <link>https://baresql.com/mysql-compares-text-without-regard-to-letter-case-and-postgresql-compares-it-exactly/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-compares-text-without-regard-to-letter-case-and-postgresql-compares-it-exactly/</guid>
      <description>&lt;p&gt;Sign-ups as &lt;code&gt;ann@example.com&lt;/code&gt; and &lt;code&gt;Ann@example.com&lt;/code&gt; are two users on PostgreSQL and SQLite. On MySQL and MariaDB a unique key on the email column refuses the second sign-up.&lt;/p&gt;&#xA;&lt;p&gt;The default collations of MySQL and MariaDB ignore case and accents, so &#39;abc&#39; = &#39;ABC&#39; and &#39;e&#39; = &#39;é&#39; are true there and false on SQLite and PostgreSQL. The same goes for joins, GROUP BY, DISTINCT and unique keys: two values that differ only in case count as one on MySQL and as two elsewhere. Whether an answer changes depends on the data, so lint reports this as information.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Refuses a Key on a TEXT Column Without a Prefix Length</title>
      <link>https://baresql.com/mysql-refuses-a-key-on-a-text-column-without-a-prefix-length/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-refuses-a-key-on-a-text-column-without-a-prefix-length/</guid>
      <description>&lt;p&gt;A TEXT column as a primary key is ordinary on SQLite and PostgreSQL. MySQL and MariaDB refuse it, and MySQL refuses a plain index or unique key on TEXT as well.&lt;/p&gt;&#xA;&lt;p&gt;SQLite and PostgreSQL index TEXT columns as they are. MySQL wants a prefix length, INDEX (x(100)), which the others reject. MariaDB 10.4 and later accept plain indexes and unique keys on TEXT columns.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-index-on-text.png&#34; alt=&#34;CREATE TABLE t (x TEXT PRIMARY KEY); SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 0; PostgreSQL 16.15 0; MySQL 8.0.46 error: BLOB/TEXT column &#39;x&#39; used in key specification without a key length; MariaDB 10.11.14 error: BLOB/TEXT column &#39;x&#39; used in key specification without a key length.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Refuses a Plain DEFAULT Value on TEXT Columns</title>
      <link>https://baresql.com/mysql-refuses-a-plain-default-value-on-text-columns/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-refuses-a-plain-default-value-on-text-columns/</guid>
      <description>&lt;p&gt;A notes column declared as &lt;code&gt;TEXT DEFAULT &#39;&#39;&lt;/code&gt; works on SQLite, PostgreSQL and MariaDB. MySQL refuses the CREATE TABLE.&lt;/p&gt;&#xA;&lt;p&gt;MySQL 8.0.13 and later accept a default written as an expression in parentheses, DEFAULT (&#39;a&#39;), which the other three engines accept as well.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-text-default.png&#34; alt=&#34;CREATE TABLE t (id INTEGER, x TEXT DEFAULT &#39;a&#39;); INSERT INTO t (id) VALUES (1); SELECT x FROM t, run on four engines: SQLite 3.45.1 &#39;a&#39;; PostgreSQL 16.15 &#39;a&#39;; MySQL 8.0.46 error: BLOB, TEXT, GEOMETRY or JSON column &#39;x&#39; can&#39;t have a default value; MariaDB 10.11.14 &#39;a&#39;.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Refuses an UPDATE Whose Subquery Reads the Same Table</title>
      <link>https://baresql.com/mysql-refuses-an-update-whose-subquery-reads-the-same-table/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-refuses-an-update-whose-subquery-reads-the-same-table/</guid>
      <description>&lt;p&gt;Marking rows with &lt;code&gt;UPDATE t SET flag = 1 WHERE id IN (SELECT id FROM t WHERE ...)&lt;/code&gt; works on SQLite, PostgreSQL and MariaDB. MySQL refuses an UPDATE or DELETE whose subquery reads the table being changed.&lt;/p&gt;&#xA;&lt;p&gt;MySQL says you can’t specify target table &#39;t&#39; for update in FROM clause. MariaDB lifted the limit in version 10.3.2. A subquery used as a table works on MySQL too, because MySQL then computes its rows before it changes anything.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Refuses CREATE INDEX IF NOT EXISTS</title>
      <link>https://baresql.com/mysql-refuses-create-index-if-not-exists/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-refuses-create-index-if-not-exists/</guid>
      <description>&lt;p&gt;Migrations that may run twice often say &lt;code&gt;CREATE INDEX IF NOT EXISTS&lt;/code&gt;, and SQLite, PostgreSQL and MariaDB accept it. MySQL stops the migration with a syntax error.&lt;/p&gt;&#xA;&lt;p&gt;MySQL accepts IF NOT EXISTS for tables and databases, and not for indexes.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-index-if-not-exists.png&#34; alt=&#34;CREATE TABLE t (x INTEGER); CREATE INDEX IF NOT EXISTS t_x ON t (x); SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 0; PostgreSQL 16.15 0; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;IF NOT EXISTS t_x ON t (x)&#39; at line 1; MariaDB 10.11.14 0.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Refuses INSERT RETURNING While PostgreSQL and SQLite Accept It</title>
      <link>https://baresql.com/mysql-refuses-insert-returning-while-postgresql-and-sqlite-accept-it/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-refuses-insert-returning-while-postgresql-and-sqlite-accept-it/</guid>
      <description>&lt;p&gt;&lt;code&gt;INSERT ... RETURNING id&lt;/code&gt; hands back the new row’s id in the same round trip on SQLite, PostgreSQL and MariaDB. MySQL has no RETURNING and refuses the statement.&lt;/p&gt;&#xA;&lt;p&gt;SQLite added RETURNING in version 3.35, and MariaDB in 10.5 for INSERT and DELETE (MariaDB refuses it on UPDATE). Each engine also has its own function for the last generated id: LAST_INSERT_ID() on MySQL and MariaDB, last_insert_rowid() on SQLite and lastval() on PostgreSQL.&lt;/p&gt;</description>
    </item>
    <item>
      <title>MySQL Table Options Like ENGINE and CHARSET Fail on PostgreSQL and SQLite</title>
      <link>https://baresql.com/mysql-table-options-like-engine-and-charset-fail-on-postgresql-and-sqlite/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/mysql-table-options-like-engine-and-charset-fail-on-postgresql-and-sqlite/</guid>
      <description>&lt;p&gt;A MySQL schema ends its CREATE TABLE statements with options such as &lt;code&gt;ENGINE=InnoDB DEFAULT CHARSET=utf8mb4&lt;/code&gt;. SQLite and PostgreSQL refuse the statement at the first option.&lt;/p&gt;&#xA;&lt;p&gt;Dumps made with mysqldump carry these options on every table. SQLite’s own options, WITHOUT ROWID and STRICT, fail on the other three engines in the same way.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-table-options.png&#34; alt=&#34;CREATE TABLE t (x INTEGER) ENGINE=InnoDB; SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 error: near &amp;quot;=&amp;quot;: syntax error; PostgreSQL 16.15 error: syntax error at or near &amp;quot;ENGINE&amp;quot;; MySQL 8.0.46 0; MariaDB 10.11.14 0.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>No Auto Increment Column Syntax Works on Every SQL Engine</title>
      <link>https://baresql.com/no-auto-increment-column-syntax-works-on-every-sql-engine/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/no-auto-increment-column-syntax-works-on-every-sql-engine/</guid>
      <description>&lt;p&gt;A schema meant for several engines needs ids that number themselves, and no single way of asking for that works on all four. SERIAL comes closest, and on SQLite it fails without a word.&lt;/p&gt;&#xA;&lt;p&gt;AUTO_INCREMENT belongs to MySQL and MariaDB, AUTOINCREMENT to SQLite, and GENERATED BY DEFAULT AS IDENTITY to PostgreSQL. SERIAL is PostgreSQL’s as well, and MySQL and MariaDB accept it as BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE. SQLite accepts any type name, so SERIAL is only a name there and the column stays NULL: SQLite numbers rows for a column declared INTEGER PRIMARY KEY and nothing else.&lt;/p&gt;</description>
    </item>
    <item>
      <title>NULL Sorts First on SQLite and MySQL and Last on PostgreSQL</title>
      <link>https://baresql.com/null-sorts-first-on-sqlite-and-mysql-and-last-on-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/null-sorts-first-on-sqlite-and-mysql-and-last-on-postgresql/</guid>
      <description>&lt;p&gt;Sort customers by the date of their last order and the ones who never ordered come first on SQLite, MySQL and MariaDB and last on PostgreSQL. The first page of the same report shows different people.&lt;/p&gt;&#xA;&lt;p&gt;The standard leaves the place of NULL in ORDER BY to the engine. PostgreSQL treats NULL as larger than every value, the others as smaller, and descending order turns both around. Paged results change when the column holds NULL, and so do window functions such as ROW_NUMBER() OVER (ORDER BY x).&lt;/p&gt;</description>
    </item>
    <item>
      <title>NULLS FIRST and NULLS LAST Fail on MySQL and MariaDB</title>
      <link>https://baresql.com/nulls-first-and-nulls-last-fail-on-mysql-and-mariadb/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/nulls-first-and-nulls-last-fail-on-mysql-and-mariadb/</guid>
      <description>&lt;p&gt;&lt;code&gt;ORDER BY shipped_at NULLS LAST&lt;/code&gt; is the tidy way to put unshipped orders at the end on PostgreSQL and SQLite. MySQL and MariaDB have no such syntax and refuse the query.&lt;/p&gt;&#xA;&lt;p&gt;SQLite added them in version 3.30. MySQL and MariaDB have no syntax for choosing where NULL sorts.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-nulls-first-last.png&#34; alt=&#34;SELECT x FROM t ORDER BY x NULLS LAST, run on four engines: SQLite 3.45.1 1; 2; NULL; PostgreSQL 16.15 1; 2; NULL; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;NULLS LAST&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;NULLS LAST&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Only SQLite Lets WHERE Use a Name From the Select List</title>
      <link>https://baresql.com/only-sqlite-lets-where-use-a-name-from-the-select-list/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/only-sqlite-lets-where-use-a-name-from-the-select-list/</guid>
      <description>&lt;p&gt;A query that names &lt;code&gt;price * quantity&lt;/code&gt; as total in the select list and then filters on total in WHERE runs on SQLite. PostgreSQL, MySQL and MariaDB refuse it, since the table has no column named total.&lt;/p&gt;&#xA;&lt;p&gt;WHERE is worked out before the select list, so in standard SQL the y of SELECT x * 2 AS y does not exist yet. SQLite looks it up anyway.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-alias-in-where.png&#34; alt=&#34;SELECT x * 2 AS y FROM t WHERE y &amp;gt; 2, run on four engines: SQLite 3.45.1 4; PostgreSQL 16.15 error: column &amp;quot;y&amp;quot; does not exist; MySQL 8.0.46 error: Unknown column &#39;y&#39; in &#39;where clause&#39;; MariaDB 10.11.14 error: Unknown column &#39;y&#39; in &#39;WHERE&#39;.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>PostgreSQL Refuses Select List Names in HAVING</title>
      <link>https://baresql.com/postgresql-refuses-select-list-names-in-having/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/postgresql-refuses-select-list-names-in-having/</guid>
      <description>&lt;p&gt;A report of repeat customers names its count of orders n in the select list and filters on that name in HAVING. SQLite, MySQL and MariaDB run it, and PostgreSQL refuses the query.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL looks up names in HAVING among the table’s columns only, so HAVING n &amp;gt; 1 fails when n is the name of COUNT(*).&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-alias-in-having.png&#34; alt=&#34;SELECT a, COUNT(*) AS n FROM t GROUP BY a HAVING n &amp;gt; 1, run on four engines: SQLite 3.45.1 1, 2; PostgreSQL 16.15 error: column &amp;quot;n&amp;quot; does not exist; MySQL 8.0.46 1, 2; MariaDB 10.11.14 1, 2.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>PostgreSQL Returns Column Names in Lower Case Unless You Quote Them</title>
      <link>https://baresql.com/postgresql-returns-column-names-in-lower-case-unless-you-quote-them/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/postgresql-returns-column-names-in-lower-case-unless-you-quote-them/</guid>
      <description>&lt;p&gt;&lt;code&gt;SELECT SUM(amount) AS Total&lt;/code&gt; gives a column named Total on SQLite, MySQL and MariaDB. PostgreSQL calls it total.&lt;/p&gt;&#xA;&lt;p&gt;SQLite, MySQL and MariaDB return names as written. PostgreSQL folds unquoted names to lower case, in aliases and in CREATE TABLE alike, so code that reads columns by name (row[&amp;quot;Total&amp;quot;]) finds nothing on PostgreSQL.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-column-name-case.png&#34; alt=&#34;SELECT 1 AS Total, run on four engines: SQLite 3.45.1 columns Total: 1; PostgreSQL 16.15 columns total: 1; MySQL 8.0.46 columns Total: 1; MariaDB 10.11.14 columns Total: 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Queries That Pass Their Tests on SQLite and Break on PostgreSQL in Production</title>
      <link>https://baresql.com/queries-that-pass-their-tests-on-sqlite-and-break-on-postgresql-in-production/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/queries-that-pass-their-tests-on-sqlite-and-break-on-postgresql-in-production/</guid>
      <description>&lt;p&gt;This case study uses a demo project in BareSQL’s code repository: a made-up booking app with a schema of two tables and eleven queries, written the way an app tested on SQLite tends to write them. Its test suite stays fast the usual way. Each test builds the schema in an in-memory SQLite database, runs the app’s queries and throws the database away.&lt;/p&gt;&#xA;&lt;p&gt;Production runs on PostgreSQL. Every query passes every test, and the question is how many of them do the same thing in production. The outputs below are what BareSQL printed.&lt;/p&gt;</description>
    </item>
    <item>
      <title>REAL on PostgreSQL and FLOAT on MySQL Are 4-Byte Floats That Drop Digits</title>
      <link>https://baresql.com/real-on-postgresql-and-float-on-mysql-are-4-byte-floats-that-drop-digits/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/real-on-postgresql-and-float-on-mysql-are-4-byte-floats-that-drop-digits/</guid>
      <description>&lt;p&gt;Measurements kept in a REAL column on PostgreSQL, or in a FLOAT column on MySQL or MariaDB, come back with fewer digits than went in. The same type names keep them all on the other engines.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL’s REAL is a 4-byte float, and so is FLOAT on MySQL and MariaDB, while the other engines store 8 bytes for the same name. 1.23456789 comes back as 1.2345679 or 1.23457.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-float-precision.png&#34; alt=&#34;CREATE TABLE t (r REAL, f FLOAT); INSERT INTO t VALUES (1.23456789, 1.23456789); SELECT r, f FROM t, run on four engines: SQLite 3.45.1 1.23456789, 1.23456789; PostgreSQL 16.15 1.2345679, 1.23456789; MySQL 8.0.46 1.23456789, 1.23457; MariaDB 10.11.14 1.23456789, 1.23457.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQL Functions That Exist on Some Engines Only and What to Write Instead</title>
      <link>https://baresql.com/sql-functions-that-exist-on-some-engines-only-and-what-to-write-instead/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sql-functions-that-exist-on-some-engines-only-and-what-to-write-instead/</guid>
      <description>&lt;p&gt;IFNULL, NOW, LEFT and STRING_AGG are everyday functions, and each one is missing on at least one of SQLite, PostgreSQL, MySQL and MariaDB. BareSQL Portable knows 78 functions like these.&lt;/p&gt;&#xA;&lt;p&gt;Each function in the table was called on all four engines, and the table shows where the call ran and what to write instead.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-function-not-everywhere.png&#34; alt=&#34;SELECT IFNULL(NULL, &#39;x&#39;), run on four engines: SQLite 3.45.1 &#39;x&#39;; PostgreSQL 16.15 error: function ifnull(unknown, unknown) does not exist; MySQL 8.0.46 &#39;x&#39;; MariaDB 10.11.14 &#39;x&#39;.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQL Operators That Exist on Some Engines Only and What to Write Instead</title>
      <link>https://baresql.com/sql-operators-that-exist-on-some-engines-only-and-what-to-write-instead/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sql-operators-that-exist-on-some-engines-only-and-what-to-write-instead/</guid>
      <description>&lt;p&gt;Operators such as &lt;code&gt;::&lt;/code&gt;, &lt;code&gt;ILIKE&lt;/code&gt;, &lt;code&gt;DIV&lt;/code&gt; and &lt;code&gt;XOR&lt;/code&gt; belong to one or two engines, and the others refuse them. BareSQL Portable knows 16 operators like these.&lt;/p&gt;&#xA;&lt;p&gt;Each operator in the table was used on all four engines, and the table shows where it ran and what to write instead.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-operator-not-everywhere.png&#34; alt=&#34;SELECT &#39;abc&#39; ILIKE &#39;ABC&#39;, run on four engines: SQLite 3.45.1 error: near &amp;quot;&#39;ABC&#39;&amp;quot;: syntax error; PostgreSQL 16.15 true; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;&#39;ABC&#39;&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;&#39;ABC&#39;&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQLite Has No CREATE OR REPLACE VIEW</title>
      <link>https://baresql.com/sqlite-has-no-create-or-replace-view/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sqlite-has-no-create-or-replace-view/</guid>
      <description>&lt;p&gt;&lt;code&gt;CREATE OR REPLACE VIEW&lt;/code&gt; lets a migration change a view in one step on PostgreSQL, MySQL and MariaDB. On SQLite the statement is a syntax error.&lt;/p&gt;&#xA;&lt;p&gt;SQLite can only drop a view and create it again.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-create-or-replace.png&#34; alt=&#34;CREATE OR REPLACE VIEW v AS SELECT x FROM t; SELECT COUNT(*) FROM v, run on four engines: SQLite 3.45.1 error: near &amp;quot;OR&amp;quot;: syntax error; PostgreSQL 16.15 0; MySQL 8.0.46 0; MariaDB 10.11.14 0.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQLite Has No TRUNCATE Statement</title>
      <link>https://baresql.com/sqlite-has-no-truncate-statement/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sqlite-has-no-truncate-statement/</guid>
      <description>&lt;p&gt;Test setups often empty their tables with &lt;code&gt;TRUNCATE TABLE&lt;/code&gt; between runs. PostgreSQL, MySQL and MariaDB have it, and SQLite refuses it.&lt;/p&gt;&#xA;&lt;p&gt;SQLite empties a table quickly with a DELETE FROM t that has no WHERE clause. On MySQL and MariaDB, TRUNCATE also resets the AUTO_INCREMENT counter, which DELETE leaves alone.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-truncate.png&#34; alt=&#34;TRUNCATE TABLE t; SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 error: near &amp;quot;TRUNCATE&amp;quot;: syntax error; PostgreSQL 16.15 0; MySQL 8.0.46 0; MariaDB 10.11.14 0.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQLite Keeps DECIMAL Values as Floats and Sums of Prices Pick Up Rounding Errors</title>
      <link>https://baresql.com/sqlite-keeps-decimal-values-as-floats-and-sums-of-prices-pick-up-rounding-errors/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sqlite-keeps-decimal-values-as-floats-and-sums-of-prices-pick-up-rounding-errors/</guid>
      <description>&lt;p&gt;A shop keeps its prices in a &lt;code&gt;DECIMAL(10, 2)&lt;/code&gt; column and checks that an order’s lines add up to its total. On SQLite that check can fail for an order of 0.10 and 0.20.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL, MySQL and MariaDB keep DECIMAL values exact, so the sum of 0.1 and 0.2 equals 0.3 there. SQLite has no exact decimal type: it stores 0.1 as the nearest binary fraction, the sum is 0.30000000000000004, and the comparison is false.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQLite LOWER and UPPER Change Only the Letters A to Z</title>
      <link>https://baresql.com/sqlite-lower-and-upper-change-only-the-letters-a-to-z/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sqlite-lower-and-upper-change-only-the-letters-a-to-z/</guid>
      <description>&lt;p&gt;Searching names with &lt;code&gt;LOWER(name) = LOWER(&#39;Émile&#39;)&lt;/code&gt; works on PostgreSQL, MySQL and MariaDB. On SQLite, LOWER changes only A to Z, so É stays É and a stored émile never matches.&lt;/p&gt;&#xA;&lt;p&gt;SQLite folds case for ASCII letters only, and so do its LIKE and COLLATE NOCASE, unless it is built with the ICU extension, as some systems ship it. Comparisons that use LOWER() to ignore case work on the other engines and fail on SQLite for names like Émile or Ødegaard.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SQLite Stores Text in INTEGER Columns and Ignores VARCHAR Lengths</title>
      <link>https://baresql.com/sqlite-stores-text-in-integer-columns-and-ignores-varchar-lengths/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/sqlite-stores-text-in-integer-columns-and-ignores-varchar-lengths/</guid>
      <description>&lt;p&gt;A test suite on SQLite passes with a quantity of &lt;code&gt;&#39;abc&#39;&lt;/code&gt;, or &lt;code&gt;&#39;Germany&#39;&lt;/code&gt; in a &lt;code&gt;VARCHAR(2)&lt;/code&gt; country column. PostgreSQL, MySQL and MariaDB refuse both values, so the bug shows up in production instead.&lt;/p&gt;&#xA;&lt;p&gt;Column types on SQLite are preferences: a value that does not fit is stored as it is, and lengths are never checked. Tests that run on SQLite then pass with data the production engine rejects. SQLite’s STRICT tables (version 3.37 and later) check types but not lengths, and the other engines do not accept the STRICT keyword.&lt;/p&gt;</description>
    </item>
    <item>
      <title>SUBSTR From Position 0 Returns Text on SQLite and PostgreSQL and Nothing on MySQL</title>
      <link>https://baresql.com/substr-from-position-0-returns-text-on-sqlite-and-postgresql-and-nothing-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/substr-from-position-0-returns-text-on-sqlite-and-postgresql-and-nothing-on-mysql/</guid>
      <description>&lt;p&gt;Code ported from a language that counts from 0 often ends up with &lt;code&gt;SUBSTR(code, 0, 3)&lt;/code&gt;. SQLite and PostgreSQL return the first two characters. MySQL and MariaDB return an empty string.&lt;/p&gt;&#xA;&lt;p&gt;Positions start at 1. SQLite and PostgreSQL count position 0 as part of the requested length, while MySQL and MariaDB return nothing for a start of 0. Negative starts differ as well: SQLite, MySQL and MariaDB count them from the end, and PostgreSQL counts them before the first character.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Caret Operator Is a Power on PostgreSQL and a Bitwise XOR on MySQL</title>
      <link>https://baresql.com/the-caret-operator-is-a-power-on-postgresql-and-a-bitwise-xor-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/the-caret-operator-is-a-power-on-postgresql-and-a-bitwise-xor-on-mysql/</guid>
      <description>&lt;p&gt;A growth formula written as &lt;code&gt;rate ^ years&lt;/code&gt; works on PostgreSQL. On MySQL and MariaDB it still runs and gives numbers that have nothing to do with growth: &lt;code&gt;2 ^ 3&lt;/code&gt; is 1 there.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL raises the left side to the power of the right one. MySQL and MariaDB combine the bits of both numbers. SQLite has no ^ operator.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-caret-operator.png&#34; alt=&#34;SELECT 2 ^ 3, run on four engines: SQLite 3.45.1 error: unrecognized token: &amp;quot;^&amp;quot;; PostgreSQL 16.15 8; MySQL 8.0.46 1; MariaDB 10.11.14 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Double Pipe Operator Joins Text on PostgreSQL and SQLite and Means OR on MySQL</title>
      <link>https://baresql.com/the-double-pipe-operator-joins-text-on-postgresql-and-sqlite-and-means-or-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/the-double-pipe-operator-joins-text-on-postgresql-and-sqlite-and-means-or-on-mysql/</guid>
      <description>&lt;p&gt;Code that builds labels with &lt;code&gt;first_name || &#39; &#39; || last_name&lt;/code&gt; works on PostgreSQL and SQLite. Move it to MySQL or MariaDB and every label comes back as 0, with no error to say why.&lt;/p&gt;&#xA;&lt;p&gt;The SQL standard joins strings with ||, and SQLite and PostgreSQL follow it. MySQL and MariaDB read || as a logical OR unless the server runs in the PIPES_AS_CONCAT mode, so both strings are turned into numbers and the answer is 0. The warnings they give for it seldom reach the application.&lt;/p&gt;</description>
    </item>
    <item>
      <title>UPDATE With a Join Needs Different Syntax on MySQL and PostgreSQL</title>
      <link>https://baresql.com/update-with-a-join-needs-different-syntax-on-mysql-and-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/update-with-a-join-needs-different-syntax-on-mysql-and-postgresql/</guid>
      <description>&lt;p&gt;Copying prices from a staging table with a join takes &lt;code&gt;UPDATE ... FROM&lt;/code&gt; on SQLite and PostgreSQL and &lt;code&gt;UPDATE ... JOIN&lt;/code&gt; on MySQL and MariaDB, and neither runs on all four.&lt;/p&gt;&#xA;&lt;p&gt;The engines use different syntax for changing rows based on another table. DELETE differs the same way: DELETE ... USING on PostgreSQL, DELETE t FROM t JOIN ... on MySQL and MariaDB.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-update-join.png&#34; alt=&#34;UPDATE t SET x = u.x FROM u WHERE u.id = t.id; SELECT x FROM t ORDER BY id, run on four engines: SQLite 3.45.1 7; 0; PostgreSQL 16.15 7; 0; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;FROM u WHERE u.id = t.id&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;FROM u WHERE u.id = t.id&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Upsert Needs ON CONFLICT on PostgreSQL and ON DUPLICATE KEY UPDATE on MySQL</title>
      <link>https://baresql.com/upsert-needs-on-conflict-on-postgresql-and-on-duplicate-key-update-on-mysql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/upsert-needs-on-conflict-on-postgresql-and-on-duplicate-key-update-on-mysql/</guid>
      <description>&lt;p&gt;Saving a setting that may or may not exist yet takes one statement on every engine, though not the same one on all four. SQLite and PostgreSQL use &lt;code&gt;ON CONFLICT ... DO UPDATE&lt;/code&gt;, MySQL and MariaDB &lt;code&gt;ON DUPLICATE KEY UPDATE&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;REPLACE INTO works on SQLite, MySQL and MariaDB, but it deletes the old row and inserts a new one, which fires delete triggers and resets the columns the statement leaves out. Of these four engines, MERGE exists on PostgreSQL 15 and later only.&lt;/p&gt;</description>
    </item>
    <item>
      <title>VARCHAR Without a Length Fails on MySQL and MariaDB</title>
      <link>https://baresql.com/varchar-without-a-length-fails-on-mysql-and-mariadb/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/varchar-without-a-length-fails-on-mysql-and-mariadb/</guid>
      <description>&lt;p&gt;Schemas written for PostgreSQL often declare names and notes as plain &lt;code&gt;VARCHAR&lt;/code&gt;, with no length. MySQL and MariaDB refuse that CREATE TABLE, since they want a length on every VARCHAR column.&lt;/p&gt;&#xA;&lt;p&gt;PostgreSQL reads VARCHAR without a length as text of any length, and SQLite ignores lengths anyway.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://baresql.com/images/rule-varchar-length.png&#34; alt=&#34;CREATE TABLE t (x VARCHAR); SELECT COUNT(*) FROM t, run on four engines: SQLite 3.45.1 0; PostgreSQL 16.15 0; MySQL 8.0.46 error: You have an error in your SQL syntax ... near &#39;)&#39; at line 1; MariaDB 10.11.14 error: You have an error in your SQL syntax ... near &#39;)&#39; at line 1.&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>What an AI Support Agent Could Read Before Its Database Login Went Live</title>
      <link>https://baresql.com/what-an-ai-support-agent-could-read-before-its-database-login-went-live/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/what-an-ai-support-agent-could-read-before-its-database-login-went-live/</guid>
      <description>&lt;p&gt;This case study runs on the sample multi-tenant database that BareSQL’s tests build. The software company in it is made up: it runs one PostgreSQL database for all its customers, the tenants, and row security keeps each tenant’s rows apart. Every output below is what BareSQL printed.&lt;/p&gt;&#xA;&lt;p&gt;The support team wants an AI agent to answer tickets, so the agent gets a login of its own, &lt;code&gt;saas_agent&lt;/code&gt;, in the &lt;code&gt;saas_support&lt;/code&gt; group that support’s views are granted to. The views were written for people, months ago, and nobody remembers exactly what they return. So before the login goes live, the team audits that one login.&lt;/p&gt;</description>
    </item>
    <item>
      <title>What Changed in the Answers When a MySQL App Moved to PostgreSQL</title>
      <link>https://baresql.com/what-changed-in-the-answers-when-a-mysql-app-moved-to-postgresql/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/what-changed-in-the-answers-when-a-mysql-app-moved-to-postgresql/</guid>
      <description>&lt;p&gt;This case study uses a demo project in BareSQL’s code repository: a made-up invoicing app that has run on MySQL for years and is moving to PostgreSQL. The project holds the app’s MySQL schema, the same schema rewritten for both engines with some data, and eleven queries as the app ran them on MySQL. The outputs below are what BareSQL printed.&lt;/p&gt;&#xA;&lt;p&gt;The plan sounds simple: rewrite the schema and fix whatever PostgreSQL refuses. The trouble is in the queries PostgreSQL doesn’t refuse. They run and return rows, and some of the rows are different.&lt;/p&gt;</description>
    </item>
    <item>
      <title>What the Bare in BareSQL Stands For</title>
      <link>https://baresql.com/what-the-bare-in-baresql-stands-for/</link>
      <pubDate>Mon, 26 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://baresql.com/what-the-bare-in-baresql-stands-for/</guid>
      <description>&lt;p&gt;BareSQL is one small program with two tools in it, and the name had to fit both. Bare does that by meaning two things at once: access laid bare, and SQL stripped down to the part every engine agrees on.&lt;/p&gt;&#xA;&lt;p&gt;The first sense belongs to BareSQL Audit. To lay something bare is to take the covers off and show what’s underneath. In a PostgreSQL database the covers are the layers between a login and a column: roles inside roles, grants made years ago to a group nobody remembers, views that read tables with their owner’s rights, functions that run as someone else. The audit takes those layers apart and lists, for each login, every column it can read and the route that gets it there.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
