{"id":"cedd97e5-31f5-4792-9433-dbdcc947dee3","shortId":"6ejPYp","kind":"skill","title":"Database Schema Reviewer","tagline":"Reviews database schemas for normalization issues, missing indexes, naming inconsistencies, and scalability risks.","description":"# Database Schema Reviewer\n\n## What this skill does\n\nThis skill directs the agent to review a database schema — provided as SQL DDL, an ORM model file (Drizzle, Prisma, SQLAlchemy, ActiveRecord, etc.), or a plain description — and produce a prioritized list of issues. It checks normalization, indexes, constraints, naming conventions, nullable columns, and overall scalability. Every issue includes a concrete SQL or ORM fix.\n\nUse this before deploying a new schema to production, during code review of a migration file, or when a database is growing and you're starting to feel query pain.\n\n## How to use\n\n### Claude Code / Cline\n\nCopy this file to `.agents/skills/database-schema-reviewer/SKILL.md` in your project root.\n\nThen share your schema and ask:\n- *\"Use the Database Schema Reviewer skill on `shared/schema.ts`.\"*\n- *\"Review this SQL migration file for schema issues using the Database Schema Reviewer skill.\"*\n\nProvide the full schema file, a SQL DDL dump, or paste the relevant CREATE TABLE statements.\n\n### Cursor\n\nAdd the \"Prompt / Instructions\" section to your `.cursorrules` file. Open your schema or migration file and ask Cursor to review it.\n\n### Codex\n\nPaste the schema DDL or ORM model definitions into the chat along with the instructions below. Include any known query patterns if you want index recommendations tailored to your workload.\n\n## The Prompt / Instructions for the Agent\n\nWhen asked to review a database schema, follow these steps:\n\n1. **Parse the schema.** Accept any format:\n   - SQL DDL (`CREATE TABLE` statements)\n   - Prisma schema (`model` blocks)\n   - Drizzle ORM table definitions\n   - SQLAlchemy/Django/ActiveRecord model classes\n   - A plain-text description of tables and columns\n\n2. **Check normalization:**\n   - **1NF:** Flag any column that stores multiple values (comma-separated lists, JSON arrays used as a substitute for a proper relation)\n   - **2NF:** In tables with composite primary keys, flag non-key columns that depend on only part of the key\n   - **3NF:** Flag transitive dependencies — non-key columns that describe another non-key column rather than the primary key (e.g., `zip_code` and `city` in the same table as `user_id`)\n\n3. **Check indexes:**\n   - Foreign key columns almost always need an index — flag any FK column without one\n   - Columns frequently used in `WHERE`, `ORDER BY`, or `JOIN` conditions should be indexed — infer from column names (e.g., `email`, `status`, `created_at`, `user_id`)\n   - Unique constraints are implicit indexes, but flag columns that should be unique but aren't (e.g., `email` in a users table)\n   - Flag any table with no index beyond its primary key if it's likely to be queried by non-PK columns\n\n4. **Check constraints:**\n   - Flag foreign key relationships that exist logically but have no `REFERENCES` constraint\n   - Flag columns that should have `NOT NULL` but are nullable: primary-purpose columns like `email`, `name`, `created_at`\n   - Flag columns that should have a `DEFAULT` value but don't (e.g., `created_at`, boolean flags)\n   - Flag missing `ON DELETE` / `ON UPDATE` behavior on foreign keys\n\n5. **Check naming conventions:**\n   - Table names should be consistent: all lowercase_snake_case plural, or all PascalCase — flag inconsistencies\n   - Column names should be consistent: all lowercase_snake_case — flag camelCase or mixed styles\n   - Primary keys should consistently be named `id` (or `table_id`) — flag non-standard PK names\n   - Foreign keys should be named `referenced_table_id` — flag deviations\n   - Boolean columns should be named with `is_`, `has_`, or `can_` prefix — flag ambiguous names like `active` or `enabled`\n\n6. **Flag scalability risks:**\n   - Using `TEXT` or `VARCHAR(MAX)` for columns that are used in `WHERE` clauses or indexes\n   - Storing large blobs (images, documents) in the database instead of referencing an object store\n   - Tables that are likely to grow very large with no partitioning strategy mentioned\n   - Using `ENUM` types in PostgreSQL/MySQL where the values change frequently (painful to alter)\n   - `SELECT *` traps: wide tables with many columns that are rarely all needed together (suggest splitting)\n\n7. **Suggest missing tables or relationships** if the schema implies data that has no home (e.g., a `role` column on users with no `roles` table, suggesting future extensibility issues).\n\n8. **Prioritize findings:**\n   - **High** — data integrity risk or will cause query failures at scale\n   - **Medium** — performance risk or maintainability issue\n   - **Low** — style/convention issue\n\n9. **Output format:**\n\n```\n## Schema Review Results\n\n### Summary\n[X] High · [Y] Medium · [Z] Low findings across [N] tables\n\n---\n\n### High Priority\n\n#### [Issue title] — [Table.column]\n**Problem:** [Plain English explanation]\n**Risk:** [What goes wrong if this isn't fixed]\n**Fix:**\n```sql\n[concrete SQL or ORM fix]\n```\n\n---\n[repeat by priority level]\n\n### Passed Checks\n- [List areas that look good]\n```\n\n## Example\n\n**Input to Agent:**\n> \"Use the Database Schema Reviewer skill on this schema:\n>\n> ```sql\n> CREATE TABLE users (\n>   id SERIAL PRIMARY KEY,\n>   name VARCHAR(255),\n>   email VARCHAR(255),\n>   role VARCHAR(50),\n>   address TEXT,\n>   city VARCHAR(100),\n>   zip VARCHAR(20),\n>   created TIMESTAMP\n> );\n>\n> CREATE TABLE orders (\n>   order_id SERIAL PRIMARY KEY,\n>   userId INTEGER,\n>   items TEXT,\n>   status VARCHAR(50),\n>   total DECIMAL(10,2)\n> );\n> ```\"\n\n**Output from Agent:**\n> ## Schema Review Results\n>\n> ### Summary\n> 4 High · 3 Medium · 2 Low findings across 2 tables\n>\n> ---\n>\n> ### High Priority\n>\n> #### Missing UNIQUE constraint on users.email\n> **Problem:** Email is the primary identifier for login but has no unique constraint. Duplicate accounts can be created.\n> **Risk:** Authentication bugs, data corruption, duplicate account merging nightmare.\n> **Fix:**\n> ```sql\n> ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);\n> CREATE INDEX idx_users_email ON users (email);\n> ```\n>\n> #### orders.userId has no foreign key constraint\n> **Problem:** `userId` references the `users` table logically but has no `REFERENCES` constraint. Orphaned orders with deleted users will exist silently.\n> **Risk:** Referential integrity violations, broken order history views.\n> **Fix:**\n> ```sql\n> ALTER TABLE orders ADD CONSTRAINT fk_orders_user\n>   FOREIGN KEY (userId) REFERENCES users(id) ON DELETE RESTRICT;\n> CREATE INDEX idx_orders_user_id ON orders (userId);\n> ```\n>\n> #### orders.items stores multiple values in a TEXT column (1NF violation)\n> **Problem:** Storing order items as a text blob violates first normal form and makes querying individual items impossible without string parsing.\n> **Risk:** Impossible to query \"which orders contain product X\", no referential integrity on items.\n> **Fix:** Create an `order_items` table:\n> ```sql\n> CREATE TABLE order_items (\n>   id SERIAL PRIMARY KEY,\n>   order_id INTEGER NOT NULL REFERENCES orders(order_id) ON DELETE CASCADE,\n>   product_id INTEGER NOT NULL,\n>   quantity INTEGER NOT NULL DEFAULT 1,\n>   unit_price DECIMAL(10,2) NOT NULL\n> );\n> ```\n>\n> ---\n>\n> ### Medium Priority\n>\n> #### users.city and users.zip are a transitive dependency (3NF violation)\n> **Problem:** City and zip code describe each other, not the user. Moving address data to an `addresses` table is cleaner for reuse and consistency.\n>\n> #### users.created should be NOT NULL with a DEFAULT\n> **Fix:** `created TIMESTAMP NOT NULL DEFAULT NOW()`\n>\n> #### Naming inconsistency: orders uses order_id and userId (mixed conventions)\n> **Fix:** Rename to `id` and `user_id` to match PostgreSQL snake_case convention.\n>\n> ---\n>\n> ### Passed Checks\n> - Primary keys are present on both tables\n> - DECIMAL used correctly for monetary values (not FLOAT)","tags":["database","schema","reviewer","openagentskills","notysoty","agent-skills","claude","claude-code","claude-skills","cline","cursor","llm"],"capabilities":["skill","source-notysoty","skill-database-schema-reviewer","topic-agent-skills","topic-claude","topic-claude-code","topic-claude-skills","topic-cline","topic-cursor","topic-llm","topic-llm-skills","topic-skills"],"categories":["openagentskills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/Notysoty/openagentskills/database-schema-reviewer","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add Notysoty/openagentskills","source_repo":"https://github.com/Notysoty/openagentskills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (7,767 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:13:21.234Z","embedding":null,"createdAt":"2026-05-18T13:20:41.821Z","updatedAt":"2026-05-18T19:13:21.234Z","lastSeenAt":"2026-05-18T19:13:21.234Z","tsv":"'1':237,1020 '10':803,1024 '100':780 '1nf':272,946 '2':269,804,816,820,1025 '20':783 '255':769,772 '2nf':294 '3':346,814 '3nf':314,1037 '4':430,812 '5':490 '50':775,800 '6':567 '7':641 '8':670 '9':693 'accept':241 'account':843,853 'across':707,819 'activ':564 'activerecord':45 'add':169,861,915 'address':776,1051,1055 'agent':28,226,749,807 'agents/skills/database-schema-reviewer/skill.md':119 'almost':352 'along':202 'alter':625,858,912 'alway':353 'ambigu':561 'anoth':324 'area':742 'aren':400 'array':285 'ask':129,185,228 'authent':848 'behavior':486 'beyond':414 'blob':588,955 'block':252 'boolean':478,549 'broken':906 'bug':849 'camelcas':519 'cascad':1009 'case':502,517,1099 'caus':679 'chang':621 'chat':201 'check':59,270,347,431,491,740,1102 'citi':338,778,1040 'class':259 'claud':112 'claus':583 'cleaner':1058 'cline':114 'code':89,113,336,1043 'codex':190 'column':66,268,275,305,321,328,351,360,363,378,394,429,446,458,465,509,550,577,632,659,945 'comma':281 'comma-separ':280 'composit':298 'concret':74,730 'condit':372 'consist':498,513,526,1062 'constraint':62,388,432,444,826,841,862,881,893,916 'contain':975 'convent':64,493,1087,1100 'copi':115 'correct':1112 'corrupt':851 'creat':165,246,383,462,476,760,784,786,846,868,929,984,990,1072 'cursor':168,186 'cursorrul':176 'data':651,674,850,1052 'databas':1,5,17,32,98,132,148,232,593,752 'ddl':37,159,194,245 'decim':802,1023,1110 'default':470,1019,1070,1076 'definit':198,256 'delet':483,897,927,1008 'depend':307,317,1036 'deploy':82 'describ':323,1044 'descript':50,264 'deviat':548 'direct':26 'document':590 'drizzl':42,253 'dump':160 'duplic':842,852 'e.g':334,380,402,475,656 'email':381,403,460,770,830,864,867,872,875 'enabl':566 'english':717 'enum':614 'etc':46 'everi':70 'exampl':746 'exist':438,900 'explan':718 'extens':668 'failur':681 'feel':106 'file':41,94,117,142,156,177,183 'find':672,706,818 'first':957 'fix':78,727,728,734,856,910,983,1071,1088 'fk':359,917 'flag':273,301,315,357,393,408,433,445,464,479,480,507,518,533,547,560,568 'float':1117 'follow':234 'foreign':349,434,488,539,879,920 'form':959 'format':243,695 'frequent':364,622 'full':154 'futur':667 'goe':721 'good':745 'grow':100,605 'high':673,701,710,813,822 'histori':908 'home':655 'id':345,386,529,532,546,763,790,925,934,994,999,1006,1011,1083,1091,1094 'identifi':834 'idx':870,931 'imag':589 'impli':650 'implicit':390 'imposs':965,970 'includ':72,207 'inconsist':13,508,1079 'index':11,61,215,348,356,375,391,413,585,869,930 'individu':963 'infer':376 'input':747 'instead':594 'instruct':172,205,223 'integ':795,1000,1012,1016 'integr':675,904,980 'isn':725 'issu':9,57,71,145,669,689,692,712 'item':796,951,964,982,987,993 'join':371 'json':284 'key':300,304,313,320,327,333,350,417,435,489,524,540,766,793,880,921,997,1104 'known':209 'larg':587,607 'level':738 'like':421,459,563,603 'list':55,283,741 'logic':439,888 'login':836 'look':744 'low':690,705,817 'lowercas':500,515 'maintain':688 'make':961 'mani':631 'match':1096 'max':575 'medium':684,703,815,1028 'mention':612 'merg':854 'migrat':93,141,182 'miss':10,481,643,824 'mix':521,1086 'model':40,197,251,258 'monetari':1114 'move':1050 'multipl':278,940 'n':708 'name':12,63,379,461,492,495,510,528,538,543,553,562,767,1078 'need':354,637 'new':84 'nightmar':855 'non':303,319,326,427,535 'non-key':302,318,325 'non-pk':426 'non-standard':534 'normal':8,60,271,958 'null':451,1002,1014,1018,1027,1067,1075 'nullabl':65,454 'object':598 'one':362 'open':178 'order':368,788,789,895,907,914,918,932,936,950,974,986,992,998,1004,1005,1080,1082 'orders.items':938 'orders.userid':876 'orm':39,77,196,254,733 'orphan':894 'output':694,805 'overal':68 'pain':108,623 'pars':238,968 'part':310 'partit':610 'pascalcas':506 'pass':739,1101 'past':162,191 'pattern':211 'perform':685 'pk':428,537 'plain':49,262,716 'plain-text':261 'plural':503 'postgresql':1097 'postgresql/mysql':617 'prefix':559 'present':1106 'price':1022 'primari':299,332,416,456,523,765,792,833,996,1103 'primary-purpos':455 'priorit':54,671 'prioriti':711,737,823,1029 'prisma':43,249 'problem':715,829,882,948,1039 'produc':52 'product':87,976,1010 'project':122 'prompt':171,222 'proper':292 'provid':34,152 'purpos':457 'quantiti':1015 'queri':107,210,424,680,962,972 'rare':635 'rather':329 're':103 'recommend':216 'refer':443,884,892,923,1003 'referenc':544,596 'referenti':903,979 'relat':293 'relationship':436,646 'relev':164 'renam':1089 'repeat':735 'restrict':928 'result':698,810 'reus':1060 'review':3,4,19,30,90,134,138,150,188,230,697,754,809 'risk':16,570,676,686,719,847,902,969 'role':658,664,773 'root':123 'scalabl':15,69,569 'scale':683 'schema':2,6,18,33,85,127,133,144,149,155,180,193,233,240,250,649,696,753,758,808 'section':173 'select':626 'separ':282 'serial':764,791,995 'share':125 'shared/schema.ts':137 'silent':901 'skill':22,25,135,151,755 'skill-database-schema-reviewer' 'snake':501,516,1098 'source-notysoty' 'split':640 'sql':36,75,140,158,244,729,731,759,857,911,989 'sqlalchemi':44 'sqlalchemy/django/activerecord':257 'standard':536 'start':104 'statement':167,248 'status':382,798 'step':236 'store':277,586,599,939,949 'strategi':611 'string':967 'style':522 'style/convention':691 'substitut':289 'suggest':639,642,666 'summari':699,811 'tabl':166,247,255,266,296,342,407,410,494,531,545,600,629,644,665,709,761,787,821,859,887,913,988,991,1056,1109 'table.column':714 'tailor':217 'text':263,572,777,797,944,954 'timestamp':785,1073 'titl':713 'togeth':638 'topic-agent-skills' 'topic-claude' 'topic-claude-code' 'topic-claude-skills' 'topic-cline' 'topic-cursor' 'topic-llm' 'topic-llm-skills' 'topic-skills' 'total':801 'transit':316,1035 'trap':627 'type':615 'uniqu':387,398,825,840,865,866 'unit':1021 'updat':485 'use':79,111,130,146,286,365,571,580,613,750,1081,1111 'user':344,385,406,661,762,860,863,871,874,886,898,919,924,933,1049,1093 'userid':794,883,922,937,1085 'users.city':1030 'users.created':1063 'users.email':828 'users.zip':1032 'valu':279,471,620,941,1115 'varchar':574,768,771,774,779,782,799 'view':909 'violat':905,947,956,1038 'want':214 'wide':628 'without':361,966 'workload':220 'wrong':722 'x':700,977 'y':702 'z':704 'zip':335,781,1042","prices":[{"id":"8d70a63d-c006-4645-930e-7d11e363283a","listingId":"cedd97e5-31f5-4792-9433-dbdcc947dee3","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"Notysoty","category":"openagentskills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:20:41.821Z"}],"sources":[{"listingId":"cedd97e5-31f5-4792-9433-dbdcc947dee3","source":"github","sourceId":"Notysoty/openagentskills/database-schema-reviewer","sourceUrl":"https://github.com/Notysoty/openagentskills/tree/main/skills/database-schema-reviewer","isPrimary":false,"firstSeenAt":"2026-05-18T13:20:41.821Z","lastSeenAt":"2026-05-18T19:13:21.234Z"}],"details":{"listingId":"cedd97e5-31f5-4792-9433-dbdcc947dee3","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"Notysoty","slug":"database-schema-reviewer","github":{"repo":"Notysoty/openagentskills","stars":8,"topics":["agent-skills","claude","claude-code","claude-skills","cline","cursor","llm","llm-skills","skills"],"license":"mit","html_url":"https://github.com/Notysoty/openagentskills","pushed_at":"2026-03-28T06:50:19Z","description":"A  community-driven library of reusable AI agent skills for Claude Code, Cursor, Codex, Cline, and more.","skill_md_sha":"a7bd26cbb529b7a810fc28e0fb4be30ed29d33d7","skill_md_path":"skills/database-schema-reviewer/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/Notysoty/openagentskills/tree/main/skills/database-schema-reviewer"},"layout":"multi","source":"github","category":"openagentskills","frontmatter":{"name":"Database Schema Reviewer","description":"Reviews database schemas for normalization issues, missing indexes, naming inconsistencies, and scalability risks."},"skills_sh_url":"https://skills.sh/Notysoty/openagentskills/database-schema-reviewer"},"updatedAt":"2026-05-18T19:13:21.234Z"}}