SysTutorials

Generate and Store HTML Forms from Database Data

· updated April 11, 2026

When you need to store form submissions where different form types have different attributes, you’re essentially implementing an Entity-Attribute-Value (EAV) model. This is a common pattern for flexible schemas, but it requires careful design.

The Core Problem

You have multiple form types (Laptop, Mobile, Tablet) with different fields. A naive approach is a wide table with all possible columns, but that wastes storage and becomes unwieldy at scale. The alternative is a normalized schema that handles variable attributes per form type.

Use a three-table approach: one for the items being submitted, one for the attribute definitions, and one to store the actual values.

items table — stores one row per form submission

CREATE TABLE items (
    item_id INT PRIMARY KEY AUTO_INCREMENT,
    form_id INT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (form_id) REFERENCES forms(form_id)
);

forms table — defines available form types

CREATE TABLE forms (
    form_id INT PRIMARY KEY AUTO_INCREMENT,
    form_name VARCHAR(100),
    description TEXT
);

form_attributes table — junction table connecting forms to available attributes

CREATE TABLE form_attributes (
    form_id INT NOT NULL,
    attribute_id INT NOT NULL,
    PRIMARY KEY (form_id, attribute_id),
    FOREIGN KEY (form_id) REFERENCES forms(form_id),
    FOREIGN KEY (attribute_id) REFERENCES attributes(attribute_id)
);

attributes table — defines the attribute names and their data types

CREATE TABLE attributes (
    attribute_id INT PRIMARY KEY AUTO_INCREMENT,
    attribute_name VARCHAR(100),
    data_type ENUM('string', 'integer', 'decimal', 'date')
);

item_attributes table — stores the actual values submitted by users

CREATE TABLE item_attributes (
    item_id INT NOT NULL,
    attribute_id INT NOT NULL,
    attribute_value VARCHAR(500),
    PRIMARY KEY (item_id, attribute_id),
    FOREIGN KEY (item_id) REFERENCES items(item_id),
    FOREIGN KEY (attribute_id) REFERENCES attributes(attribute_id)
);

Sample Data

Start with your attribute definitions:

INSERT INTO attributes (attribute_name, data_type) VALUES
('Model', 'string'),
('Screen Size', 'decimal'),
('OS', 'string'),
('CD Player', 'string');

INSERT INTO forms (form_name, description) VALUES
(1, 'Laptop', 'Possible attributes: Model, Screen Size, OS, CD Player'),
(2, 'Mobile', 'Possible attributes: Model, OS'),
(3, 'Tablet', 'Possible attributes: Model, Screen Size, OS');

INSERT INTO form_attributes (form_id, attribute_id) VALUES
(1, 1), (1, 2), (1, 3), (1, 4),
(2, 1), (2, 3),
(3, 1), (3, 2), (3, 3);

When a user submits a Laptop form with values:

INSERT INTO items (form_id) VALUES (1);
-- Assume item_id 9 was generated

INSERT INTO item_attributes (item_id, attribute_id, attribute_value) VALUES
(9, 1, 'HP Pavilion 15'),
(9, 2, '15.6'),
(9, 3, 'Windows 11'),
(9, 4, 'Yes');

Retrieving and Filtering Data

To fetch a specific item with all its attributes:

SELECT 
    a.attribute_name,
    ia.attribute_value
FROM item_attributes ia
JOIN attributes a ON ia.attribute_id = a.attribute_id
WHERE ia.item_id = 9;

To filter items by a range (e.g., laptops with screen size 14 to 16):

SELECT DISTINCT
    i.item_id,
    f.form_name
FROM items i
JOIN item_attributes ia ON i.item_id = ia.item_id
JOIN attributes a ON ia.attribute_id = a.attribute_id
JOIN forms f ON i.form_id = f.form_id
WHERE i.form_id = 1  -- Laptops only
AND a.attribute_id = 2  -- Screen Size
AND CAST(ia.attribute_value AS DECIMAL(5,2)) BETWEEN 14 AND 16;

For multiple filter conditions (e.g., screen size AND OS):

SELECT DISTINCT
    i.item_id,
    f.form_name
FROM items i
JOIN forms f ON i.form_id = f.form_id
WHERE i.form_id = 1
AND i.item_id IN (
    SELECT ia1.item_id
    FROM item_attributes ia1
    JOIN attributes a1 ON ia1.attribute_id = a1.attribute_id
    WHERE a1.attribute_id = 2 
    AND CAST(ia1.attribute_value AS DECIMAL(5,2)) BETWEEN 14 AND 16
)
AND i.item_id IN (
    SELECT ia2.item_id
    FROM item_attributes ia2
    JOIN attributes a2 ON ia2.attribute_id = a2.attribute_id
    WHERE a2.attribute_id = 3 
    AND ia2.attribute_value = 'Linux'
);

HTML Form Generation

When displaying a form, query the allowed attributes for that form type:

SELECT a.attribute_id, a.attribute_name, a.data_type
FROM attributes a
JOIN form_attributes fa ON a.attribute_id = fa.attribute_id
WHERE fa.form_id = ?;

Then dynamically generate input fields based on the data_type. Use the attribute_id as the field name or data attribute to map submissions back to the attribute definitions.

Trade-offs

This normalized approach scales well with 40+ form types and 15+ attributes per form. Storage is minimal because you only store actual values. Query complexity increases with multiple filters, but this is manageable with proper indexing on item_id and attribute_id.

For high-volume filtering, consider denormalizing specific frequently-queried attributes (like Screen Size) into the items table, keeping the EAV structure for less-common fields. Add indexes on item_attributes(attribute_id, attribute_value) and items(form_id, created_at) to optimize common queries.

← All posts