Anomalies, First and Second Normal Form

Normalization and Transactions · 30 min

Core CS · DBMS

Three writes that go wrong on one good-looking table

One lab register, six rows, every column declared correctly. You will watch an insert the table refuses, an update that lands on two rows out of three, and a delete that removes a student who is still enrolled. Then you will split it once and run all three again.

Break the register, then split it
Asha’s batch is one fact. The register writes it on three rows. Nothing in the schema says those three cells have to agree, and nothing raises an error on the day they stop.

01 The idea

The register that cannot record a new student

A department keeps one table for its DBMS lab: who the student is, which experiment they submitted, and what it scored. Every column has a type, the primary key is declared, and every row in it is true. Three ordinary operations still go wrong on it, and not one of them raises an error or violates a constraint.

Those three are called anomalies: an insert, update or delete that leaves the data wrong or that the table will not let you perform, even though your code has no bug in it. They are not bad luck and they are not three separate diseases. All three come from one cause, which is that this table stores two different kinds of fact at once. “CS07 is Asha, batch B1” is a fact about a student. “CS07 scored 15 on E2” is a fact about a submission. Put them in the same row and the student fact gets copied onto every submission the student makes.

That copying is redundancy, and it is worth being precise about why it matters, because the usual answer is wrong. Redundancy is not a disk problem. Splitting the register in this lesson saves three cells out of thirty, which is not a reason to do anything. Redundancy is a write problem: a fact stored in three places gives every future update three chances to be right, and no way to notice when it was right twice. The anomalies are that bill arriving.

Normalisation pays that bill by moving each fact to a table whose key is the thing the fact is about. This lesson does the first two steps. 1NF makes every cell hold one value, which is what lets you state a rule about the data at all. 2NF then removes exactly the copying that 1NF created. They run in that order for a reason you will see in section 02: flattening the table is what grows the key from one column to two, and a key made of more than one column is the only thing that makes a 2NF violation possible in the first place.

A fact belongs on exactly as many rows as it is about. Write a student’s batch on three rows and you have bought three chances to disagree, one student you cannot record at all, and one delete that takes her with it.
Repeating groupOne cell holding a list of values, or several columns holding the same kind of value. E1, E2, E3 in one cell is one; so is replacing it with Expt1, Expt2, Expt3. 1NF exists to remove both shapes.
Prime attributeAn attribute that belongs to at least one candidate key, where a candidate key is a minimal set of columns unique across every legal row. Everything in no candidate key is non-prime, and 2NF is a rule about non-prime attributes only.
Partial dependencyA non-prime attribute determined by a proper subset of a candidate key. Three conditions, all required: the target is non-prime, the determinant is part of a key, and not the whole of it.

02 Worked example

One lab register, and the three writes it breaks on

Here is the register as the department actually keeps it: one line per student, with the experiment numbers typed into one cell and the marks into another. Three students, three rows, and everything a human needs to read it.

-- LAB_RECORD, as kept in the spreadsheet. Not a relation yet.RollNo  StudentName  Batch  Experiments   MarksCS07    Asha         B1     E1, E2, E3    18, 15, 20CS12    Ravi         B1     E1, E2        12, 19CS19    Meera        B2     E1            17 -- two lists in two cells, and nothing but position pairs E2 with 15.-- delete one entry from one list and every pairing after it shifts.

The Experiments and Marks columns are what stops this from being a relation, and they cost you twice. A cell in them holds three values instead of one, and the pairing between an experiment and its mark is carried by nothing but the order of two strings. 1NF requires that every attribute value is a single value drawn from its domain, with no repeating group. Flattening turns each list entry into its own row, so 3 rows become 3 + 2 + 1 = 6.

-- LAB_RECORD in 1NF. One value per cell. Key: {RollNo, ExptNo}RollNo  StudentName  Batch  ExptNo  MarksCS07    Asha         B1     E1      18CS07    Asha         B1     E2      15CS07    Asha         B1     E3      20CS12    Ravi         B1     E1      12CS12    Ravi         B1     E2      19CS19    Meera        B2     E1      17 -- RollNo -> StudentName, Batch      half the key decides both-- {RollNo, ExptNo} -> Marks         the whole key decides this one

The key had to grow. Before flattening, RollNo identified a row on its own. Now CS07 is on three rows and E1 is on three rows, so neither column is unique by itself and the candidate key is the pair {RollNo, ExptNo}. Drop either half and rows collide, so the pair is minimal, and there is no other candidate key in this table.

Line 10 is the whole lesson. RollNo is half of that key, and it decides both StudentName and Batch on its own. So Asha’s name and batch are written on all three of her rows and Ravi’s on both of his. Count the copies: six cells out of thirty are repeats of a fact already stored one line above.

1 · One row per studentthe spreadsheet shape: experiment numbers in one cell, marks in another
2 · Flatten it1NF: one value per cell, 3 rows become 6, the key grows to {RollNo, ExptNo}
3 · The name comes alongStudentName and Batch hang off RollNo, half the new key, so they are copied onto every row the student has
4 · Three writes breakone insert rejected, one update lands on 2 rows of 3, one delete removes a student
5 · Split on the dependency2NF: RollNo and everything it decides move to their own table, and RollNo stays behind as a foreign key

Read the highlighted node as the pivot, not the mistake. 1NF was correct and necessary: until every cell held one value there was no dependency you could state and no key you could declare. What 1NF did was make the key composite, and a composite key is the only place a column can depend on part of the key. Node 3 is that possibility being realised, and nodes 4 and 5 are its consequence and its fix.

Now run the three operations against the six-row table. The exact statements, and the exact thing the database says back:

-- 1. INSERT anomaly. Karan joins the lab today and has submitted nothing.INSERT INTO lab_record VALUES ('CS24','Karan','B2', NULL, NULL);  -> ERROR: null value in column "exptno" violates not-null constraint -- 2. UPDATE anomaly. Asha moves to B2. Her batch sits on three rows.UPDATE lab_record SET batch='B2' WHERE rollno='CS07' AND exptno IN ('E1','E2');  -> UPDATE 2        the third row still says B1SELECT DISTINCT batch FROM lab_record WHERE rollno='CS07';  -> B1  -> B2            one student, two batches, no error anywhere -- 3. DELETE anomaly. Meera withdraws her only submission.DELETE FROM lab_record WHERE rollno='CS19' AND exptno='E1';  -> DELETE 1        and CS19 Meera B2 is now stored nowhere at all

The insert. Karan exists, he is in batch B2, and the register has nowhere to put him. ExptNo is half the primary key, and no part of a primary key may be null: that is entity integrity, and it is the rule doing the rejecting. PostgreSQL words the message as a not-null violation because it implements a primary key as NOT NULL plus UNIQUE; Oracle says ORA-01400 and MySQL raises error 1048. Same rule, three wordings. The only way to record Karan is to invent an experiment he has not done, and inventing data to satisfy a schema is the definition of an insertion anomaly.

The update. Asha’s batch is not one cell to change. It is three, and nothing in the schema links them, so the update has to be right three times over. This one was written off a report of the two experiments already graded, so its WHERE clause names E1 and E2 and never reaches E3. Two rows change and one does not. Look at what the database now believes: CS07 is in B2 on two rows and B1 on one, and every one of those three rows is individually legal. No type is wrong, no constraint is broken, no error was raised. The table holds two answers to one question and cannot tell you which is current.

The delete. Meera has exactly one row, because she has submitted exactly one experiment. Deleting that submission deletes the only place the register recorded that CS19 is Meera and that she is in batch B2. She has not left the college. The database no longer holds any record that she is in it, and nothing about the operation looked unusual: one row asked for, one row deleted.

Three operations, three different failures, zero errors. That is what makes anomalies dangerous in an interview answer and in production: they are not caught by constraints, because the schema never recorded that Asha’s three batch cells were the same fact.

03 Mechanics

Two tests, and the split the second one forces

Anomalies are the symptom. The test is mechanical, and it runs on functional dependencies, written X → Y: any two rows that agree on X must agree on Y. That is a rule about every legal row, so you confirm it from what the data means, never by eyeballing six sample rows. Here is every non-trivial dependency this table has, tested one at a time.

DependencyTested against the 2NF ruleVerdict
RollNo → StudentName RollNo is a proper subset of the only candidate key {RollNo, ExptNo}, and StudentName is in no candidate key, so it is non-prime. Partial. 2NF fails.
RollNo → Batch Same determinant, same reasoning. Batch is non-prime and depends on half the key. Partial. 2NF fails.
{RollNo, ExptNo} → Marks The determinant is the whole key. RollNo alone does not fix a mark (Asha has 18, 15 and 20) and ExptNo alone does not either. Full. Allowed.
ExptNo → ? E1 sits on three rows carrying 18, 12 and 17, so ExptNo fixes no other value in this table. No dependency. Nothing hangs off this half.
StudentName → ? and Batch → ? Two students may share a name, so a name fixes nothing; B1 covers both CS07 and CS12, so a batch fixes nothing. No dependency. Checked, not assumed.

Row four is why this decomposition produces two tables and not three. Nothing hangs off ExptNo, so there is no experiment table to build. Row five matters as much and is the one candidates skip: you have to check the columns that are not keys before you claim a table is finished, rather than assume they determine nothing.

FormThe test, exactlyWhat it removesWhat it costs
1NF Every attribute value is a single value drawn from its domain, and no group of columns repeats the same kind of value. Lists inside cells, and the positional pairing between two parallel lists. the key grows to two columns here, and each student’s details are copied onto every row
2NF In 1NF, and no non-prime attribute is functionally dependent on a proper subset of any candidate key. Exactly the copies 1NF created, for the columns that hang off part of the key. one join whenever you want a name next to a mark

The fix is mechanical once the dependency is written down: take the offending determinant and everything it determines into a new table where that determinant is the key, and leave it behind in the original as a foreign key. Both violations here share the determinant RollNo, so one split closes both.

CREATE TABLE student (  rollno  TEXT PRIMARY KEY,            -- the whole key of this table now  name    TEXT NOT NULL,  batch   TEXT NOT NULL);CREATE TABLE result (  rollno  TEXT REFERENCES student(rollno),  -- left behind as a foreign key  exptno  TEXT,  marks   INT NOT NULL CHECK (marks BETWEEN 0 AND 20),  PRIMARY KEY (rollno, exptno));

Line 2 is the split. RollNo was half a key and is now a whole one, which is the only change that matters. A student’s name and batch are written once each, on the single row that is about that student.

Line 10 keeps the composite key where it belongs. Marks genuinely depends on both halves, so result keeps the pair as its key and carries nothing else. Its only dependency is {rollno, exptno} → marks, and the determinant is the key.

Neither table has a rule left over. Check rather than assume: in student, name determines nothing because two students may share a name, and batch determines nothing because B1 covers two roll numbers. Both tables are as far as this lesson’s two rules can take them. Whether a 2NF split always lands there is the question the next lesson opens.

Now check the split did not lose anything, because a decomposition that drops or invents rows is worse than the table you started with. A two-way split is lossless when the columns the two pieces share are a superkey of at least one of them. Here the shared column is rollno, which is the whole key of student, so every result row matches exactly one student row: never two, because the key is unique, and never zero, because the foreign key says so. Join them back and count: CS07 contributes 3 rows, CS12 contributes 2, CS19 contributes 1. Six rows in, six rows out, identical to the register.

Compare that with a split that ignores the condition. Take A(RollNo, StudentName, Batch) and B(Batch, ExptNo, Marks). Every column survives and neither piece has a duplicate row, so it looks fine. The shared column is Batch, which is a key of neither. Joining on it pairs A’s two B1 rows with B’s five B1 rows and A’s single B2 row with B’s single B2 row: 2 × 5 + 1 × 1 = 11 rows where there were 6. Nothing was lost, and five rows appeared that were never real, among them CS12, Ravi, B1, E3, 20 for an experiment Ravi never submitted.

Last, the arithmetic that settles the space argument. The register is 6 rows by 5 columns, so 30 cells. After the split, student is 3 by 3 and result is 6 by 3, so 27. You saved three name cells and three batch cells and paid three back on the duplicated rollno column: a net saving of 3 cells, or 10 per cent, which would not justify a single extra join on its own. The reason to do it is the thing you cannot measure on disk, which is that after the split there is no write that can leave two rows disagreeing.

2NF is one question, asked once per non-prime column and against every candidate key: does this column depend on the whole key, or on part of it? If it depends on part of it, that part is a key in disguise, and the column belongs in a table where that part is the key.

05 Cheat sheet

1NF and 2NF on one card

Every row here is something an interviewer asks directly. Learn the middle column as a sentence you can say, not as a phrase you recognise.

TermWhat it means, exactlyOn our register
Insertion anomalyA fact you are entitled to store and the schema has nowhere to put, usually because part of the key would have to be null.Karan has joined the lab and submitted nothing, so he cannot be recorded
Update anomalyA fact stored on several rows, so one write must be right on all of them, and being right on some raises no error.batch changed on 2 of Asha’s 3 rows: B1 and B2 at once
Deletion anomalyRemoving one fact removes a second, different fact that had no other row of its own.deleting Meera’s only mark deletes Meera
1NFEvery attribute value is a single value from its domain, and no group of columns repeats the same kind of value.E1, E2, E3 in one cell fails; 6 rows with one ExptNo each pass
Repeating groupA list inside a cell, or Expt1 / Expt2 / Expt3 as separate columns. The second removes the commas and still fails 1NF.a fourth experiment means a schema change, not a row
Prime attributeBelongs to at least one candidate key. Everything else is non-prime.prime: RollNo, ExptNo · non-prime: StudentName, Batch, Marks
Partial dependencyA non-prime attribute determined by a proper subset of a candidate key.RollNo → StudentName, and RollNo is half of {RollNo, ExptNo}
Full dependencyDetermined by the whole key, with no proper part of it sufficient.{RollNo, ExptNo} → Marks
2NFIn 1NF, and no non-prime attribute is partially dependent on a candidate key.fails twice, both on RollNo · one split closes both
Automatically in 2NFA 1NF relation with no composite candidate key at all, or in which every attribute is prime. Neither case leaves the test anything to fail on.student(RollNo, ...) after the split · RollNo is its only key
Lossless splitThe shared columns are a superkey of at least one piece, so every row of one matches exactly one row of the other.RollNo is the key of student · 6 rows in, 6 rows out
1NF creates the problem 2NF solvesBefore flattening, RollNo was the whole key and no partial dependency was possible. Flattening grew the key to two columns, and only a composite key gives a non-prime column a part of a key to depend on. That is why the forms run in this order: each one makes the next one’s failure expressible.
Space is never the argumentThirty cells became twenty-seven here, a 10 per cent saving that would not pay for one join. On a wider table the number is bigger, and it is still not the reason. The reason is that a fact stored once cannot be updated inconsistently, and that is not a quantity you can read off disk.
Test against every candidate key2NF is defined against all candidate keys, not only the one you happened to declare as PRIMARY KEY. A table with two candidate keys can survive the test against one and fail it against the other, and failing once is enough. No engine checks this for you: PRIMARY KEY enforces uniqueness and never a normal form.

06 Where & why

Where 1NF is a choice, and where 2NF is never checked

No product refuses a table for a partial dependency, and several will happily store a list in a cell. Both forms are decisions you make and defend, which is exactly why an interviewer can tell the difference between a candidate who has made them and one who has read about them.

PostgreSQL
1NF is a choice the engine leaves to you

A text[] or jsonb column will hold {E1,E2,E3} without complaint, and a GIN index will even search inside it. That is the right call when the list is only ever read whole with its parent row. It is the wrong one the first time somebody asks for marks per experiment, because the query becomes an unnest and the join you avoided arrives anyway, without a key to help it.

Oracle
Collection columns are an official feature

VARRAY and nested table column types have let an Oracle column hold a collection since the object-relational extensions, and SQL:1999 standardised the idea. Worth knowing for one sentence in an interview: 1NF is a rule of the relational model and of your design, not a limit the product imposes on you. If you break it, break it deliberately and write down why.

MySQL · InnoDB
Nothing checks 2NF, and a fat key is charged for twice

PRIMARY KEY (rollno, exptno) enforces uniqueness and nothing else. In InnoDB it also costs: the table is physically stored in primary-key order, and every secondary index stores the primary key as its row pointer. A wide composite key inherited from an un-normalised table is therefore paid for again on every index you add to it.

SQL Server
No array type, so the workaround is a string

SQL Server has no array column, so teams that skip 1NF store 'E1,E2,E3' in a varchar and pull it apart with STRING_SPLIT, available since SQL Server 2016. That call cannot use an index on the individual values, so every query filtering inside the list scans the table. Here the 1NF decision shows up as a slow plan rather than as an error.

Name the dependency, not the form. “This table is not in 2NF” is a label. “RollNo decides the batch, RollNo is half the key, so the batch is written once per experiment and every update has to be right three times” is an answer, and it is the sentence the split falls out of.

07 Interview questions

What they actually ask

Normalisation is asked in almost every DBMS interview, and it is almost always answered as recited definitions. What separates an answer is a concrete failure you can produce on demand and a dependency you can write on the whiteboard.

What is a data anomaly?
An insert, update or delete that leaves the table wrong, or that the table refuses to let you make, even though your code has no bug and no constraint is violated. There are three of them: insertion, update and deletion. All three have one cause, which is a table holding two different kinds of fact, so a write that is correct about one kind is wrong about the other.
Give me the three anomalies with one example each.
Insertion: Karan joins the lab today and cannot be recorded, because ExptNo is half the primary key and no part of a key may be null. Update: Asha changes batch, her batch sits on three rows, and an update that reaches two of them leaves the table saying B1 and B2 at once. Deletion: Meera has one row, and deleting that mark deletes the only record that she exists. None of the three raises an error, which is the part worth saying out loud.
Redundancy costs almost nothing on disk. So why is it a problem?
Because the copies are allowed to disagree. Splitting our register saves three cells out of thirty, which is not a reason to do anything at all. What you buy is that afterwards exactly one row carries a student’s batch, so an update cannot land on some copies and leave the rest behind. The currency is not disk; it is the number of places a single write has to be right.
What exactly does 1NF require?
Two things: every attribute value is a single value drawn from its domain, and no group of columns repeats the same kind of value. Our register broke both at once by keeping E1, E2, E3 in one cell with 18, 15, 20 beside it, where nothing but string position paired E2 with 15. Worth adding that atomic is relative to the application: a full name is one value if you never query the surname and two if you do.
Is Expt1, Expt2, Expt3 in first normal form?
No. Every cell holds one value, so it passes the first half of the definition and fails the second: three columns holding the same kind of value are a repeating group. The practical tell is the query. “Which students attempted E2” becomes three conditions joined by OR instead of one, and a fourth experiment means altering the table rather than inserting a row.
1NF made the table bigger and more repetitive. Why is it still step one?
Because until every cell holds one value there is nothing to reason about. You cannot state a functional dependency over a comma-separated list, you cannot declare it as a key, and an index or a join over it does not mean anything. 1NF also grows the key from one column to two, and that is exactly what makes a partial dependency possible, so it does not only precede 2NF, it creates the violation 2NF removes.
What makes an attribute prime?
It belongs to at least one candidate key. In our register RollNo and ExptNo are prime, and StudentName, Batch and Marks are not. The “at least one” matters, because a table can have several candidate keys and membership of any of them is enough. It matters at all because 2NF is a rule about non-prime attributes and says nothing about the others.
Define a partial dependency precisely.
X → A where A is non-prime, X is a proper subset of a candidate key, and the dependency actually holds. Three conditions, and candidates routinely drop one. RollNo → StudentName qualifies, because the name is in no key and RollNo is half of {RollNo, ExptNo}. {RollNo, ExptNo} → Marks does not, because the determinant is the whole key and neither half fixes a mark on its own.
What is 2NF?
A relation is in 2NF when it is in 1NF and no non-prime attribute is partially dependent on a candidate key. Our register fails twice, on RollNo → StudentName and RollNo → Batch. The repair is mechanical: take the offending determinant and everything it determines into a table where that determinant is the key, and leave it behind as a foreign key. Both violations shared a determinant, so one split closed both.
A table has a single-column primary key. Is it in 2NF?
Yes, as long as it is in 1NF and that column is its only candidate key. A partial dependency needs a proper subset of a candidate key on the left, and a one-column key has no non-empty proper subset, so the test has nothing to fail on. Say the caveat out loud, because it is the follow-up: if the same table also has a two-column candidate key, something can still depend on half of that one, and 2NF is judged against every candidate key. The same free pass applies when every attribute is prime, since 2NF constrains non-prime attributes and there are none.
How do you know the split did not lose anything?
Join the pieces back and check you get exactly the original rows, none missing and none extra. You can also know it in advance rather than by testing: a two-way split is lossless when the shared columns are a superkey of at least one piece. RollNo is the whole key of student, so every result row matches exactly one student row, and our six rows rejoin to six. Splitting on a column that is a key in neither piece is what invents rows.
The cell count barely moved. Was the split worth it?
On the numbers alone, no: thirty cells became twenty-seven and you now join two tables to put a name beside a mark. The reason to do it is that afterwards there is no write that can leave the register in a state a constraint cannot describe. Before the split, three ordinary statements broke it and not one raised an error. That is the trade in one sentence: read-time work bought with write-time certainty.

08 Practice problems

Six to take apart

Work in the same order every time: write the candidate keys down first, mark every attribute prime or non-prime, and only then test a dependency. Reaching for the split first is how people report violations that are not there.

Prime or not

Easy
SEAT(TrainNo, JourneyDate, CoachNo, SeatNo, PassengerName, TrainName) records one booked seat on one journey. A train runs on many dates, and seat number 12 exists in every coach. State the candidate key, then label every one of the six attributes prime or non-prime.
Follow-up
The column that names the train looks like it belongs in the key and does not, and the column you are most likely to leave out of the key is the one stopping two different journeys from collapsing into the same row.
Show the hint
Test each column you want to leave out of the key: if two real bookings would become the same row without it, it belongs in the key.

Flatten it, then test it

Easy
CERT(RollNo, StudentName, Batch, Certificates) holds CS07 Asha B1 with “AWS, Azure”, CS12 Ravi B1 with “AWS”, and CS19 Meera B2 with “AWS, GCP, Oracle”. Write the 1NF table, give its row count and its candidate key, then state whether that 1NF table is in 2NF and name every dependency that decides the answer.
Follow-up
Flattening does not finish the job. What you write down is a correct 1NF table and a broken 2NF one, and the evidence for the second part is visible in the row count before you test a single dependency.
Show the hint
Do the two jobs separately. Flatten and write every row out in full, then find the candidate key of what you wrote, and only then start testing dependencies against it.

Two keys, one verdict

Medium
SLOT(RoomNo, TimeSlot, CourseCode, Instructor) records which course runs in which room in which time slot. A room holds one course per slot, a course occupies one room in any given slot, and a course has exactly one instructor. List every candidate key, label every attribute prime or non-prime, and state whether the table is in 2NF.
Follow-up
Which candidate key you test against changes what you find, and 2NF is judged against all of them at once, so the one you would have written as PRIMARY KEY decides whether anybody ever notices.
Show the hint
Find every candidate key before you test anything. 2NF can only be broken by an attribute that is in none of them, so work out what is left over once you have them all.

Count the damage

Medium
MESS_BILL(RollNo, StudentName, RoomNo, BillMonth, AmountDue) has key {RollNo, BillMonth} and holds twelve monthly rows for each of 40 students. Give four numbers: total cells, redundant cells, rows a single room change has to touch, and total cells after the 2NF split. Then say which of the four is the reason to decompose.
Follow-up
The space number is the biggest of the four and the least important. Exactly one of the four does not grow when the hostel takes 400 students instead of 40, and that is the one that matters.
Show the hint
Work all four out for a single student first, then multiply by 40 and watch which multiplication changes nothing.

Find the rows that were never real

Medium
Split this lesson’s six-row register a different way: A(RollNo, StudentName, Batch, ExptNo) and B(ExptNo, Marks). Name the shared columns, say whether they are a superkey of either piece, then join the two back on that column and give the exact number of rows in the result and one row of it that was never a real submission.
Follow-up
Nothing is lost. All six original submissions are still in the result, so “no data was lost” is not the test you are being asked to apply here. The damage is entirely in what appears from nowhere.
Show the hint
For each experiment number, count how many rows each piece contributes. A join on a column that is a key in neither piece pairs every one of those with every one of the others.

In 2NF and still broken

Hard
Design a five-column table whose only candidate key is one column, so that it is in 2NF by the definition in this lesson, and on which all three anomalies still happen. Give the rows, the dependencies that hold, the exact statement that triggers each anomaly, and the two-table split that stops all three. Finish with one sentence naming the property your table has that the 2NF test never looks at.
Follow-up
The single-column key is not a loophole you are exploiting, it is the constraint you are working under: it makes 2NF pass outright, so the redundancy has to come from somewhere the 2NF test cannot reach. Your split still follows exactly the rule this lesson gave you.
Show the hint
Store a fact about something your key does not identify, in a row keyed by something it does.