{"id":45933,"date":"2026-11-12T13:38:27","date_gmt":"2026-11-12T13:38:27","guid":{"rendered":"https:\/\/www.withdata.com\/blog\/?p=45933"},"modified":"2026-11-12T13:44:09","modified_gmt":"2026-11-12T13:44:09","slug":"get-a-file-out-of-a-blob-column-without-a-script","status":"publish","type":"post","link":"https:\/\/www.withdata.com\/blog\/dbblobeditor\/get-a-file-out-of-a-blob-column-without-a-script.html","title":{"rendered":"Getting a File Out of a BLOB Column Without Writing a Script"},"content":{"rendered":"<p>The two Stack Overflow questions are years old and very busy: <em>&#8220;Script to save varbinary data to disk&#8221;<\/em> (<strong>121,094 views<\/strong>, 55 votes) and <em>&#8220;How to dump a file stored in a sqlite database as a blob&#8221;<\/em> (<strong>37,133 views<\/strong>, 53 votes). The answers are the same shape in both: write a loop, read the column row by row, write each value to a file, hope the extension is right. It works \u2014 once \u2014 and then it is a script nobody in the team wants to own.<\/p>\n<p>There is a smaller category next door with even more traffic: <em>&#8220;How do I get textual contents from BLOB in Oracle SQL&#8221;<\/em> (<strong>695,117 views<\/strong>) and <em>&#8220;How to query a CLOB column&#8221;<\/em> (628,245). Those are about <em>reading<\/em> a value in SQL, and SQL is genuinely the right answer there. The problem starts when the value is a <strong>file<\/strong> \u2014 a PDF, a scanned page, a photo \u2014 and what you need is the file back on disk, or a look at it before you decide it is the one.<\/p>\n<h2>1. What &#8220;get it out&#8221; actually involves<\/h2>\n<table>\n<thead>\n<tr>\n<th>Step<\/th>\n<th>What people do today<\/th>\n<th>What goes wrong<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Find the row<\/td>\n<td><code>SELECT ... WHERE name = '...'<\/code><\/td>\n<td>you need to know which table\/column holds the LOB<\/td>\n<\/tr>\n<tr>\n<td>Read the value<\/td>\n<td><code>dbms_lob.substr<\/code>, <code>substring<\/code>, <code>hex()<\/code>, a client that renders <code>(BLOB)<\/code><\/td>\n<td>the client shows <code>(BLOB)<\/code> or a wall of hex<\/td>\n<\/tr>\n<tr>\n<td>Get the bytes out<\/td>\n<td>a script (<code>utl_file<\/code>, <code>bcp<\/code>, <code>psycopg2<\/code>, <code>sqlite3<\/code>)<\/td>\n<td>encoding, row size, and the extension you have to guess<\/td>\n<\/tr>\n<tr>\n<td>Check the result<\/td>\n<td>open the file<\/td>\n<td>half the time the extension is wrong, or the file is truncated<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A viewer removes three of those four steps: it lists the table, shows the value in a preview, and writes the file with a name and extension you choose.<\/p>\n<h2>2. The GUI route<\/h2>\n<p><img decoding=\"async\" src=\"\/images\/dbblobeditor\/login_sql_server.png\" alt=\"DBBlobEditor Logon: Database Type, recent databases, server\/port, authentication, user, database, and the Connect button\" style=\"max-width:100%;height:auto;display:block;\" width=\"430\" height=\"473\" style=\"max-width:100%;height:auto;display:block;\" \/><\/p>\n<ol>\n<li><strong>Log On<\/strong> (toolbar, or <em>File \u2192 Log On<\/em>). Pick the database type \u2014 SQLite, Oracle, SQL Server,<\/li>\n<\/ol>\n<p>PostgreSQL, MySQL, DB2 \u2014 and point it at the database (for SQLite, the <code>.db<\/code> file), then <strong>Connect<\/strong>.<\/p>\n<ol>\n<li><strong>View\/Edit Data<\/strong> opens the table grid: choose the table that holds the LOB column. Rows come up<\/li>\n<\/ol>\n<p>with the LOB column marked as a LOB, not as text.<\/p>\n<ol>\n<li><strong>Click the LOB cell<\/strong> and the preview opens: images render, PDF pages render, text and XML\/JSON show<\/li>\n<\/ol>\n<p>as text, audio and video get a player. This is the step that answers &#8220;is this the file I think it is?&#8221; without extracting anything.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/dbblobeditor\/dbblobeditor.png\" alt=\"DBBlobEditor main window: schema tree with the tables on the left, the row grid, and the LOB preview for a \"Column: image\" value \u2014 tabs Text, Rich Text, XML, Html, Image, PDF, Hex, Open with; status bar reads 10 records, 0.079 seconds\" style=\"max-width:100%;height:auto;display:block;\" width=\"878\" height=\"684\" style=\"max-width:100%;height:auto;display:block;\" \/><\/p>\n<ol>\n<li><strong>Export LOB<\/strong> (or <em>Tools \u2192 Export LOB<\/em>) writes the selected value to disk. <strong>Export Lob Task<\/strong> does<\/li>\n<\/ol>\n<p>the same for a whole table or a query result \u2014 that is the &#8220;121k views&#8221; script, without the script.<\/p>\n<p>A PDF value looks like this in the same preview \u2014 the page itself, not a hex dump:<\/p>\n<p><img decoding=\"async\" src=\"\/images\/dbblobeditor\/blob-pdf.png\" alt=\"DBBlobEditor preview of a PDF stored in a BLOB column: the rendered page with a bar chart\" style=\"max-width:100%;height:auto;display:block;\" \/><\/p>\n<ol>\n<li><strong>Import LOB<\/strong> goes the other way: one file, or a folder into many rows, with the file name and<\/li>\n<\/ol>\n<p>extension kept in the columns you choose.<\/p>\n<p>Measured on a SQLite database with four rows (a PDF, a PNG, a text file, and a NULL):<\/p>\n<table>\n<thead>\n<tr>\n<th>Step<\/th>\n<th>Result<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Log On + pick table<\/td>\n<td>seconds; no client libraries to install<\/td>\n<\/tr>\n<tr>\n<td>Preview a PDF row<\/td>\n<td>renders in the preview pane (pdfium)<\/td>\n<\/tr>\n<tr>\n<td>Export one row<\/td>\n<td>one dialog: target folder + file name; the extension follows the detected type<\/td>\n<\/tr>\n<tr>\n<td>Export a table<\/td>\n<td><em>Export Lob Task<\/em>: folder + naming pattern, one row per file, log line per row<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>3. Where the SQL answers still win<\/h2>\n<ul>\n<li><strong>Text inside a CLOB<\/strong> \u2014 <code>dbms_lob.substr<\/code>, <code>cast(... as varchar)<\/code>, <code>convert(...)<\/code>: if you need the<\/li>\n<\/ul>\n<p>characters, not the file, SQL is faster than any GUI.<\/p>\n<ul>\n<li><strong>One-off checks in a pipeline<\/strong> \u2014 a nightly job that extracts 200 files is a script, and should stay one.<\/li>\n<li><strong>No desktop<\/strong> \u2014 on a locked-down server, a CLI is the only option.<\/li>\n<\/ul>\n<p>This tool is for the opposite case: you have a database on your machine (or reachable from it), you need to <em>look<\/em> at what is inside a LOB column, and then get one file \u2014 or one table&#8217;s worth of files \u2014 out.<\/p>\n<h2>4. The three traps<\/h2>\n<ol>\n<li><strong><code>(BLOB)<\/code> in the grid is not an error.<\/strong> Most SQL clients show that for a binary column; it means the<\/li>\n<\/ol>\n<p>client is not rendering it. A LOB-aware client shows the type and a preview.<\/p>\n<ol>\n<li><strong>The extension is not stored in the database.<\/strong> A BLOB is bytes; the file name and type live in other<\/li>\n<\/ol>\n<p>columns (or nowhere). On export you choose the name; the preview tells you what it is.<\/p>\n<ol>\n<li><strong>NULL and empty are different.<\/strong> A NULL LOB exports nothing; a zero-length LOB exports an empty file.<\/li>\n<\/ol>\n<p>The row count is the same, so check both when a batch looks short.<\/p>\n<h2>5. From the command line<\/h2>\n<p>For the scheduled case, the same program exports and imports without the window:<\/p>\n<p>&#8220;<code> DBBlobEditorCmd export --db=sqlite:documents.db --table=documents --column=data --folder=out DBBlobEditorCmd import --db=sqlite:documents.db --table=documents --folder=in <\/code>&#8220;<\/p>\n<p>Both write a log line per row with the file name and byte count \u2014 which is the thing the hand-written scripts usually forget.<\/p>\n<h2>FAQ<\/h2>\n<h3>Why does my client show <code>(BLOB)<\/code> instead of the data?<\/h3>\n<p>Because the column is binary and the client is not decoding it. The bytes are there; what is missing is a viewer that knows it is a PDF rather than a string.<\/p>\n<h3>Can I export a whole table of BLOBs at once?<\/h3>\n<p>Yes \u2014 <em>Export Lob Task<\/em> walks the table (or a query) and writes one file per row, using a naming pattern so the file names match the rows.<\/p>\n<h3>How do I know the file type if the database does not store it?<\/h3>\n<p>The preview: PDF renders as a page, an image renders as an image, text renders as text. If nothing renders, the value is either not a known format or it is truncated \u2014 which is itself useful information.<\/p>\n<h3>Does it work with Oracle and SQL Server as well?<\/h3>\n<p>Yes: Oracle, SQL Server, PostgreSQL, MySQL, MariaDB, SQLite, DB2 and Access, with the same Log On \u2192 View\/Edit Data \u2192 Export LOB flow.<\/p>\n<h3>Can it write back into the database?<\/h3>\n<p><em>Import LOB<\/em> does: one file into one row, or a folder into many rows. It does not change the table structure.<\/p>\n<h2>Getting the file out<\/h2>\n<p><a href=\"https:\/\/www.withdata.com\/dbblobeditor\/\" target=\"_blank\" rel=\"noopener noreferrer\">DBBlobEditor<\/a> opens a database, shows the LOB column with a preview (image, PDF, text, audio, video, hex) and exports what you select \u2014 one row or a whole table \u2014 on Windows, Linux and macOS. There is a 30-day trial, and the <a href=\"https:\/\/www.withdata.com\/dbblobeditor\/download.html\" target=\"_blank\" rel=\"noopener noreferrer\">download page<\/a> has the installers.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The two Stack Overflow questions are years old and very busy: &#8220;Script to save varbinary data to disk&#8221; (121,094 views, 55 votes) and &#8220;How to dump a file stored in a sqlite database as a blob&#8221; (37,133 views, 53 votes). The answers are the same shape in both: write a loop, read the column row [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[177],"tags":[97,100,641,1354,16],"class_list":["post-45933","post","type-post","status-publish","format-standard","hentry","category-dbblobeditor","tag-blob","tag-clob","tag-database","tag-dbblobeditor","tag-export"],"_links":{"self":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45933","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/comments?post=45933"}],"version-history":[{"count":2,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45933\/revisions"}],"predecessor-version":[{"id":45935,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45933\/revisions\/45935"}],"wp:attachment":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/media?parent=45933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/categories?post=45933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/tags?post=45933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}