Generate safe, correct SQL scripts for the Rock RMS database. Handles INSERT, UPDATE, DELETE, SELECT queries, data migrations, and seed scripts that respect Rock's schema conventions (PersonAlias, audit columns, FK constraints, DefinedType/DefinedValue lookups, Guid-based references). Use when the user says "create a sql script", "write sql", "insert data", "seed data", "data migration", "update records", "sql for Rock", "populate data", "add test data", "query Rock database", or any request involving direct SQL against the Rock RMS database. Also use when the user describes data they want to add, modify, or query in Rock — even if they don't explicitly say "SQL" — such as "add 100 attendance records", "create a new campus", "give Ted Decker some financial transactions", or "set up check-in data". If the task involves Rock database records and SQL is the right tool, use this skill.
You are generating SQL scripts that run against the Rock RMS database. Rock has specific schema conventions that, if ignored, cause FK constraint violations, missing audit trails, or broken data. This skill ensures you get it right.
The user's request: $ARGUMENTS
Load reference files progressively — only when needed.
| Reference File | Load When |
|---|---|
references/schema-patterns.md | Before writing any INSERT or UPDATE (always) |
references/sample-data.md | When the script references people, families, or groups |
Do NOT read both files upfront. Read schema-patterns.md for every script; read sample-data.md only when people are involved.
Parse what the user wants:
If the request is ambiguous, ask clarifying questions before writing SQL.
Before writing any SQL, read the relevant Entity Framework model files to understand the actual columns, data types, required fields, and FK relationships. This is critical because Rock's schema evolves and assumptions lead to errors.
How to find model files:
Rock/Model/[Domain]/[Entity]/[Entity].csRock/Model/**/{EntityName}.csRock.Enums/[Domain]/Rock/SystemGuid/{EntityType}.csWhat to look for in model files:
[Required] attributes — these columns cannot be NULL[MaxLength(N)] — string length limitspublic virtual PersonAlias CreatedByPersonAlias)Read references/schema-patterns.md for the standard column patterns that apply to every Rock entity.
Follow these rules strictly:
SELECT, FROM, WHERE, JOIN, INSERT, UPDATE, DELETE, DECLARE, SET, BEGIN, END[Person].[FirstName]JOIN syntax, not WHERE clauses for joins[Person] AS [p], [GroupMember] AS [gm]Every INSERT must include:
[Guid] — use NEWID() unless a specific Guid is required[IsSystem] — 0 for user-created data, 1 only for Rock core data[CreatedDateTime] = GETDATE()[ModifiedDateTime] = GETDATE()[CreatedByPersonAliasId] = looked up from a known person (use admin or sample data person)[ModifiedByPersonAliasId] = same as abovePersonAlias vs Person — this is the most common mistake:
CreatedByPersonAliasId, ModifiedByPersonAliasId) reference [PersonAlias], NOT [Person]SELECT [Id] FROM [PersonAlias] WHERE [PersonId] = @PersonId AND [AliasPersonId] = @PersonIdLookups by Guid (preferred over hardcoded Ids):
DECLARE @CampusId INT = (SELECT TOP 1 [Id] FROM [Campus] WHERE [Guid] = 'some-guid-here')
DECLARE @DefinedValueId INT = (SELECT TOP 1 [Id] FROM [DefinedValue] WHERE [Guid] = 'some-guid-here')
Safety guards:
-- For idempotent scripts (can be re-run safely)
IF NOT EXISTS (SELECT 1 FROM [TableName] WHERE [Guid] = 'your-guid')
BEGIN
INSERT INTO [TableName] (...) VALUES (...)
END
Transactions for multi-statement scripts:
BEGIN TRANSACTION
-- Your statements here
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
COMMIT TRANSACTION
Whether to use existing sample data people or create new ones depends on context. Use your judgment:
Use existing sample data people when:
Create new people when:
When creating new people, remember to also create the PersonAlias and add them to a Family group — an orphaned Person record causes problems in Rock.
Read references/sample-data.md for the full list of available sample people, their GUIDs, and family structures.
Looking up existing people — prefer Guid over hardcoded Ids:
-- By Guid from sample data
DECLARE @TedDeckerPersonId INT = (SELECT TOP 1 [Id] FROM [Person] WHERE [Guid] = '8FEDC6EE-8630-41ED-9FC5-C7157FD1EAA4')
DECLARE @TedDeckerPersonAliasId INT = (SELECT TOP 1 [Id] FROM [PersonAlias] WHERE [PersonId] = @TedDeckerPersonId AND [AliasPersonId] = @TedDeckerPersonId)
-- By name if Guid isn't critical
DECLARE @PersonId INT = (SELECT TOP 1 [Id] FROM [Person] WHERE [FirstName] = 'Ted' AND [LastName] = 'Decker')
-- Grab a set of existing people for bulk data generation
SELECT TOP 10 [Id] FROM [Person] WHERE [IsDeceased] = 0 AND [RecordStatusValueId] = (SELECT [Id] FROM [DefinedValue] WHERE [Guid] = '618F906C-C33D-4FA3-8AEF-E58CB7B63F1E') ORDER BY [Id]
Insert a DefinedValue:
DECLARE @DefinedTypeId INT = (SELECT TOP 1 [Id] FROM [DefinedType] WHERE [Guid] = '{guid}')
DECLARE @MaxOrder INT = (SELECT ISNULL(MAX([Order]), 0) FROM [DefinedValue] WHERE [DefinedTypeId] = @DefinedTypeId)
INSERT INTO [DefinedValue] ([IsSystem], [DefinedTypeId], [Order], [Value], [Description], [IsActive], [Guid], [CreatedDateTime], [ModifiedDateTime])
VALUES (0, @DefinedTypeId, @MaxOrder + 1, 'New Value', 'Description here', 1, NEWID(), GETDATE(), GETDATE())
Add a person to a group:
DECLARE @GroupId INT = (SELECT TOP 1 [Id] FROM [Group] WHERE [Guid] = '{group-guid}')
DECLARE @GroupTypeId INT = (SELECT TOP 1 [GroupTypeId] FROM [Group] WHERE [Id] = @GroupId)
DECLARE @DefaultRoleId INT = (SELECT TOP 1 [Id] FROM [GroupTypeRole] WHERE [GroupTypeId] = @GroupTypeId AND [IsDefaultGroupTypeRole] = 1)
DECLARE @PersonId INT = (SELECT TOP 1 [Id] FROM [Person] WHERE [Guid] = '{person-guid}')
IF NOT EXISTS (SELECT 1 FROM [GroupMember] WHERE [GroupId] = @GroupId AND [PersonId] = @PersonId AND [IsArchived] = 0)
BEGIN
INSERT INTO [GroupMember] ([IsSystem], [GroupId], [PersonId], [GroupRoleId], [GroupMemberStatus], [Guid], [CreatedDateTime], [ModifiedDateTime], [DateTimeAdded], [IsNotified], [IsArchived], [CommunicationPreference], [GroupTypeId])
VALUES (0, @GroupId, @PersonId, @DefaultRoleId, 1, NEWID(), GETDATE(), GETDATE(), GETDATE(), 0, 0, 0, @GroupTypeId)
END
Set an attribute value:
DECLARE @AttributeId INT = (SELECT TOP 1 [Id] FROM [Attribute] WHERE [Key] = 'AttributeKey' AND [EntityTypeId] = (SELECT [Id] FROM [EntityType] WHERE [Name] = 'Rock.Model.Person'))
DECLARE @EntityId INT = (SELECT TOP 1 [Id] FROM [Person] WHERE [Guid] = '{person-guid}')
IF EXISTS (SELECT 1 FROM [AttributeValue] WHERE [AttributeId] = @AttributeId AND [EntityId] = @EntityId)
BEGIN
UPDATE [AttributeValue]
SET [Value] = 'new value', [ModifiedDateTime] = GETDATE()
WHERE [AttributeId] = @AttributeId AND [EntityId] = @EntityId
END
ELSE
BEGIN
INSERT INTO [AttributeValue] ([IsSystem], [AttributeId], [EntityId], [Value], [Guid], [CreatedDateTime], [ModifiedDateTime])
VALUES (0, @AttributeId, @EntityId, 'new value', NEWID(), GETDATE(), GETDATE())
END
Run through this checklist before giving the script to the user:
[Guid] with NEWID() (or a specific Guid if required)[CreatedDateTime] and [ModifiedDateTime][IsSystem] is set to 0 unless this is core Rock data[MaxLength] from the modelNOT NULL) columns are populated[Order] columns are calculated, not hardcoded to 0Write the SQL script to a .sql file in the Dev Tools/Sql/ directory. Follow the existing naming conventions:
| Prefix | Use When |
|---|---|
Populate_ | Seeding/generating test data (e.g., Populate_FinancialTransactions_Contributions.sql) |
CodeGen_ | Generating migration code or system data (e.g., CodeGen_SystemGuid_DefinedValue.sql) |
Tool_ | Utility/maintenance scripts (e.g., Tool_DeletePeople.sql) |
View_ | Creating or querying views (e.g., View_PersonAttributeValues.sql) |
Report_ | Reporting queries (e.g., Report_OrphanedAttributes.sql) |
Enable_ | Enabling features or settings (e.g., Enable_FacebookLogin.sql) |
Use PascalCase with underscores between words in the filename.
File contents should include:
DECLARE statements for all lookups at the topSET NOCOUNT ON
-- =============================================
-- Description: [What this script does]
-- Date: [Current date]
-- =============================================
-- Lookups
DECLARE @SomeId INT = (...)
-- Main operations
BEGIN TRANSACTION
...
COMMIT TRANSACTION
-- Verify
SELECT * FROM [TableName] WHERE [Guid] = '...'
Also show the script contents in the conversation so the user can review it without opening the file.
User says: "Create some financial transactions for the sample data people"
Actions:
references/schema-patterns.md and references/sample-data.mdRock/Model/Finance/FinancialTransaction/FinancialTransaction.cs and Rock/Model/Finance/FinancialTransactionDetail/FinancialTransactionDetail.cs for required columnsDev Tools/Sql/Populate_FinancialTransactions_SampleData.sqlResult: A runnable .sql file that creates realistic transactions tied to existing sample people.
User says: "Add a new campus called North Campus"
Actions:
references/schema-patterns.mdRock/Model/Core/Campus/Campus.cs for required columnsDev Tools/Sql/Populate_Campus_NorthCampus.sqlResult: An idempotent script that adds a new Campus record with all required fields.
User says: "Add a new family — the Johnsons — with two adults and a child, and give them some attendance history"
Actions:
references/schema-patterns.mdDev Tools/Sql/Populate_JohnsonFamily_WithAttendance.sqlResult: A transactional script that creates a complete family with attendance data, no orphaned records.
FK constraint violation: You're referencing an Id that doesn't exist. Check your DECLARE lookups — did the subquery return NULL? Add a NULL check: IF @SomeId IS NOT NULL BEGIN ... END
Cannot insert NULL: You missed a required column. Read the model file and look for [Required] attributes.
String truncation: Your string value exceeds the column's [MaxLength]. Check the model.
Duplicate key: Your INSERT conflicts with a unique constraint (usually on [Guid] or a composite key). Use IF NOT EXISTS.