CMD Guide
HomeDatabasesSQL Practice Problems

Dynamic Unpivoting of a Table

Problem

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| product_id  | int     |
| store_name1 | int     |
| store_name2 | int     |
|      :      | int     |
|      :      | int     |
|      :      | int     |
| store_namen | int     |
+-------------+---------+
product_id is the primary key for this table.
Each row in this table indicates the product's price in n different stores.
If the product is not available in a store, the price will be null in that store's column.
The names of the stores may change from one testcase to another. There will be at least 1 store and at most 30 stores.

Problem Definition

Implement the procedure UnpivotProducts to reorganize the Products table so that each row has the id of one product, the name of a store where it is sold, and its price in that store. If a product is not available in a store, do not include a row with that product_id and store combination in the result table. There should be three columns: product_id, store, and price.

The procedure should return the table after reorganizing it.

Return the result table in any order.

Example

Image
Image

Output

Image
Image

Solution

To transform the Products table from a wide format—where each store has its own column—to a long format—where each row represents a product's price in a specific store—we implement the stored procedure UnpivotProducts. This procedure dynamically reorganizes the data, ensuring flexibility regardless of the number or names of stores involved. Below is a comprehensive breakdown of the approach, step-by-step execution, and detailed explanations for each component of the provided SQL query.

Approach Overview

  1. Understand the Data Structure:

    • Wide Format: The Products table has multiple columns for each store (store_name1, store_name2, ..., store_namen), each representing the price of the product in that store.
    • Long Format: The desired output has three columns: product_id, store, and price, with each row representing a product's price in a specific store.
  2. Identify Store Columns Dynamically:

    • Since the number and names of stores can vary across different test cases (up to 30 stores), the procedure must dynamically identify all store columns in the Products table.
  3. Construct Dynamic SQL for Unpivoting:

    • Use SQL string functions to build a dynamic SELECT statement that unpivots the data by converting each store column into separate rows.
  4. Execute the Dynamic SQL:

    • Prepare and execute the dynamically constructed SQL statement to generate the final unpivoted table.

SQL Query

CREATE PROCEDURE UnpivotProducts() BEGIN # Write your MySQL query statement below. set group_concat_max_len = 1000000; set @sql = null; with stores as ( SELECT COLUMN_NAME store FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products' and COLUMN_NAME<>'product_id' ) select group_concat( concat( 'select product_id, "', store, '" ', 'as store, ', store, ' ', 'as price from Products where ', store, ' is not null union' ) order by store separator ' ' ) into @sql from stores; /*select SUBSTRING(@sql, 1, LENGTH(@sql)-6);*/ set @sql = SUBSTRING(@sql, 1, LENGTH(@sql)-6); prepare stmt from @sql; execute stmt; END

Step-by-Step Explanation

Step 1: Increase GROUP_CONCAT Maximum Length

set group_concat_max_len = 1000000;

Step 2: Initialize the SQL Variable

set @sql = null;

Step 3: Retrieve Store Column Names

with stores as ( SELECT COLUMN_NAME store FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products' and COLUMN_NAME<>'product_id' )

Step 4: Dynamically Construct SELECT Statements for Each Store

select group_concat( concat( 'select product_id, "', store, '" ', 'as store, ', store, ' ', 'as price from Products where ', store, ' is not null union' ) order by store separator ' ' ) into @sql from stores;

Step 5: Remove the Trailing 'UNION'

/*select SUBSTRING(@sql, 1, LENGTH(@sql)-6);*/ set @sql = SUBSTRING(@sql, 1, LENGTH(@sql)-6);

Step 6: Prepare and Execute the Dynamic SQL Statement

prepare stmt from @sql; execute stmt;

Pattern: dynamic UNPIVOT (metadata-driven UNION ALL)

Name: turn wide store columns into tall (product_id, store, price) rows by generating SQL from information_schema when the store set is not fixed at authoring time.

Static alternative (when store set is known):

SELECT product_id, 'store1' AS store, store1 AS price FROM Products WHERE store1 IS NOT NULL
UNION ALL
SELECT product_id, 'store2', store2 FROM Products WHERE store2 IS NOT NULL
UNION ALL
SELECT product_id, 'store3', store3 FROM Products WHERE store3 IS NOT NULL;
-- Postgres 11+ / Oracle / SQL Server also have UNPIVOT or CROSS JOIN LATERAL (VALUES …)

Prefer static UNION ALL in application code when columns rarely change — simpler, plan-cache friendly, no dynamic SQL.

Case-sensitivity of INFORMATION_SCHEMA: matching TABLE_NAME = 'Products' is case-sensitive on some MySQL installs when lower_case_table_names=0 (typical on Linux). If the table was created as products or PRODUCTS, a hard-coded 'Products' returns zero columns and generates empty/invalid SQL. Compare using the actual table name as stored, or normalize with the same casing the server uses (SHOW TABLES, query information_schema.tables without assuming CamelCase).

SQL injection: never concatenate unsanitized user input into dynamic SQL. Table/column names from information_schema are safer than user strings, but if any identifier comes from an API parameter, allow-list it against information_schema before quoting with backticks/`quote_ident`.

When-NOT: dynamic unpivot inside a hot OLTP path on every request — cache the generated statement or redesign to a tall fact table so unpivot is unnecessary.

Drill: Write the static three-store UNION ALL for the sample Products table; list one failure mode if TABLE_NAME casing is wrong.

🎯 STANDOUT elevation: Why / example / when-not / failure / panel / drills — Dynamic Unpivoting of a Table

Why this exists / the decision it encodes

Wide store-per-column layout is convenient for pivots but wrong for analytics queries. Unpivot restores long form (product_id, store, price). Dynamic SQL exists because store column names vary by testcase — static UNION ALL is simpler when the schema is fixed.

Worked example with numbers or traced SQL/FD

Wide: product_id | Shop | LC | Noi
Long: rows (id,'Shop',price) UNION ALL … WHERE price IS NOT NULL
Dynamic MySQL sketch:
  INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products' AND COLUMN_NAME<>'product_id'
  GROUP_CONCAT(CONCAT('SELECT product_id, \"',store,'\" AS store, ',store,
                      ' AS price FROM Products WHERE ',store,' IS NOT NULL UNION'))
  strip trailing UNION; PREPARE/EXECUTE
Case: MySQL table names may be case-sensitive depending on OS/lower_case_table_names —
filter TABLE_SCHEMA = DATABASE() too
Security: never concatenate untrusted identifiers; catalog-driven names only
group_concat_max_len must be raised or @sql truncates silently

When NOT / named alternative

Prefer static UNION ALL / CROSS JOIN unnest when ≤ few known stores. Prefer native UNPIVOT (SQL Server/Oracle) or Postgres LATERAL/jsonb when available. Do not dynamic-SQL unpivot for every request in OLTP — materialize long-form or EAV carefully.

Failure mode / ops fingerprint / interview trap

Trap: INFORMATION_SCHEMA without schema filter hits wrong table name in another DB. Ops: SQL injection if store names ever come from user input. Silent truncation of @sql. UNION vs UNION ALL: branches are disjoint by construction — ALL avoids useless dedup.

Domain judgment (K11 theory-bridge / K12 concurrency / K13 query-judgment)

K13: wide vs long is a schema-for-query-shape decision. Dynamic SQL is a last resort with catalog safety rails. Prefer UNION ALL over UNION in generated branches.

Hostile-panel drills (with model answers)

Q1. Why dynamic SQL here?
Model answer: Store column names and count vary across testcases (≤30); a fixed SELECT list cannot name unknown columns at procedure author time.

Q2. Static alternative for fixed stores Shop, LC, Noi?
Model answer: SELECT product_id,'Shop' store, Shop price FROM Products WHERE Shop IS NOT NULL UNION ALL SELECT … LC … UNION ALL SELECT … Noi …

Q3. Name two production hazards of this procedure pattern.
Model answer: (1) group_concat truncation without raising max_len. (2) Missing TABLE_SCHEMA filter / case-sensitivity mismatches. (3) Injection if identifiers were not catalog-sourced.

🤖 Don't fully get this? Learn it with Claude

Stuck on Dynamic Unpivoting of a Table? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Dynamic Unpivoting of a Table** (Databases) and want to truly understand it. Explain Dynamic Unpivoting of a Table from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Dynamic Unpivoting of a Table** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Dynamic Unpivoting of a Table** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Dynamic Unpivoting of a Table** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes