[{"data":1,"prerenderedAt":357},["ShallowReactive",2],{"kc-/knowledge/query-form-data-with-sql":3,"kc-clusters-/knowledge/query-form-data-with-sql":167,"kc-related-/knowledge/query-form-data-with-sql":168},{"id":4,"title":5,"author":6,"body":7,"date":128,"description":129,"draft":130,"extension":131,"faqs":132,"image":142,"isPillar":130,"meta":143,"navigation":144,"path":145,"pillar":146,"pillarName":147,"seo":148,"sources":149,"stem":160,"tags":161,"takeaways":165,"updated":128,"__hash__":166},"knowledge/knowledge/query-form-data-with-sql.md","How to Query Form Data With SQL","RoundPushPin Team",{"type":8,"value":9,"toc":120},"minimark",[10,14,19,32,36,39,91,95,105,109],[11,12,13],"p",{},"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,16,18],"h2",{"id":17},"can-you-query-form-responses-with-sql","Can you query form responses with SQL?",[11,20,21,25,26,31],{},[22,23,24],"strong",{},"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 ",[27,28,30],"a",{"href":29},"/knowledge/form-data-architecture","form data architecture"," for why it matters.",[15,33,35],{"id":34},"what-can-you-actually-ask","What can you actually ask?",[11,37,38],{},"The three workhorses are filtering, aggregating, and joining:",[40,41,42,53,77],"ul",{},[43,44,45,48,49],"li",{},[22,46,47],{},"Filter"," to a segment: ",[50,51,52],"code",{},"SELECT * FROM responses WHERE role = 'Engineer' AND submitted_at > '2026-01-01'",[43,54,55,58,59,62,63,66,67,66,70,66,73,76],{},[22,56,57],{},"Aggregate"," to a metric: ",[50,60,61],{},"SELECT role, COUNT(*) FROM responses GROUP BY role"," — PostgreSQL's aggregate functions (",[50,64,65],{},"COUNT",", ",[50,68,69],{},"AVG",[50,71,72],{},"MIN",[50,74,75],{},"MAX",") turn raw rows into answers.",[43,78,79,82,83,86,87,90],{},[22,80,81],{},"Join"," to context: connect responses to your ",[50,84,85],{},"users"," or ",[50,88,89],{},"orders"," table on a shared key to analyze answers alongside the rest of your data.",[15,92,94],{"id":93},"how-do-you-analyze-completion-or-drop-off-with-sql","How do you analyze completion or drop-off with SQL?",[11,96,97,100,101,104],{},[22,98,99],{},"Group by question and count non-null answers."," Because each question is its own column, a query like ",[50,102,103],{},"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,106,108],{"id":107},"how-roundpushpin-makes-responses-queryable","How RoundPushPin makes responses queryable",[11,110,111,114,115,119],{},[22,112,113],{},"RoundPushPin stores every response relationally, so your data is queryable from day one — no export step, no reshaping."," Connect your own SQL client or ",[27,116,118],{"href":117},"/knowledge/export-form-responses-to-bigquery","export to BigQuery"," when you want to analyze form data next to everything else.",{"title":121,"searchDepth":122,"depth":122,"links":123},"",2,[124,125,126,127],{"id":17,"depth":122,"text":18},{"id":34,"depth":122,"text":35},{"id":93,"depth":122,"text":94},{"id":107,"depth":122,"text":108},"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.",false,"md",[133,136,139],{"q":134,"a":135},"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":137,"a":138},"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":140,"a":141},"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",{},true,"/knowledge/query-form-data-with-sql","form-data-architecture","Form data architecture",{"title":5,"description":129},[150,154,157],{"title":151,"url":152,"publisher":153},"SELECT","https://www.postgresql.org/docs/current/sql-select.html","PostgreSQL Global Development Group",{"title":155,"url":156,"publisher":153},"Aggregate Functions","https://www.postgresql.org/docs/current/functions-aggregate.html",{"title":158,"url":159,"publisher":153},"Joins Between Tables","https://www.postgresql.org/docs/current/tutorial-join.html","knowledge/query-form-data-with-sql",[162,163,164],"sql","analytics","guide",[],"8-YH_T1SbhmtDvDbowCzrSccBzS9qyn4zgXNLGRfKJk",[],[169,253],{"id":170,"title":171,"author":6,"body":172,"date":224,"description":225,"draft":130,"extension":131,"faqs":226,"image":236,"isPillar":130,"meta":237,"navigation":144,"path":238,"pillar":146,"pillarName":147,"seo":239,"sources":240,"stem":247,"tags":248,"takeaways":251,"updated":224,"__hash__":252},"knowledge/knowledge/export-form-responses-to-csv.md","How to Export Form Responses to CSV",{"type":8,"value":173,"toc":219},[174,177,181,187,191,201,205],[11,175,176],{},"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,178,180],{"id":179},"what-makes-a-clean-csv-export","What makes a \"clean\" CSV export?",[11,182,183,186],{},[22,184,185],{},"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,188,190],{"id":189},"why-are-spreadsheet-based-form-tools-messy-to-export","Why are spreadsheet-based form tools messy to export?",[11,192,193,196,197,200],{},[22,194,195],{},"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 ",[50,198,199],{},"COPY"," command to stream a table straight to CSV.",[15,202,204],{"id":203},"how-roundpushpin-exports-to-csv","How RoundPushPin exports to CSV",[11,206,207,210,211,214,215,218],{},[22,208,209],{},"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 ",[27,212,213],{"href":117},"exports directly to BigQuery"," or is ",[27,216,217],{"href":145},"queryable in place with SQL",".",{"title":121,"searchDepth":122,"depth":122,"links":220},[221,222,223],{"id":179,"depth":122,"text":180},{"id":189,"depth":122,"text":190},{"id":203,"depth":122,"text":204},"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.",[227,230,233],{"q":228,"a":229},"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":231,"a":232},"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":234,"a":235},"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",{},"/knowledge/export-form-responses-to-csv",{"title":171,"description":225},[241,245],{"title":242,"url":243,"publisher":244},"Common Format and MIME Type for CSV Files (RFC 4180)","https://www.rfc-editor.org/rfc/rfc4180","IETF",{"title":199,"url":246,"publisher":153},"https://www.postgresql.org/docs/current/sql-copy.html","knowledge/export-form-responses-to-csv",[249,250,164],"csv","export",[],"Yr1qEbZAaKKsvAjU32SFRvpGh9Mjbh9-9tnKT98Budw",{"id":254,"title":255,"author":6,"body":256,"date":325,"description":326,"draft":130,"extension":131,"faqs":327,"image":337,"isPillar":130,"meta":338,"navigation":144,"path":339,"pillar":146,"pillarName":147,"seo":340,"sources":341,"stem":351,"tags":352,"takeaways":355,"updated":325,"__hash__":356},"knowledge/knowledge/store-form-responses-in-a-database.md","How to Store Form Responses in a Database",{"type":8,"value":257,"toc":319},[258,261,265,271,275,293,297,303,307],[11,259,260],{},"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,262,264],{"id":263},"when-should-you-store-form-responses-in-a-database","When should you store form responses in a database?",[11,266,267,270],{},[22,268,269],{},"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,272,274],{"id":273},"how-do-you-model-form-responses-as-tables","How do you model form responses as tables?",[11,276,277,280,281,284,285,288,289,292],{},[22,278,279],{},"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 — ",[50,282,283],{},"NOT NULL"," for required questions, ",[50,286,287],{},"UNIQUE"," where answers must not repeat, and foreign keys linking responses back to their form. PostgreSQL's ",[50,290,291],{},"CREATE TABLE"," and constraint system exist precisely to encode these rules at the data layer, where they can't be bypassed.",[15,294,296],{"id":295},"what-about-questions-that-change-over-time","What about questions that change over time?",[11,298,299,302],{},[22,300,301],{},"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,304,306],{"id":305},"how-roundpushpin-stores-responses-for-you","How RoundPushPin stores responses for you",[11,308,309,312,313,86,316,218],{},[22,310,311],{},"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 ",[27,314,315],{"href":145},"query with SQL",[27,317,318],{"href":238},"export to CSV",{"title":121,"searchDepth":122,"depth":122,"links":320},[321,322,323,324],{"id":263,"depth":122,"text":264},{"id":273,"depth":122,"text":274},{"id":295,"depth":122,"text":296},{"id":305,"depth":122,"text":306},"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.",[328,331,334],{"q":329,"a":330},"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":332,"a":333},"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":335,"a":336},"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":255,"description":326},[342,344,347],{"title":291,"url":343,"publisher":153},"https://www.postgresql.org/docs/current/sql-createtable.html",{"title":345,"url":346,"publisher":153},"Constraints","https://www.postgresql.org/docs/current/ddl-constraints.html",{"title":348,"url":349,"publisher":350},"Drizzle ORM — Migrations","https://orm.drizzle.team/docs/migrations","Drizzle","knowledge/store-form-responses-in-a-database",[353,354,164],"database","postgresql",[],"T9BsnyPelUYLC2_-LOVcmy8gU4xiki1YmYNpwgbC1_g",1780692426588]