How would I do this with SQL Server? - sql

I have two tables users and address
users table schema
user_id
name
address_id
sent
address table schema
address_id
create_date
location
user_id
I have this query that returns 16 rows
select * from users where sent = 1;
but all the address_id are all NULL because they have not been created yet
So what I need to do is create 16 rows in the address table one for each of the users with the user_id of the user and then set the address_id in the users table of that address
For now I can leave the location field blank. All I need to do is set the create_date to CURRENT_DATE
Is there a way to do this in one query?

Try this:
declare #user table(user_id [int] IDENTITY(1,1), name varchar(25), address_id int, sent int)
declare #address table(address_id [int] IDENTITY(1,1) NOT NULL, create_date datetime default getdate(), location varchar(100), user_id int)
declare #t table(user_id int, address_id int)
insert #user (name) values('you')
insert #user (name) values('someone else')
begin Transaction
insert #address (user_id)
output inserted.user_id, inserted.address_id
into #t
select user_id from #user u
where not exists (select 1 from #address where u.user_id = user_id)
update u
set u.address_id = t.address_id
from #user u
join #t t
on u.user_id = t.user_id
commit transaction
select * from #user
select * from #address

Not exactly sure what your mean but this should at least point you in the right direction
Begin Try
Begin Transaction
Update Users
Set Users.address = Address.address, create_date = GetDate()
From Addresses
Inner Join Users On Addresses.userid = Users.userid
Commit Transaction
End Try
Begin Catch
Rollback Transaction
End Catch
It should be something like this. There are a couple of ways of doing the problem so have fun with it and hopefully this helped. For testing it write two Select * From Users statements one before and one after. Also change Commit Transaction to Rollback Transaction so you don't have to worry about making a mistake.
Just reread question yea you can't do that in one shot just replace the Update statement with
Insert Into Addresses (address_id, create_date, location, user_id)
Values ('#ddr355_1d', GetDate(), '1234theLocation', 123478)
and you will have to do that for each one but should be easy with only 16 entries in the User table. You might want to look into writing a Stored Procedure if you plan on adding more to the table. Something kind of like this
Create Procedure [dbo].[AddressesInsertData]
(
#Address Int,
#Location varchar(100),
#UserId Int
)
As
Begin Try
Begin Transaction
Insert Into Addresses (address_id, create_date, location, user_id)
Values (#Address, GetDate(), #Location, #UserId)
Commit Transaction
End Try
Begin Catch
Rollback Transaction
End Catch
Basic structure of a stored procedure. I would add an if not exists in there that would update instead of insert but this should be plenty to get you started. Hopefully these examples should clear up somethings for you and help you out.

There is a design rules of thumb that a table models EITHER an entity/class OR the relationship between entities/classes but not both.
Therefore, I suggest you remove the address_id column from the users table, remove user_id from the address table and create a third table comprising both user_id and address_id to model the relationship between users and their addresses. This will also rid you of the need to have nullable columns.

Related

Sql job when certain things happen

lets say I have the table below
Table User
UserId int
Username varchar(50)
Table History
ID int
WhoWasAdded varchar(50)
The SQl Job must insert a row into Table History every time a new user is added to Table User
WhoWasAdded comes from Username
Sample insert trigger code...
CREATE TABLE User
(
[NAME] NVARCHAR(50)
)
GO
CREATE TABLE History
(
[name] NVARCHAR(50)
)
GO
CREATE TRIGGER tblUpdateTrigger ON User
AFTER INSERT
AS
BEGIN
INSERT INTO History ([name])
SELECT T.[name]
FROM user T
INNER JOIN inserted i ON T.[name]=I.[name]
END
GO

Creating a status update trigger

I have 2 tables like so:
JOBS table
Jobcode UserId Status
101 130 R
102 139 D
USERS table
UserId Email
130 test#example.com
I want to create a trigger on insert and update that sends an email to my stored procedure:
EXEC dbo.SendMyEmail #email, #jobcode;
when the jobcode is inserted as 'D' or updated to 'D'.
In my opinion, sending email in a trigger is not optimal.
Instead, you should just insert to a queue table, and have a process run frequently that checks the table and sends the email.
What happens if you get an error in your email procedure? It will force a rollback of your job completion status. Only you know whether that is minor or possibly catastrophic. But I can tell you for sure that DB best practice is to NOT do extended I/O during a DML operation.
CREATE TRIGGER TR_Jobs_EnqueueEmail_IU ON dbo.Jobs FOR INSERT, UPDATE
AS
SET NOCOUNT ON;
INSERT dbo.EmailQueue (UserID, JobCode)
SELECT UserID, JobCode
FROM
Inserted I
LEFT JOIN Deleted D
ON I.JobCode = D.JobCode -- or proper PK columns
WHERE
IsNull(D.Status, 'R') <> 'D'
AND I.Status = 'D';
Tables needed:
CREATE TABLE dbo.EmailQueue (
QueuedDate datetime NOT NULL
CONSTRAINT DF_EmailQueue_QeueueDate DEFAULT (GetDate()),
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailQueue PRIMARY KEY CLUSTERED (QueuedDate, UserID, JobCode)
);
CREATE TABLE dbo.EmailSent (
SentDate datetime NOT NULL
CONSTRAINT DF_EmailSent_SentDate DEFAULT (GetDate()),
QueuedDate datetime NOT NULL,
UserID int NOT NULL,
JobCode int NOT NULL,
CONSTRAINT PK_EmailSent PRIMARY KEY CLUSTERED (SentDate, QueuedDate, UserID, JobCode)
);
Then, run the following stored procedure once a minute from a SQL Job:
CREATE PROCEDURE dbo.EmailProcess
AS
DECLARE #Email TABLE (
QueuedDate datetime,
UserID int,
JobCode int
);
DECLARE
#EmailAddress nvarchar(255),
#JobCode int;
WHILE 1 = 1 BEGIN
DELETE TOP 1 Q.*
OUTPUT Inserted.QueuedDate, Inserted.UserID, Inserted.JobCode
INTO #Email (QueuedDate, UserID, JobCode)
FROM dbo.EmailQueue Q WITH (UPDLOCK, ROWLOCK, READPAST)
ORDER BY QueuedDate;
IF ##RowCount = 0 RETURN;
SELECT #EmailAddress = U.EmailAddress, #JobCode = E.JobCode
FROM
#Email E
INNER JOIN dbo.User U
ON E.UserID = U.UserID;
EXEC dbo.SendMyEmail #EmailAddress, #JobCode;
DELETE E
OUTPUT QueuedDate, UserID, JobCode
INTO dbo.EmailSent (QueuedDate, UserID, JobCode)
FROM #Email E;
END;
The delete pattern and locks I used are very specifically chosen. If you change them or change the delete pattern in any way it is almost certain you will break it. Handling locks and concurrency is hard. Don't change it.
Note: I typed all the above without checking anything on a SQL Server. It is likely there are typos. Please forgive any.
I'm not sure about data types etc but this should at least put you on the right track.
Hope it helps...
CREATE TRIGGER SendEmailOnStatusD
ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
-- holds the UserID so we know which Customer was updated
DECLARE #UserID int
DECLARE #JobCode int
SELECT #UserID = UserId, #JobCode = JobCode
FROM INSERTED WHERE [Status] = 'D' --If you want the old value before the update, use 'deleted' table instead of 'inserted' table
IF (#UserID IS NOT NULL)
BEGIN
-- holds the email
DECLARE #email varchar(250)
SELECT #email = Email FROM USERS WHERE UserId = #UserID
EXEC SendMyEmail (#email, #jobcode);
END
GO
EDIT:
Above code does not handle multiple updates, so for better practice see below option
CREATE TRIGGER SendEmailOnStatusD ON JOBS
-- trigger is fired when an update is made for the table
FOR UPDATE --You can add the same for INSERT
AS
DECLARE #Updates table(UserID int, JobCode int, Email varchar(250))
INSERT INTO #Updates (UserID, JobCode, Email)
SELECT i.UserID, i.JobCode, u.Email
FROM INSERTED i
JOIN USERS u ON i.UserID = u.UserID
WHERE [Status] = 'D'
DECLARE #UserID int
DECLARE #JobCode int
DECLARE #Email varchar(250)
WHILE EXISTS(SELECT * FROM #Updates)
BEGIN
SELECT TOP 1
#UserID = UserID,
#Email = Email,
#JobCode = JobCode
FROM #Updates WHERE UserID = #UserID
EXEC SendMyEmail (#email, #jobcode);
DELETE FROM #Updates
WHERE UserID = #UserID
END
GO
Additionally, as discussed in the comments, sending emails from a trigger is also not the best, but as this is what the question asks for it has been included. I would recommend alternative options for sending emails such as a queue which has been mentioned in other answers.

How to insert a record and make sure the entire row is unique

I want to insert multiple values into a row, but I want to make sure that the row is unique, i.e. no duplicate rows.
I am unsure how to do this (it is fairly easy to if there is only a single value to check for i.e. like this: SQL Server - How to insert a record and make sure it is unique).
This is my code, but it won't allow me to insert unique rows as it tests for single columns and multiple columns combined.
CREATE TABLE myCities (
UserID int null,
CityID int null
)
DECLARE #UserID int, #CityID int
SET #UserID = 1
SET #CityID = 1
INSERT INTO myCities (UserID,CityID)
SELECT #UserID,#CityID
WHERE
#UserID NOT IN ( SELECT UserID FROM myCities WHERE UserID = #UserID )
AND
#CityID NOT IN ( SELECT CityID FROM myCities WHERE CityID = #CityID )
The only sure way is to put the check in the database. In this case create a unique key on the table which will also be its primary key so
-- syntax for MS/Sybase at least is
ALTER TABLE myCities
ADD CONSTRAINT uc_myCities UNIQUE (UserID,CityID)
Then when you insert a duplicate then you will get an error and your code will have to deal with it.
Sometimes the obvious is right at hand - solved it by using NOT EXISTS, like this:
INSERT INTO myCities (UserID,CityID)
SELECT #UserID,#CityID
WHERE NOT EXISTS (
SELECT UserID FROM myCities
WHERE
UserID = #UserID and
CityID = #CityID
)

Simplest way to insert a related row for each row in a table and link them together?

What is the simplest way to insert a Car for each user in Users and set the users CarID to the ID of the inserted Car?
[Users]
- ID
- Name
- CarID
[Cars]
- ID (Auto increment)
- Name
Sorry if this might be a duplicate question but I can't find any simple solutions. Everything I've found is using complicated cursors, pointers etc.
A simple and reusable syntax for this would save me hours while migrating data during system upgrades etc.
If you are on SQL Server 2008 or later you can use merge and output something like this.
Sample tables and data:
declare #Users table
(
ID int identity primary key,
Name varchar(10) not null,
CarID int null
);
declare #Cars table
(
ID int identity primary key,
Name varchar(10) not null
);
insert into #Users(Name) values ('User1'),('User2'),('User3');
Add one care for each user and move the auto-generated CarID back to Users.
declare #ID table(CarID int, UserID int)
merge #Cars as C
using #Users as U
on 0 = 1
when not matched then
insert (Name) values ('CarName')
output inserted.ID, U.ID into #ID;
update U
set CarID = I.CarID
from #Users as U
inner join #ID as I
on U.ID = I.UserID
Try it out on SE Data.
More info on the merge/output trick can be found here.
I'm assuming the code you're using lets you call stored procedures?
create procedure dbo.CarUserInsert_sp
(
#p_UserID int,
#p_UserName varchar(100),
#p_CarID int,
#p_CarName varchar(100)
)
as
if not exists ( select 1 from Cars where ID = #p_CarID )
insert Cars values ( #p_CarID, #p_CarName )
if exists (select 1 from Users where ID = #p_UserID )
update Users set Name = #p_UserName, CarID = #p_CarID where ID = #p_UserID
else
insert Users values ( #p_UserID, #p_UserName, #p_CarID )
go
try this:
insert into cars (name)
select distinct(name) from users
update user
set carId = (select ID from cars where cars.name=user.Name)

i am trying to execute the before insert trigger , but i m getting the sql errors

what i want to achieve is i have a table called orders.
i want to perform the before insert trigger on my orders table.i want to capture the
username of person performing INSERT into table.
one table called info which contain the user.
this is my code
create table orders
(
order_id int,
quantity int,
cost int,
total_cost int,
created_date datetime,
created_by varchar(20)
)
create trigger beforeInsertdata
before insert
on orders
for each row
declare
v_username varchar2(10);
begin
-- Find username of person performing INSERT into table
SELECT user INTO v_username
FROM info;
-- Update create_date field to current system date
:new.create_date := sysdate;
-- Update created_by field to the username of the person performing the INSERT
:new.created_by := v_username;
END;
--user information--
create table info
(
userid int ,
user_name varchar(10)
)
insert into info values(1,'vivek')
select * from info
Basically, triggers are classified into two main types:-
1)After Triggers (For Triggers)
2)Instead Of Triggers
and the syntax for trigger is
CREATE TRIGGER trigger_name ON table_name
[FOR|AFTER|INSTEAD OF] [INSERT|UPDATE|DELETE]
AS
//your code goes here
GO
NOTE : FOR keyword used for INSERT |UPDATE Command where as AFTER USED FOR DELETE Command.
It's hard to tell what you're really trying to do. I've modified your code sample so that it will work on SQL2K5 and made some assumptions about how you're wanting to use the connected user account.
CREATE TABLE orders (
order_id int,
quantity int,
cost int,
total_cost int,
created_date datetime,
created_by varchar(20)
);
CREATE TABLE info (
userid int,
user_name varchar(10)
);
INSERT INTO info
VALUES (1, 'vivek');
SELECT *
FROM info;
CREATE TRIGGER orders_InsteadOfInsert ON orders
INSTEAD OF INSERT AS BEGIN
SET NOCOUNT ON;
-- varchar(10) is to match your table, but probably should be larger
DECLARE #CurrentUser VarChar(10);
SELECT #CurrentUser = SYSTEM_USER;
IF (#CurrentUser NOT IN (SELECT user_name FROM info)) BEGIN
-- consider using an identity column for the key instead of this
INSERT INTO info (userid, user_name)
SELECT
ISNULL((SELECT MAX(userid) FROM info), 0) + 1,
#CurrentUser;
END;
INSERT INTO orders (order_id, quantity, cost, total_cost, created_date, created_by)
SELECT
INS.order_id,
INS.quantity,
INS.cost,
INS.total_cost,
GETDATE(),
#CurrentUser
FROM INSERTED INS;
END;