At some point in their lives, projects begin to require support and testing. To do this, in most cases, they come to the distribution of assemblies on dev - stage - prod, where each element is a polygon with its own version of the code.
dev: developer's unstable polygon, for development. Errors, temporary offline, manual edits and so on are allowed.
stage: pre-prod polygon, for testing and error detection. Errors allowed, offline not allowed, fully automated roll-out using CI.
prod: "battle" polygon. Users come here. Its fall and found bugs are the reason for the decrease in bonuses for programmers / testers / devops.
Accordingly, each polygon has its own database, its own environment, and so on. Typically, this is a collection of separate virtual machines. There can be several polygons of each type, depending on the goals, it is allowed to raise a sub-dev-polygon using containerization directly on the developer's machine.
Also, for each polygon in the repository, its own branch is created, the commit to which should initialize the automatic deployment of the fresh release to the polygon. With prod, as a rule, they do not do anything, but I think this is purely for personal reasons.
I had a similar situation in one of the projects. A separate test database is created before starting the tests and the structure of the prod database is copied into it and filled with fake data. Then it is removed.
Tests use a mock object to work with a database, where a test database is substituted instead of a prod.
Sometimes you need to run non-destructive tests on existing records in a prod or stage database; or if it is not convenient to update the test data - you can switch the used database.
Never try to unify something without having a unified thing. Refactoring or optimization is done only if there is working code to compare the results. In your case, write a few examples by hand and then try to find a common denominator. The transition should always be from simple to complex.