I would probably just do this the brute force way, and add indexes to support these joins where they don't exist. Not much gain to treating new customers and old customers any different once you've inserted all the customers that don't already exist:
INSERT dbo.Customer(fname, lname, address, city, state, zip, email)
SELECT fname, lname, address, city, state, zip, email
FROM dbo.job AS j
WHERE job_no = @job_no
AND NOT EXISTS
(
SELECT 1 FROM dbo.Customer
WHERE fname = j.fname
AND lname = j.lname
AND (address = j.address OR email = j.email)
);
INSERT INTO dbo.personal_code (customer_id, mailing_id, personal_code, email)
SELECT c.customer_id, j.mailing_id, j.personal_code, c.email
FROM dbo.Customer AS c
INNER JOIN dbo.job AS j
ON c.fname = j.fname AND c.lname = j.lname
AND (c.address = j.address OR c.email = j.email)
WHERE j.job_no = @job_no;
INSERT dbo.personal_code_extra(personal_code_id, extra)
SELECT pc.personal_code_id, j.extra
FROM dbo.personal_code AS pc
INNER JOIN dbo.Customer AS c
ON pc.customer_id = c.customer_id
INNER JOIN dbo.job AS j
ON c.fname = j.fname AND c.lname = j.lname
AND (c.address = j.address OR c.email = j.email)
WHERE j.job_no = @job_no;
How big is this database? How many rows are in each table? Etc?
I would say that normalized data is default state to try to obtain. It is a leaner database, rows are shorter, and indexes may be used more effectively. The short, leaner rows therefore lead to a smaller, leaner database.
One of the major accelerators of performance is memory. If you can get your 5 tables to remain cached in memory, that will be a performance accelerator for your queries since you will avoid much of the disk I/O overhead.
You identify that you are joining with IDs (which are usually integers), so your indexes may be narrow and offer relatively inexpensive joins.
If you decide to denormalize, your tables will be bigger because they are carrying more redundant data on every row. This causes a need for more memory to keep the data in cache and will require even more I/O when the cache is insufficient to buffer the data. (And your backups are bigger.)
In addition, you have taken on the task to denormalize and to maintain the denormalized data. This is an extra load of programming and on the server as well: consuming memory, I/O, and CPU.
But sometimes denormalization is the best choice. Data Warehouses, for example, are largely denormalized data. Also, you may find that in your system the benefits of denormalization may exceed the cost.
Still, you are asking a forum for an answer.
Your best answer would come by building normalized test case and seeing how it works. Even though you may not have a lot of 'real data', you should generate a fairly large data set in the millions of rows to test with.
You can try to find a tool that does it for you (RedGate has one for example, but it is not free) or generate the data yourself so that you control the complexity. There are online sources of states, cities, et cetera, and you can make up call centers, generate landline numbers and so forth.
Then try it.
If you do not like the performance, then create a denormalized table to test. And put some effort into writing the code to maintain the denormalization, since that will become integral to your process.
Best Answer
I think this will do it
I changed this answer as I may have read the question incorrectly
Look at edit history if you are looking for something different
as for the fk
run above for addition fk