I have an Excel file that I imported into a table (with customerID, Customer name, accountID, Address, and accountmanagerID) in my SQL Server database.
Now I want to split the data into different tables, for customers, accounts, address, and managers.
The issue is that some customers have more then 5 accounts with different managers others with 2 different account with the same manager.
If I use the stored procedure shown here, I can only store one customer with one account.
But what I want to do is to store one customer with all the different accounts related to him.
This is my stored procedure:
ALTER PROCEDURE [dbo].[sp_Import_Client]
AS
BEGIN
TRUNCATE TABLE Customers
TRUNCATE TABLE Addresses
TRUNCATE TABLE Accounts
TRUNCATE TABLE Managers
DECLARE NewClient CURSOR LOCAL FAST_FORWARD READ_ONLY
FOR
SELECT
Distinct(CustomerName), --CustomerName
AccountID , --AccountNo
Address,
ManagerName -- manager
FROM
OldDetails
DECLARE #CustomerName VARCHAR(255)
DECLARE #Contact VARCHAR(300)
DECLARE #AccountNo VARCHAR(20)
DECLARE #Address1 VARCHAR(300)
DECLARE #CustomerID INT
OPEN NewClient
FETCH FROM NewClient INTO #CustomerName, #Manager, #AccountNo, #Address1
WHILE (##FETCH_STATUS = 0)
BEGIN
--- 1 Insert Customer
INSERT INTO Customers (CustomerName)
VALUES (#CustomerName)
---Set the CustomerID to continue with stage 2
SET #CustomerID = SCOPE_IDENTITY()
--- 2 Insert CustomerAddresses
INSERT INTO Address (CustomerID, Address1)
VALUES (#CustomerID, #Address1)
---3 Insert Managers
INSERT INTO Managers (CustomerID, Name)
VALUES (#CustomerID, #Contact)
---4 Insert Account
INSERT INTO Accounts(CustomerID, AccountNo, Manager)
VALUES (#CustomerID, #AccountNo, #manager)
FETCH NEXT FROM NewClient INTO #CustomerName, #Manager, #AccountNo, #Address1
END
CLOSE NewClient
DEALLOCATE NewClient
END
I think you might need to change the step 1 in your procedure to check if the customer already exists, and if it does, use it instead of inserting a new row for it. You can use the code block below instead of your INSERT statement:
-- SET THE #CustomerID
SET #CustomerID = NULL
SELECT #CustomerID = ID
FROM Customers
WHERE CustomerName = #CustomerName
IF #CustomerID IS NULL
BEGIN
--- 1 Insert Customer
INSERT INTO Customers (CustomerName)
VALUES (#CustomerName)
---Set the CustomerID to continue with stage 2
SET #CustomerID = SCOPE_IDENTITY()
END
This way, you can save only 1 customer record, and multiple rows for managers / accounts if needed.
Hope it makes sense.
EDIT
By the way, a small observation, you dont need to specify FAST_FORWARD and READ_ONLY together because FAST_FORWARD implies READ_ONLY and FORWARD_ONLY, so FAST_FORWARD will do it.
Related
I have an old table with lots of columns that i want to split into 3 tables with many to many relation.
The old table have no identity column.
Old table:
CustomerNumber
FirstName
LastName
Address
Postal
City
....
New tables
Customer:
Id
Customernumber
Firstname
Lastname
... ect
Address:
Id
Address
Postal
City
... ect
CustomerAddress
Id
CustomerId
AddressId
Now how can I spilt the old tabel into the new ones using SQL?
I have tried with MERGE but that can't handle more than one table at the time. One option is using CURSOR, but I read that it is a bad idea, to use that or iteration, but for now that is the only solution I have found for this.
declare
#CustomerId bigint,
#CustomerNumber float,
#Status int,
#Address varchar(50),
#RoadNumber int,
#LastEdited datetime,
#AddressId bigint
declare my_cursor cursor
local static read_only forward_only
for
select CustomerNumber, Address, Housenumber, Status, Date
from [db1].dbo.OldCustomer k
where
FIRMANR in (1, 40, 60, 80, 90, 120, 180, 400)
open my_cursor
fetch next from my_cursor into #CustomerNumber, #Address, #RoadNumber, #Status, #LastEdited
while ##FETCH_STATUS = 0
begin
--if the customer already exists we get the identity
if exists (select Id from [db2].dbo.Customers where CustomerNumber = #CustomerNumber)
select #CustomerId = Id from [db2].dbo.Customers where CustomerNumber = #CustomerNumber
--if the customer does not exit we need to insert and retrieve the new Identity value
else
begin
-- insert the customer
insert into [db2].dbo.Customers (CustomerNumber, [Status], LastEdited) values (#CustomerNumber, #Status, #LastEdited)
set #CustomerId = SCOPE_IDENTITY()
end
-- get address if it already exists
if exists (select Id from [db2].dbo.Addresses where Road = #Address and Roadnumber = #RoadNumber)
select #AddressId = Id from [db2].dbo.Addresses where Road = #Address and Roadnumber = #RoadNumber
else
begin
-- insert new addresses
insert into [db2].dbo.Addresses (Road,Roadnumber) values (#Address, #RoadNumber)
set #AddressId = SCOPE_IDENTITY()
end
-- insert customer => address reference if it does not exist
if not exists (select Id from [db2].dbo.CustomerAddress where CustomerId = #CustomerId and AddressId = #AddressId)
-- insert customer => address reference
insert into [db2].dbo.CustomerAddress(CustomerId,AddressId) values (#CustomerId, #AddressId)
fetch next from my_cursor into #CustomerNumber, #Address, #RoadNumber, #Status, #LastEdited
end
close my_cursor
deallocate my_cursor
It would probably be easiest to:
Add a column to old customer called AddressId
Populate it with unique IDs (you can reuse the Customer ID, or if its GUID, use NewID())
Create new tables for address and customeraddress
Insert part of the old customer data into each new table
Drop columns from Customer that relate to address
--populate new Address table
INSERT INTO Address(id,col1,col2...)
SELECT AddressID, col1, col2... FROM Customer
--populate new CustomerAddress table
INSERT INTO CustomerAddress(CustomerId,AddressId)
SELECT Id, AddressID FROM Customer
With SQLS you can do this as a simple script, with a transaction if you want.. No need for stored procedures, cursors, merge etc..
Don't give CustomerAddress its own Id column; the primary key of CustomerAddress is the combination of CustomerId and AddressId; make a composite PK, not a separate one
Tbh, I'd probably not have a CustomerAddress table and instead just have a BillingAddressId, WorkAddressId, HomeAddressId, ShippingAddressId column in customer, but it's up to you how to manage this; If you have lots and variable types of addresses then sure, have a M:M breakdown, but if realistically your customers are only ever going to have up to 3 addresses etc, I'd stick with having a named column stating what the address was for, in customer
If you do go with a CustomerAddress table, consider adding a column declaring the type/reason for the address
I need to record all previous addresses and postcodes when they are updated in the Customer table.
Business requirement: Addresses cannot be changed without also updating the postcode and vice versa.
A mechanism to prevent this is required along with appropriate error messages.
I already created the table:
create table tblCustomerAudit
(
CustomerID int identity(1,1) not null,
CustomerName nvarchar(255) null,
CustomerAddress nvarchar(255) null,
CustomerPostcode nvarchar(255) null,
CardNumber nvarchar(255) null,
)
go
alter table tblCustomerAudit
add constraint FK_CustomerAudit
foreign key(CustomerID)
references CstmrEng.tblCustomer(CustomerID)
What would trigger look like? please help!
Perhaps you can do this with a stored procedure? Just be aware that stored procedures are not magic pixie dust, and maintaining them can be a nightmare.
You could have your trigger call a stored procedure that handles the particular constraint between CustomerAddress and CustomerPostcode.
This code is untested and may very well not work.
CREATE PROCEDURE UpdateCustomerAddressAndPostcode #CustomerID INT, #CustomerAddress NVARCHAR(255), #CustomerPostcode NVARCHAR(255)
AS BEGIN
IF (#CustomerAddress IS NULL OR #CustomerAddress = '')
BEGIN
PRINT 'Customer address must be present to modify Customer table.';
THROW;
END
IF (#CustomerPostcode IS NULL OR #CustomerPostcode = '')
BEGIN
PRINT 'Customer postcode must be present to modify Customer table.';
THROW;
END
INSERT INTO tblCustomerAudit (CustomerID, CustomerName, CustomerAddress, CustomerPostcode, CardNumber)
SELECT CustomerID, CustomerName, CustomerAddress, CustomerPostcode, CardNumber FROM Customer where CustomerID = #CustomerID;
-- just printing the table for example
SELECT * FROM tblCustomerAudit
-- make the change to Customer here or in the trigger
END
GO
CREATE TRIGGER CustomerTrigger
ON [dbo].[Customer]
INSTEAD OF INSERT
AS
DECLARE #CustomerID int
DECLARE #CustomerAddress nvarchar(255)
DECLARE #CustomerPostcode nvarchar(255)
BEGIN
SET NOCOUNT ON;
-- verify the data
EXEC UpdateCustomerAddressAndPostcode #CustomerID, #CustomerAddress, #CustomerPostcode
-- do the Customer change here or in the stored procedure
END
I really recommend a store procedure to control the update, insert, and delete, so you'll use the store procedure to pass the values before it goes to the table. If you use a trigger, then the values will be actually changed, then the trigger will be fired, and from the trigger you'll have to re-update the table with the old values if your conditions met. So, this is a kind of redundancy for me, which is why I recommended a store procedure to handle everything before change the table values.
anyhow, you can still use triggers with the advantage of deleted and inserted tables :
CREATE TRIGGER CustomerUpdate ON tblCustomerAudit
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#CustomerID INT
, #New_CustomerAddress nvarchar(255)
, #New_CustomerPostcode nvarchar(255)
, #Old_CustomerAddress nvarchar(255)
, #Old_CustomerPostcode nvarchar(255)
SELECT
#CustomerID = CustomerID
, #Old_CustomerAddress = CustomerAddress
, #Old_CustomerPostcode = CustomerPostcode
FROM
deleted
SELECT
#New_CustomerAddress = CustomerAddress
, #New_CustomerPostcode = CustomerPostcode
FROM
tblCustomerAudit
WHERE
CustomerID = #CustomerID
IF #Old_CustomerAddress = #New_CustomerAddress OR #New_CustomerPostcode = #Old_CustomerPostcode
BEGIN
-- IF one of them matches return the old values
UPDATE tblCustomerAudit
SET
CustomerAddress = #Old_CustomerAddress
, CustomerPostcode = #Old_CustomerPostcode
WHERE
CustomerID = #CustomerID
-- display an error message
RAISERROR( 'You need to change both address and postcode to save the new values', 18 , 0);
END
END
Something like this:
CREATE TRIGGER foo.bar ON foo.mytable
AFTER UPDATE
AS
IF (##ROWCOUNT_BIG = 0)
RETURN;
IF EXISTS (SELECT *
FROM foo.mytable AS t
JOIN inserted AS i
ON t.mykey = i.mykey
WHERE i.addr <> t.addr AND i.post = t.post -- address changes but post code doesn't
OR i.post <> t.post AND i.addr = t.addr -- post code changes by address doesn't
)
BEGIN
RAISERROR ('invalid changes', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
GO
I am using SQL Server 2014. I looked up solution by Joel Coehoorn for this question but it did not work for me.
I have a table for clients which I renamed to Aac_Client which has both client information and address information as columns. I want to move Address to a new table Address to organise things and keep other remaining things at Ac_Client. I am trying to do the following:
Note the relationship between Address and Client is One to One.
BEGIN TRANSACTION
DECLARE #DataID int;
-- Insert Address
INSERT INTO Address ([StreetNumber],[StreetName] ,[StreetAddress2] ,[Unit] ,[City] ,[State] ,[Zip] ,[County])
SELECT [StreetNumber], [StreetName], [StreetAddress2], [Unit] ,[City] ,[State] ,[Zip] ,
NULL AS [County]
FROM Aac_Client
-- Get Address Id
SELECT #DataID = scope_identity();
-- Insert Client
INSERT INTO Ac_Client ( AddressId, Name, Phone, Contact )
SELECT #DataID AS AddressId, Name , Phone, Contact FROM Aac_Client
COMMIT
But the problem is INSERT is carried at once and I get the last ID of address in SELECT #DataID = scope_identity();
BEGIN TRANSACTION
IF OBJECT_ID('tempdb..#InsertedAddresses') IS NOT NULL
BEGIN
DROP TABLE #InsertedAddresses
END
CREATE TABLE #InsertedAddresses (
AddressId INT
,ClientId INT
)
DECLARE #DataID int;
-- add a unique client identifier
-- Insert Address
INSERT INTO Address ([StreetNumber],[StreetName] ,[StreetAddress2] ,[Unit] ,[City] ,[State] ,[Zip] ,[County], [ClientId])
OUTPUT INSERTED.AddressId, INSERTED.ClientId (AddressId, ClientId)
SELECT [StreetNumber], [StreetName], [StreetAddress2], [Unit] ,[City] ,[State] ,[Zip] ,
NULL AS [County]
FROM Aac_Client
-- Insert Client
INSERT INTO Ac_Client ( AddressId, Name, Phone, Contact, ClientId)
SELECT i.AddressId, Name , Phone, Contact, c.ClientId
FROM Aac_Client c
INNER JON #InsertedAddresses i
ON c.ClientId = i.ClientId
COMMIT
use the output clause of the first insert statement into a temp table. Also is it possible that clients will have more than one address? Or more then 1 client at the same address? If so you might want to consider putting the AC_Client ID in the address table instead of the address_id in the client table. Also if you are not worried about changing addresses independently in the case of multiple clients at same address you could consider a 3rd table to relate addresses and clients so you don't have to repeat addresses or clients to create a many to many relationship.
I guess to expand scope_identity is a scalar value meaning only 1 value is held and it is the identity of the last row altered in the scope. In your case you need all of the ids so you need to use the output clause.
--Add the following Columns that you can later Drop
ALTER TABLE [Address] ADD Col_GUID UNIQUEIDENTIFIER;
ALTER TABLE [Aac_Client] ADD Col_GUID UNIQUEIDENTIFIER;
GO
-- Give a Unique value to each row.
UPDATE [Aac_Client] SET Col_GUID = NEWID();
BEGIN TRANSACTION;
-- Table variable to capture newly generated Identity values
Declare #NewID TABLE (AddressId INT, Col_GUID UNIQUEIDENTIFIER)
-- Insert Address (with output clause to get the Identity values)
INSERT INTO [Address] ([StreetNumber],[StreetName] ,[StreetAddress2]
,[Unit] ,[City] ,[State] ,[Zip] ,[County], Col_GUID)
OUTPUT Inserted.AddressId , Inserted.Col_GUID INTO #NewID (AddressId , Col_GUID)
SELECT [StreetNumber], [StreetName], [StreetAddress2]
,[Unit] ,[City] ,[State] ,[Zip] , NULL AS [County] , Col_GUID
FROM Aac_Client
-- Insert Client joining with the Table variable on the guid column to
-- to get the new Identity values.
INSERT INTO Ac_Client ( AddressId, Name, Phone, Contact )
SELECT N.AddressId, a.Name , a.Phone, a.Contact
FROM Aac_Client a
INNER JOIN #NewID N ON n.Col_GUID = a.Col_GUID
COMMIT TRANSACTION;
Finally you can drop the Columns you added for this purpose.
ALTER TABLE [Address] DROP COLUMN Col_GUID;
ALTER TABLE [Aac_Client] DROP COLUMN Col_GUID;
I have two tables
First table is CUSTOMERS with columns CustomerId, CustomerName
Second table is LICENSES with columns LicenseId, Customer
The column Customer in the second table is the CustomerId from the First table
I wanted to create a stored procedure that insert values into table 2
Insert into Licenses (Customer)
Values(CustomerId)
How can I get this data from the other table?
Thanks in advance for any help
this looks to me like simply a syntax question - I think what you want is
INSERT INTO Licenses (Customer) SELECT CustomerId FROM customers where ...
CREATE PROCEDURE uspInsertToTable
#CustomerID INT OUTPUT,
#CustomerName VARCHAR(50),
#LicenseID INT,
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO CUSTOMERS
VALUES (#CustomerName);
SET #CustomerID=SCOPE_IDENTITY();
INSERT INTO LICENSES
VALUES (#LicenseID, #CustomerID)
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH;
END;
];
if CustomerID is identity, I wish that it will be work... :)
If you're using SQL Server and your PK is autocorrelative using identity(1,1) do this:
CREATE PROC Register #CustomerName varchar(25)
AS BEGIN
INSERT INTO CUSTOMERS (Customer) VALUES (#CustomerName)
INSERT INTO LICENSES (SELECT MAX(CustomerId) FROM CUSTOMERS)
END
But if you don't want to use identity (1,1) do this:
CREATE PROC Register #CustomerName varchar(25)
AS BEGIN
DECLARE #idc int = SELECT MAX(CustomerId) FROM CUSTOMERS;
DECLARE #idl int = SELECT MAX(LicenseId) FROM LICENSES ;
INSERT INTO CUSTOMERS (CustomerId, CustomerName) VALUES (#idc,#CustomerName)
INSERT INTO LICENSES (LicenseId, Customer) VALUES (#idl,#idc)--I guess your field Customer is the foreign key
END
How to insert data into 3 related tables (SQL Server)
For example, I have tables Customer <-- Customer_Address --> Address
After I insert data into Customer and Address, how do I insert the IDs from Customer and Address in to Customer_Address? (join table)
Thanks!
Use SCOPE_IDENTITY, ##IDENTITY can return a value from any scope:
DECLARE #CustomerId INT
DECLARE #AddressId INT
BEGIN TRANSACTION
INSERT INTO CUSTOMER (blah, blah) values (blah, blah)
SET #CustomerId = SCOPE_IDENTITY
INSERT INTO ADDRESS (blah, blah) values (blah, blah)
SET #AddressId = SCOPE_IDENTITY
INSERT INTO CUSTOMERADDRESS (CustomerId,AddressId) values (#CustomerId,#AddressId)
COMMIT TRANSACTION
If you want to insert more than one row you can use the output clause:
declare #insertedAddresses table (OriginalID int, AddressID int);
declare #insertedCustomers table (OriginalID int, CustomerID int);
insert into dbo.Addresses (AddressData)
output source.OriginalID, inserted.AddressID into #insertedAddresses
select AddressData from source;
insert into dbo.Customers (CustomerData)
output source.OriginalID, inserted.CustomerID into #insertedCustomers
select CustomerData from source;
insert into dbo.Customer_Address (AddressID, CustomerID)
select a.AddressID, c.CustomerID
from #insertedAddresses a inner join #insertedCustomers c on c.OriginalID=a.OriginalID;
If the IDs from the Customer and Address tables are Identity columns, you can store the new ID into a variable.
DECLARE #CustomerID int
SELECT #CustomerID = ##IDENTITY FROM TABLE CUSTOMER
Similar syntax could be used for the Address table. Then in your INSERT statement you can do this:
INSERT INTO Customer_Address (CustomerID, AddressID)
VALUES (#CustomerID, #AddressID)
use a transaction, and remember the identities. in sql server -
declare #CustomerId int
declare #AddressId int
begin tran
insert into Customer (blah, blah) values (blah, blah)
set #CustomerId = ##IDENTITY --assuming there are no triggers
insert into [Address] (blah, blah) values (blah, blah)
set #AddressId = ##IDENTITY --once again, no triggers to mess up the ##IDENTITY
insert into CustomerAddress(CustomerId,AddressId) values (#CustomerId,#AddressId)
commit
Here is one way.
Declare CustomerID int, AddressID int
insert into Customer (list, of, fields) values (list, of, values)
select #CustomerID=scope_Identity()
insert into Address (list, of, fields) values (list, of, values)
select #AddressID =scope_Identity()
insert into Customer_Address (CustomerID, AddressID) values (#CustomerID, #AddressID)
If it is for an application, why don't just use Linq, you can accomplish things like this in just a few minutes, in a more compressive way and more thigh to the business logic model.
If not the I highly recommend the transaction and SCOPE_IDENTITY version, because if you are in a heavy loaded system, an other operation can do an insert and change the identity value so you will end with inconsistent data.