[{"data":1,"prerenderedAt":915},["ShallowReactive",2],{"kc-/knowledge/form-data-architecture":3,"kc-clusters-/knowledge/form-data-architecture":131,"kc-related-/knowledge/form-data-architecture":914},{"id":4,"title":5,"author":6,"body":7,"date":90,"description":91,"draft":92,"extension":93,"faqs":94,"image":104,"isPillar":105,"meta":106,"navigation":105,"path":107,"pillar":108,"pillarName":108,"seo":109,"sources":110,"stem":122,"tags":123,"takeaways":126,"updated":90,"__hash__":130},"knowledge/knowledge/form-data-architecture.md","Form Data Architecture: How to Store Form Responses the Right Way","RoundPushPin Team",{"type":8,"value":9,"toc":82},"minimark",[10,14,19,26,30,36,43,47,50,72,76],[11,12,13],"p",{},"Form data architecture is how a form tool persists, structures, and exposes the answers people submit. It is the most consequential — and most ignored — decision in form building: the storage model you start with determines whether your responses stay a queryable asset or become a pile of exports you fight with later.",[15,16,18],"h2",{"id":17},"what-is-form-data-architecture","What is form data architecture?",[11,20,21,25],{},[22,23,24],"strong",{},"Form data architecture is the underlying model a form platform uses to store responses — flat rows, nested JSON documents, or normalized relational tables — and the interfaces it gives you to read that data back."," Two forms can look identical to a respondent while storing answers in completely different ways underneath, and that difference is what dictates how you query, join, validate, and export later.",[15,27,29],{"id":28},"why-does-the-way-you-store-form-data-matter","Why does the way you store form data matter?",[11,31,32,35],{},[22,33,34],{},"Because the storage model is hard to change after the fact, and it caps what you can do with the data."," A model with no schema accepts anything but lets nothing be trusted, indexed, or joined; a structured model enforces types and relationships so the data is usable the moment it lands. The relational model — first described by E. F. Codd in 1970 — exists precisely to keep data consistent, queryable, and free of duplication, and it still underpins most serious data work today.",[11,37,38],{},[39,40],"img",{"alt":41,"src":42},"Diagram comparing a JSON blob and relational tables for storing the same form response","/images/knowledge/diagrams/relational-vs-json.png",[15,44,46],{"id":45},"flat-files-vs-json-blobs-vs-relational-tables","Flat files vs JSON blobs vs relational tables",[11,48,49],{},"There are three common ways form tools store responses, in increasing order of structure:",[51,52,53,60,66],"ol",{},[54,55,56,59],"li",{},[22,57,58],{},"Flat files / spreadsheets."," One row per submission, one column per question. Simple and instantly readable, but fragile — columns drift as the form changes, types are unenforced, and joining to other data means manual work.",[54,61,62,65],{},[22,63,64],{},"JSON blobs."," The whole response serialized into one document. Flexible and easy to write, but you lose referential integrity, type safety, and efficient querying. PostgreSQL can store JSON, but its own documentation notes that JSON should be used when the structure is genuinely variable — not as a default for data you intend to query relationally.",[54,67,68,71],{},[22,69,70],{},"Relational tables."," Each question becomes a typed column and each response a row, with foreign keys connecting related records. More upfront discipline, but the data is queryable with SQL, joinable to other systems, and protected by constraints from day one.",[15,73,75],{"id":74},"how-roundpushpin-approaches-form-data-architecture","How RoundPushPin approaches form data architecture",[11,77,78,81],{},[22,79,80],{},"RoundPushPin maps every form to a real PostgreSQL schema rather than a blob."," Each question becomes a typed column, each response a row, and form structure is versioned with migrations — so your responses are queryable, joinable, and exportable to CSV or BigQuery without an ETL project. The sections below go deeper on each part of this model.",{"title":83,"searchDepth":84,"depth":84,"links":85},"",2,[86,87,88,89],{"id":17,"depth":84,"text":18},{"id":28,"depth":84,"text":29},{"id":45,"depth":84,"text":46},{"id":74,"depth":84,"text":75},"2026-02-02","Form data architecture is how a form tool persists, structures, and exposes responses. This guide compares flat files, JSON blobs, and relational databases — and why the model you choose decides what you can do with your data later.",false,"md",[95,98,101],{"q":96,"a":97},"What is the best way to store form data?","For data you'll query or join, a relational database is the best fit: each question becomes a typed column and each response a row, so the data stays consistent and queryable. Flat files and JSON blobs are simpler but harder to analyze and validate.",{"q":99,"a":100},"Is JSON or relational storage better for form responses?","Relational storage is better when you need to query, join, or validate responses, because it enforces types and relationships. JSON is better only when the structure is genuinely variable — and most form data is regular, so it benefits from a relational model.",{"q":102,"a":103},"Does RoundPushPin store form responses in a database?","Yes. RoundPushPin maps every form to a PostgreSQL schema, storing each question as a typed column and each response as a row, so the data is queryable from day one.","/images/knowledge/form-data-architecture.png",true,{},"/knowledge/form-data-architecture",null,{"title":5,"description":91},[111,115,119],{"title":112,"url":113,"publisher":114},"A Relational Model of Data for Large Shared Data Banks (E. F. Codd, 1970)","https://dl.acm.org/doi/10.1145/362384.362685","Communications of the ACM",{"title":116,"url":117,"publisher":118},"PostgreSQL Documentation — Data Types","https://www.postgresql.org/docs/current/datatype.html","PostgreSQL Global Development Group",{"title":120,"url":121,"publisher":118},"JSON Types","https://www.postgresql.org/docs/current/datatype-json.html","knowledge/form-data-architecture",[124,125],"data architecture","guide",[127,128,129],"Form data architecture is the model a form tool uses to store responses — flat files, JSON blobs, or relational tables.","The model is hard to change later and caps what you can do: query, join, validate, export.","Relational storage (typed columns, real rows) makes responses a queryable asset rather than a pile of exports.","0WnsYBwYZdtd-2pT6qsc1BvjmvDIuNFWh6r0E2Ntnis",[132,475,590,694,833],{"id":133,"title":134,"author":6,"body":135,"date":439,"description":440,"draft":92,"extension":93,"faqs":441,"image":451,"isPillar":92,"meta":452,"navigation":105,"path":453,"pillar":454,"pillarName":455,"seo":456,"sources":457,"stem":466,"tags":467,"takeaways":470,"updated":90,"__hash__":474},"knowledge/knowledge/why-relational-data.md","Why Your Form Data Deserves a Real Database",{"type":8,"value":136,"toc":433},[137,140,144,148,154,157,273,276,302,306,312,315,371,374,378,381,410,414,426,429],[11,138,139],{},"Most form builders store your responses as JSON documents. It works — until you need to actually do something with that data. Here's why we chose a relational model instead.",[11,141,142],{},[39,143],{"alt":41,"src":42},[15,145,147],{"id":146},"why-are-json-blobs-a-problem-for-form-data","Why are JSON blobs a problem for form data?",[11,149,150,153],{},[22,151,152],{},"JSON blobs are easy to store but hard to use: they offer no referential integrity, no cross-form queries, no type safety, and no efficient indexing."," As soon as you need to analyze, join, or validate responses, the unstructured format works against you.",[11,155,156],{},"When a user submits a form on most platforms, the response gets serialized into something like this:",[158,159,163],"pre",{"className":160,"code":161,"language":162,"meta":83,"style":83},"language-json shiki shiki-themes github-light github-dark","{\n  \"form_id\": \"abc123\",\n  \"responses\": {\n    \"name\": \"Jane Doe\",\n    \"email\": \"jane@example.com\",\n    \"role\": \"Engineer\",\n    \"experience\": \"5-10 years\"\n  },\n  \"submitted_at\": \"2026-01-28T10:00:00Z\"\n}\n","json",[164,165,166,175,191,200,213,226,239,250,256,267],"code",{"__ignoreMap":83},[167,168,171],"span",{"class":169,"line":170},"line",1,[167,172,174],{"class":173},"sVt8B","{\n",[167,176,177,181,184,188],{"class":169,"line":84},[167,178,180],{"class":179},"sj4cs","  \"form_id\"",[167,182,183],{"class":173},": ",[167,185,187],{"class":186},"sZZnC","\"abc123\"",[167,189,190],{"class":173},",\n",[167,192,194,197],{"class":169,"line":193},3,[167,195,196],{"class":179},"  \"responses\"",[167,198,199],{"class":173},": {\n",[167,201,203,206,208,211],{"class":169,"line":202},4,[167,204,205],{"class":179},"    \"name\"",[167,207,183],{"class":173},[167,209,210],{"class":186},"\"Jane Doe\"",[167,212,190],{"class":173},[167,214,216,219,221,224],{"class":169,"line":215},5,[167,217,218],{"class":179},"    \"email\"",[167,220,183],{"class":173},[167,222,223],{"class":186},"\"jane@example.com\"",[167,225,190],{"class":173},[167,227,229,232,234,237],{"class":169,"line":228},6,[167,230,231],{"class":179},"    \"role\"",[167,233,183],{"class":173},[167,235,236],{"class":186},"\"Engineer\"",[167,238,190],{"class":173},[167,240,242,245,247],{"class":169,"line":241},7,[167,243,244],{"class":179},"    \"experience\"",[167,246,183],{"class":173},[167,248,249],{"class":186},"\"5-10 years\"\n",[167,251,253],{"class":169,"line":252},8,[167,254,255],{"class":173},"  },\n",[167,257,259,262,264],{"class":169,"line":258},9,[167,260,261],{"class":179},"  \"submitted_at\"",[167,263,183],{"class":173},[167,265,266],{"class":186},"\"2026-01-28T10:00:00Z\"\n",[167,268,270],{"class":169,"line":269},10,[167,271,272],{"class":173},"}\n",[11,274,275],{},"This is easy to store. It's flexible. But it has serious limitations:",[51,277,278,284,290,296],{},[54,279,280,283],{},[22,281,282],{},"No referential integrity."," Delete a question and orphaned data floats around forever.",[54,285,286,289],{},[22,287,288],{},"No cross-form queries."," Want the average completion time grouped by question type across all forms? You need to deserialize every response.",[54,291,292,295],{},[22,293,294],{},"No type safety."," The \"experience\" field could contain anything — a number, a string, an array.",[54,297,298,301],{},[22,299,300],{},"No indexing."," You can't efficiently query \"all responses where role = Engineer\" without scanning every document.",[15,303,305],{"id":304},"how-does-a-relational-model-store-form-data","How does a relational model store form data?",[11,307,308,311],{},[22,309,310],{},"In a relational model, every form maps to a structured PostgreSQL schema: each question becomes a typed column and each response becomes a row."," Foreign keys keep everything connected, so nothing gets orphaned and every field has a known type.",[11,313,314],{},"In RoundPushPin, the same data looks like this:",[158,316,320],{"className":317,"code":318,"language":319,"meta":83,"style":83},"language-sql shiki shiki-themes github-light github-dark","-- Forms table\nSELECT * FROM forms WHERE id = 'abc123';\n\n-- Blocks (questions) with enforced types\nSELECT * FROM blocks WHERE form_id = 'abc123' ORDER BY position;\n\n-- Responses with proper columns\nSELECT name, email, role, experience\nFROM responses_abc123\nWHERE submitted_at > '2026-01-01';\n","sql",[164,321,322,327,332,337,342,347,351,356,361,366],{"__ignoreMap":83},[167,323,324],{"class":169,"line":170},[167,325,326],{},"-- Forms table\n",[167,328,329],{"class":169,"line":84},[167,330,331],{},"SELECT * FROM forms WHERE id = 'abc123';\n",[167,333,334],{"class":169,"line":193},[167,335,336],{"emptyLinePlaceholder":105},"\n",[167,338,339],{"class":169,"line":202},[167,340,341],{},"-- Blocks (questions) with enforced types\n",[167,343,344],{"class":169,"line":215},[167,345,346],{},"SELECT * FROM blocks WHERE form_id = 'abc123' ORDER BY position;\n",[167,348,349],{"class":169,"line":228},[167,350,336],{"emptyLinePlaceholder":105},[167,352,353],{"class":169,"line":241},[167,354,355],{},"-- Responses with proper columns\n",[167,357,358],{"class":169,"line":252},[167,359,360],{},"SELECT name, email, role, experience\n",[167,362,363],{"class":169,"line":258},[167,364,365],{},"FROM responses_abc123\n",[167,367,368],{"class":169,"line":269},[167,369,370],{},"WHERE submitted_at > '2026-01-01';\n",[11,372,373],{},"Each form gets a structured schema. Each question maps to a typed column. Foreign keys ensure nothing gets orphaned.",[15,375,377],{"id":376},"what-does-relational-form-storage-enable","What does relational form storage enable?",[11,379,380],{},"With relational storage, you can:",[382,383,384,392,398,404],"ul",{},[54,385,386,183,389],{},[22,387,388],{},"Run analytics directly",[164,390,391],{},"SELECT role, COUNT(*) FROM responses GROUP BY role",[54,393,394,397],{},[22,395,396],{},"Join with other data",": Connect form responses to your user table, CRM, or any other system",[54,399,400,403],{},[22,401,402],{},"Enforce constraints",": Required fields, unique values, valid email formats — at the database level",[54,405,406,409],{},[22,407,408],{},"Version your schema",": Track changes to form structure over time with migrations",[15,411,413],{"id":412},"whats-the-trade-off-of-a-relational-schema","What's the trade-off of a relational schema?",[11,415,416,419,420,425],{},[22,417,418],{},"Relational schemas are less flexible than JSON."," Adding a question means adding a column. But we think this trade-off is worth it. The discipline of a structured schema pays dividends in data quality, queryability, and long-term maintainability — a principle at the heart of ",[421,422,424],"a",{"href":423},"/knowledge/building-better-forms","our product vision",".",[11,427,428],{},"Your form data isn't throwaway. It deserves a real database.",[430,431,432],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":83,"searchDepth":84,"depth":84,"links":434},[435,436,437,438],{"id":146,"depth":84,"text":147},{"id":304,"depth":84,"text":305},{"id":376,"depth":84,"text":377},{"id":412,"depth":84,"text":413},"2026-01-28","JSON blobs vs relational tables: why we chose PostgreSQL as the backbone of RoundPushPin and what it means for your data.",[442,445,448],{"q":443,"a":444},"Why use a relational database for form responses instead of JSON?","Relational tables enforce types and relationships and let you query and join responses with SQL, while JSON blobs store everything in one document you must deserialize to analyze. For data you'll actually use, relational wins on integrity and queryability.",{"q":446,"a":447},"Can you store form data in PostgreSQL?","Yes. PostgreSQL suits form data well: each question becomes a typed column, constraints enforce required and format rules, and you query responses with standard SQL. RoundPushPin maps every form to a PostgreSQL schema automatically.",{"q":449,"a":450},"What's wrong with storing form responses as JSON blobs?","JSON blobs lack referential integrity, type safety, and efficient indexing, so you can't easily validate, join, or query responses without deserializing every document. They're flexible but become a liability once you need to analyze the data.","/images/knowledge/why-relational-data.png",{},"/knowledge/why-relational-data","form-data-architecture","Form data architecture",{"title":134,"description":440},[458,459,462],{"title":120,"url":121,"publisher":118},{"title":460,"url":461,"publisher":118},"Constraints","https://www.postgresql.org/docs/current/ddl-constraints.html",{"title":463,"url":464,"publisher":465},"Drizzle ORM — Overview","https://orm.drizzle.team/docs/overview","Drizzle","knowledge/why-relational-data",[468,469],"technical","architecture",[471,472,473],"JSON blobs are easy to write but offer no type safety, no joins, and no referential integrity.","A relational model maps each question to a typed column and each response to a row you can query with SQL.","RoundPushPin stores responses in PostgreSQL automatically, so your data is queryable and joinable from day one.","lOhUA5IBPXmTygdlUyB9c6jupBhbfDak7GF2SOtSymk",{"id":476,"title":477,"author":6,"body":478,"date":556,"description":557,"draft":92,"extension":93,"faqs":558,"image":568,"isPillar":92,"meta":569,"navigation":105,"path":570,"pillar":454,"pillarName":455,"seo":571,"sources":572,"stem":583,"tags":584,"takeaways":588,"updated":556,"__hash__":589},"knowledge/knowledge/export-form-responses-to-bigquery.md","How to Export Form Responses to BigQuery",{"type":8,"value":479,"toc":549},[480,483,487,498,502,508,512,515,526,529,533,539,543],[11,481,482],{},"Getting form responses into Google BigQuery turns a stream of submissions into a queryable analytics table you can join with the rest of your data. This guide covers why teams do it, the manual route most form tools force on you, and how a relational form builder makes the export direct.",[15,484,486],{"id":485},"can-you-export-form-responses-to-bigquery","Can you export form responses to BigQuery?",[11,488,489,492,493,497],{},[22,490,491],{},"Yes — any form tool that lets you export CSV can get data into BigQuery, because BigQuery natively loads CSV (and JSON, Avro, and Parquet) files."," Google's documentation describes BigQuery as a fully managed, serverless data warehouse, and loading data into it is a first-class operation. The real question is not ",[494,495,496],"em",{},"whether"," you can, but how much manual cleanup stands between a submission and a clean row in your warehouse.",[15,499,501],{"id":500},"why-send-form-data-to-bigquery","Why send form data to BigQuery?",[11,503,504,507],{},[22,505,506],{},"Because BigQuery is built to analyze large datasets with SQL and to join them with your other business data."," Once responses live in BigQuery you can answer questions a form dashboard never could — completion trends over time, segmentation by any field, and joins against your CRM, product, or billing tables — using the same SQL your data team already writes.",[15,509,511],{"id":510},"how-do-most-form-tools-get-data-into-bigquery","How do most form tools get data into BigQuery?",[11,513,514],{},"Most do it indirectly, through a chain of glue:",[51,516,517,520,523],{},[54,518,519],{},"Export the form's responses as a CSV (or connect a no-code automation tool).",[54,521,522],{},"Reshape the columns to match a BigQuery schema.",[54,524,525],{},"Load the file with a batch job, or pipe it through a third-party connector on a schedule.",[11,527,528],{},"BigQuery's batch-loading documentation makes each step routine, but the work compounds: every form change can break the column mapping, and nested or repeated answers rarely survive a flat CSV cleanly.",[15,530,532],{"id":531},"how-roundpushpin-exports-to-bigquery","How RoundPushPin exports to BigQuery",[11,534,535,538],{},[22,536,537],{},"Because RoundPushPin already stores responses relationally — each question a typed column, each response a row — the data is export-shaped from the start."," That removes the reshape-and-clean step: you get one-click CSV when you want a file, and a direct BigQuery export when you want the responses to land in your warehouse as a structured table, ready to query and join.",[15,540,542],{"id":541},"bigquery-vs-csv-export-which-should-you-use","BigQuery vs CSV export — which should you use?",[11,544,545,548],{},[22,546,547],{},"Use CSV for a quick one-off pull you'll open in a spreadsheet; use a direct BigQuery export when form data needs to live alongside your other analytics data for ongoing querying."," The advantage of starting from a relational store is that both paths come from the same clean, typed source — so you are never reconciling a spreadsheet against your warehouse.",{"title":83,"searchDepth":84,"depth":84,"links":550},[551,552,553,554,555],{"id":485,"depth":84,"text":486},{"id":500,"depth":84,"text":501},{"id":510,"depth":84,"text":511},{"id":531,"depth":84,"text":532},{"id":541,"depth":84,"text":542},"2026-02-04","A practical guide to getting form responses into Google BigQuery for analysis — why teams do it, the usual CSV-and-glue approach, and how RoundPushPin exports structured responses directly.",[559,562,565],{"q":560,"a":561},"How do I get form responses into BigQuery?","BigQuery natively loads CSV, JSON, Avro, and Parquet, so any tool that exports those can feed it. The friction is reshaping messy exports; starting from relational storage makes the data export-shaped, and RoundPushPin offers a direct BigQuery export.",{"q":563,"a":564},"Why analyze form data in BigQuery?","BigQuery analyzes large datasets with SQL and joins them with your other business data, so you can answer questions a form dashboard can't — trends over time, segmentation, and joins against CRM or product tables.",{"q":566,"a":567},"Is CSV or BigQuery better for form data?","Use CSV for a quick one-off pull; use a direct BigQuery export when form data needs to live alongside your other analytics for ongoing querying. Starting from a relational store keeps both clean.","/images/knowledge/export-form-responses-to-bigquery.png",{},"/knowledge/export-form-responses-to-bigquery",{"title":477,"description":557},[573,577,580],{"title":574,"url":575,"publisher":576},"BigQuery — Introduction","https://cloud.google.com/bigquery/docs/introduction","Google Cloud",{"title":578,"url":579,"publisher":576},"Loading data into BigQuery","https://cloud.google.com/bigquery/docs/loading-data",{"title":581,"url":582,"publisher":576},"Batch loading data","https://cloud.google.com/bigquery/docs/batch-loading-data","knowledge/export-form-responses-to-bigquery",[585,586,587],"bigquery","export","analytics",[],"WndWLTxIIJvRRC24e2sk4yWsJRUSlNfS0lGIe4HAqGE",{"id":591,"title":592,"author":6,"body":593,"date":665,"description":666,"draft":92,"extension":93,"faqs":667,"image":677,"isPillar":92,"meta":678,"navigation":105,"path":679,"pillar":454,"pillarName":455,"seo":680,"sources":681,"stem":688,"tags":689,"takeaways":692,"updated":665,"__hash__":693},"knowledge/knowledge/store-form-responses-in-a-database.md","How to Store Form Responses in a Database",{"type":8,"value":594,"toc":659},[595,598,602,608,612,630,634,640,644],[11,596,597],{},"Storing form responses in a database means persisting each submission as structured, typed rows in tables — not as a spreadsheet or a serialized document. It is what turns raw submissions into data you can trust, query, and connect to the rest of your stack.",[15,599,601],{"id":600},"when-should-you-store-form-responses-in-a-database","When should you store form responses in a database?",[11,603,604,607],{},[22,605,606],{},"As soon as the responses have to be queried, joined, validated, or kept consistent over time."," A spreadsheet is fine for a one-off RSVP, but the moment you need to filter thousands of responses, enforce that an email is really an email, or connect answers to your users table, a relational database is the right home.",[15,609,611],{"id":610},"how-do-you-model-form-responses-as-tables","How do you model form responses as tables?",[11,613,614,617,618,621,622,625,626,629],{},[22,615,616],{},"You map the form's structure onto a schema: a table for forms, a table for questions, and typed columns for answers."," Each question becomes a column with a real type (text, integer, boolean, timestamp), and constraints enforce the rules — ",[164,619,620],{},"NOT NULL"," for required questions, ",[164,623,624],{},"UNIQUE"," where answers must not repeat, and foreign keys linking responses back to their form. PostgreSQL's ",[164,627,628],{},"CREATE TABLE"," and constraint system exist precisely to encode these rules at the data layer, where they can't be bypassed.",[15,631,633],{"id":632},"what-about-questions-that-change-over-time","What about questions that change over time?",[11,635,636,639],{},[22,637,638],{},"Use migrations."," Forms evolve — you add a question, rename another, change a type — and a migration records each change as a versioned, repeatable step. Tools like Drizzle generate migrations from your schema definition, so the database structure stays in lock-step with the form and you never lose the history of how it changed.",[15,641,643],{"id":642},"how-roundpushpin-stores-responses-for-you","How RoundPushPin stores responses for you",[11,645,646,649,650,654,655,425],{},[22,647,648],{},"RoundPushPin does all of this automatically."," When you build a form, it maps each question to a typed PostgreSQL column, enforces required and format rules as constraints, and versions structural changes with migrations — so you get a clean relational store without writing a line of SQL or designing a schema by hand. From there, responses are ready to ",[421,651,653],{"href":652},"/knowledge/query-form-data-with-sql","query with SQL"," or ",[421,656,658],{"href":657},"/knowledge/export-form-responses-to-csv","export to CSV",{"title":83,"searchDepth":84,"depth":84,"links":660},[661,662,663,664],{"id":600,"depth":84,"text":601},{"id":610,"depth":84,"text":611},{"id":632,"depth":84,"text":633},{"id":642,"depth":84,"text":643},"2026-02-06","A practical look at storing form responses in a relational database: when a spreadsheet stops being enough, how to model questions as typed columns, and how RoundPushPin does it automatically.",[668,671,674],{"q":669,"a":670},"How do you save form submissions to a database?","Map the form to a schema: a table per form, typed columns per question, and rows per response, with constraints for required and format rules. RoundPushPin generates the PostgreSQL schema for you automatically.",{"q":672,"a":673},"Should form data go in a database or a spreadsheet?","Use a database once you need to query, join, validate, or keep responses consistent over time. A spreadsheet is fine for a quick one-off, but it drifts and enforces no types as the form changes.",{"q":675,"a":676},"How do you handle form fields that change over time?","Use migrations — versioned, repeatable steps that update the database structure as the form evolves. That keeps the schema in sync with the form and preserves a history of changes.","/images/knowledge/store-form-responses-in-a-database.png",{},"/knowledge/store-form-responses-in-a-database",{"title":592,"description":666},[682,684,685],{"title":628,"url":683,"publisher":118},"https://www.postgresql.org/docs/current/sql-createtable.html",{"title":460,"url":461,"publisher":118},{"title":686,"url":687,"publisher":465},"Drizzle ORM — Migrations","https://orm.drizzle.team/docs/migrations","knowledge/store-form-responses-in-a-database",[690,691,125],"database","postgresql",[],"T9BsnyPelUYLC2_-LOVcmy8gU4xiki1YmYNpwgbC1_g",{"id":695,"title":696,"author":6,"body":697,"date":804,"description":805,"draft":92,"extension":93,"faqs":806,"image":816,"isPillar":92,"meta":817,"navigation":105,"path":652,"pillar":454,"pillarName":455,"seo":818,"sources":819,"stem":829,"tags":830,"takeaways":831,"updated":804,"__hash__":832},"knowledge/knowledge/query-form-data-with-sql.md","How to Query Form Data With SQL",{"type":8,"value":698,"toc":798},[699,702,706,716,720,723,770,774,784,788],[11,700,701],{},"Querying form data with SQL means asking questions of your responses directly in the database — filtering, grouping, and joining them — instead of exporting a file and pivoting in a spreadsheet. It only works when responses are stored relationally, with each question as a typed column.",[15,703,705],{"id":704},"can-you-query-form-responses-with-sql","Can you query form responses with SQL?",[11,707,708,711,712,715],{},[22,709,710],{},"You can when responses live in a relational database."," If a form tool stores answers as JSON blobs you have to deserialize every document first; if each question is a typed column, you query it like any other table. The difference is the storage model — see ",[421,713,714],{"href":107},"form data architecture"," for why it matters.",[15,717,719],{"id":718},"what-can-you-actually-ask","What can you actually ask?",[11,721,722],{},"The three workhorses are filtering, aggregating, and joining:",[382,724,725,734,757],{},[54,726,727,730,731],{},[22,728,729],{},"Filter"," to a segment: ",[164,732,733],{},"SELECT * FROM responses WHERE role = 'Engineer' AND submitted_at > '2026-01-01'",[54,735,736,739,740,742,743,746,747,746,750,746,753,756],{},[22,737,738],{},"Aggregate"," to a metric: ",[164,741,391],{}," — PostgreSQL's aggregate functions (",[164,744,745],{},"COUNT",", ",[164,748,749],{},"AVG",[164,751,752],{},"MIN",[164,754,755],{},"MAX",") turn raw rows into answers.",[54,758,759,762,763,654,766,769],{},[22,760,761],{},"Join"," to context: connect responses to your ",[164,764,765],{},"users",[164,767,768],{},"orders"," table on a shared key to analyze answers alongside the rest of your data.",[15,771,773],{"id":772},"how-do-you-analyze-completion-or-drop-off-with-sql","How do you analyze completion or drop-off with SQL?",[11,775,776,779,780,783],{},[22,777,778],{},"Group by question and count non-null answers."," Because each question is its own column, a query like ",[164,781,782],{},"SELECT COUNT(question_3) / COUNT(*)::float FROM responses"," gives the completion ratio for that question — the kind of drop-off analysis that is painful when the whole response is one opaque blob.",[15,785,787],{"id":786},"how-roundpushpin-makes-responses-queryable","How RoundPushPin makes responses queryable",[11,789,790,793,794,797],{},[22,791,792],{},"RoundPushPin stores every response relationally, so your data is queryable from day one — no export step, no reshaping."," Connect your own SQL client or ",[421,795,796],{"href":570},"export to BigQuery"," when you want to analyze form data next to everything else.",{"title":83,"searchDepth":84,"depth":84,"links":799},[800,801,802,803],{"id":704,"depth":84,"text":705},{"id":718,"depth":84,"text":719},{"id":772,"depth":84,"text":773},{"id":786,"depth":84,"text":787},"2026-02-08","When form responses live in a relational database, you can answer questions with SQL instead of exporting spreadsheets. This guide shows the queries that matter — filtering, aggregating, and joining responses.",[807,810,813],{"q":808,"a":809},"Can you run SQL on form responses?","Yes, when responses are stored relationally — each question as a typed column. You can filter, aggregate, and join them like any table. If a tool stores answers as JSON blobs, you must deserialize them first.",{"q":811,"a":812},"How do you analyze form drop-off?","Group by question and count non-null answers: comparing answered versus total per question reveals where people stop. This is simple when each question is its own column and painful when the response is one opaque blob.",{"q":814,"a":815},"How do you join form data with other tables?","Connect responses to another table on a shared key, such as a user ID, with a SQL JOIN. Relational storage makes this direct; RoundPushPin stores responses this way so they sit alongside your other data.","/images/knowledge/query-form-data-with-sql.png",{},{"title":696,"description":805},[820,823,826],{"title":821,"url":822,"publisher":118},"SELECT","https://www.postgresql.org/docs/current/sql-select.html",{"title":824,"url":825,"publisher":118},"Aggregate Functions","https://www.postgresql.org/docs/current/functions-aggregate.html",{"title":827,"url":828,"publisher":118},"Joins Between Tables","https://www.postgresql.org/docs/current/tutorial-join.html","knowledge/query-form-data-with-sql",[319,587,125],[],"8-YH_T1SbhmtDvDbowCzrSccBzS9qyn4zgXNLGRfKJk",{"id":834,"title":835,"author":6,"body":836,"date":887,"description":888,"draft":92,"extension":93,"faqs":889,"image":899,"isPillar":92,"meta":900,"navigation":105,"path":657,"pillar":454,"pillarName":455,"seo":901,"sources":902,"stem":909,"tags":910,"takeaways":912,"updated":887,"__hash__":913},"knowledge/knowledge/export-form-responses-to-csv.md","How to Export Form Responses to CSV",{"type":8,"value":837,"toc":882},[838,841,845,851,855,865,869],[11,839,840],{},"Exporting form responses to CSV produces a comma-separated file with one row per submission and one column per question — the most portable way to move responses into a spreadsheet, BI tool, or another system. A clean export depends entirely on how the responses were stored.",[15,842,844],{"id":843},"what-makes-a-clean-csv-export","What makes a \"clean\" CSV export?",[11,846,847,850],{},[22,848,849],{},"A clean CSV has stable headers, one column per question, consistent types down each column, and proper escaping of commas, quotes, and line breaks."," The CSV format is specified in RFC 4180, and the details — quoting fields that contain delimiters, consistent line endings — are exactly what trips up hand-rolled exports. When data already lives in typed columns, the export is mechanical; when it lives in JSON blobs, every export risks shifting or missing columns.",[15,852,854],{"id":853},"why-are-spreadsheet-based-form-tools-messy-to-export","Why are spreadsheet-based form tools messy to export?",[11,856,857,860,861,864],{},[22,858,859],{},"Because the column layout drifts."," If responses are stored as documents or in an ever-widening sheet, adding or removing a question changes the shape of every future export, and types are unenforced — a \"number\" column may contain text. Relational storage avoids this: the schema fixes the columns, so the CSV is the same shape every time. Databases like PostgreSQL even expose a dedicated ",[164,862,863],{},"COPY"," command to stream a table straight to CSV.",[15,866,868],{"id":867},"how-roundpushpin-exports-to-csv","How RoundPushPin exports to CSV",[11,870,871,874,875,878,879,425],{},[22,872,873],{},"Because RoundPushPin stores each response as typed relational rows, a CSV export is one click and always well-formed: stable headers, consistent types, proper escaping."," When you outgrow files, the same clean source ",[421,876,877],{"href":570},"exports directly to BigQuery"," or is ",[421,880,881],{"href":652},"queryable in place with SQL",{"title":83,"searchDepth":84,"depth":84,"links":883},[884,885,886],{"id":843,"depth":84,"text":844},{"id":853,"depth":84,"text":854},{"id":867,"depth":84,"text":868},"2026-02-10","CSV is the universal format for moving form responses into spreadsheets and other tools. This guide covers what a clean CSV export looks like, the pitfalls of messy exports, and how RoundPushPin exports in one click.",[890,893,896],{"q":891,"a":892},"How do I export form responses to CSV?","Most form tools offer a CSV export; the quality depends on how the data is stored. Relational storage yields stable headers, one column per question, consistent types, and proper escaping — RoundPushPin exports a clean CSV in one click.",{"q":894,"a":895},"Why is my form CSV export messy?","Usually because the columns drift: if responses are stored as documents or an ever-widening sheet, adding or removing a question changes every export and types aren't enforced. Relational storage fixes the column layout.",{"q":897,"a":898},"What is the standard CSV format?","RFC 4180 defines CSV: one record per line, fields separated by commas, and fields containing commas, quotes, or line breaks wrapped in double quotes. Hand-rolled exports often get the escaping wrong.","/images/knowledge/export-form-responses-to-csv.png",{},{"title":835,"description":888},[903,907],{"title":904,"url":905,"publisher":906},"Common Format and MIME Type for CSV Files (RFC 4180)","https://www.rfc-editor.org/rfc/rfc4180","IETF",{"title":863,"url":908,"publisher":118},"https://www.postgresql.org/docs/current/sql-copy.html","knowledge/export-form-responses-to-csv",[911,586,125],"csv",[],"Yr1qEbZAaKKsvAjU32SFRvpGh9Mjbh9-9tnKT98Budw",[],1780692424609]