SQL Update Based on Subquery - sql

I am having trouble coming up with this SQL statement to go and update certain records that already exist. For the sake of brevity, I am using hard coded values.
What I want to do is the following:
When there is already a record within the PersonXNotifyUser table where the UserID and NotifyUserID match, I want to make sure that I update the IsDeleted column to a value of 0, and also update the ModifiedBy and ModifiedDate columns accordingly. Here is what I have so far, which doesn't execute, but am hoping someone can help me out:
UPDATE: Since one of the answers assumed there is need for a subquery, I have added the Table Type and the stored procedure definition which expects the Table Valued Parameter
CREATE TYPE dbo.GuidIDList
AS TABLE
(
ID [UNIQUEIDENTIFIER]
);
CREATE PROCEDURE [dbo].[PersonXNotifyUser_InsertUpdate]
(
,#UserID [UNIQUEIDENTIFIER]
,#NotifyUserIDs AS dbo.GuidIDList READONLY
,#EditingUserID [UNIQUEIDENTIFIER]
)
AS
SET NOCOUNT ON
UPDATE PersonXNotifyUser
SET IsDeleted = 0, ModifiedBy = #EditingUserID, ModifyDate = GETUTCDATE()
FROM (
SELECT
test.NotifyUserID
FROM (
SELECT ID FROM #NotifyUserIDs
)
AS test (NotifyUserID)
WHERE EXISTS
(SELECT PersonXNotifyUserID
FROM PersonXNotifyUser pnu
WHERE pnu.UserID = #UserID AND pnu.NotifyUserID = test.NotifyUserID
)
)

Based on the inputs given in the question, it does not seems like you need a join or subquery for achieving this. Use a Simple Update
DECLARE #UserID UNIQUEIDENTIFIER = '45D9F7E4-E111-4E62-8B1A-118F7C7FB6A1'
DECLARE #EditingUserID UNIQUEIDENTIFIER = 'CDFDBD9A-87FB-4F68-B695-F4A39424C207'
UPDATE PersonXNotifyUser
SET
IsDeleted = 0,
ModifiedBy = #EditingUserID,
ModifyDate = GETUTCDATE()
WHERE UserID = #UserID
AND EXISTS
(
SELECT
1
FROM #NotifyUserIDs
WHERE ID = PersonXNotifyUser.NotifyUserID
)

Related

If a table has an unindexed column with a 1 to many relationship to an indexed column, how to optimize a query for the unindexed column?

If there is a two column table MyTable with enough records that optimization of queries is relevant.
CorporationID int (unindexed)
BatchID int (indexed)
And lets assume there is always a 1 to many relationship between CorporationID and BatchID. In other words for each BatchID there will be only one CorporationID, but for each CorporationID there will be many BatchID values.
We need to get all BatchID values where corporationID = 1.
I know the simplest solution may be to just add an index to CorporationID, but assuming that is not allowed, is there some other way to inform SQL that each BatchID corresponds to only 1 CorporationID, through a query or otherwise?
select distinct batchid from MyTable where corporationID = 1
It seems this is not effective.
select batchid from (select min(corporationid) corporationid, batchid
from MyTable group by batchid) subselect where corporationid = 1
This is also not effective, I assume due to SQL needing to iterate needlessly through all values of corporationid? (Does an aggregate function exist to select any() value which would not have the overhead of min(), max(), sum() or avg()??)
select batchid
from (
select corporationid, batchid
from (
select *, ROW_NUMBER() OVER (PARTITION BY batchid ORDER BY(SELECT NULL)) AS RowNumber
from mytable
) subselect
where RowNumber = 1
) subselect2
where corporationid = 1
Would this work? By arbitrarily selecting the corporationid related to row number 1 after partitioning by batchid with no order?
"assuming it is not allowed to create an index" - this is a highly unlikely assumption. Of course, you should create the index.
The most direct answer to your alternate questions that lie within your question is "no". There is no function or sub query or view or other "read" action you can make to get a list of the batches for a given CorpID. You NEED to access the corpID data to do that... all your sample queries do not work because, at some point, they NEED to access the CorpIDs to know which rows to gather for BatchIDs. Any summary or "rollup" function that might exist would still NEED to access all the pages of data to "see" them. The reading of the pages cannot be avoided.
Without changes to your architecture, it's not physically possible to optimize your query further.
However, with some changes, you could have some options (but Id guess they are much uglier than just adding the index). For instance, you could modify the structure of your BatchID to include data for both the BatchID and the CorpID. Something like "8888899999999"... the 9's are the batchID and the 8's are the CorpID. This doesn't win you much though, you're not saving any index space, but at least you dont have to index the CorpID field :) Somethings like this could be done, but I wont share any others. I dont want the really experienced people here to see this stuff and get ill. :)
You need an index on CorpID if you want to improve performance.
If you don't have a lot of data, I suggest putting an index on the Corporation ID column. But if you have too much data, you can define an index for each Corporation ID
Part 01=>
/*01Create DB*/
IF DB_ID('Test01')>0
BEGIN
ALTER DATABASE Test01 SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE Test01
END
GO
CREATE DATABASE Test01
GO
USE Test01
Go
Part 02=>
/*02Create table*/
CREATE TABLE Table01(
ID INT PRIMARY KEY IDENTITY,
Title NVARCHAR(100),
CreationDate DATETIME,
CorporationID INT ,
MyID INT ,
[GuidId1] [uniqueidentifier] NOT NULL,
[GuidId2] [uniqueidentifier] NOT NULL,
[Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table01] ADD DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table01] ADD DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table01] ADD DEFAULT (NEWID()) FOR [GuidId2]
GO
CREATE TABLE Table02(
ID INT PRIMARY KEY IDENTITY,
Title NVARCHAR(100),
CreationDate DATETIME,
CorporationID INT ,
MyID INT ,
[GuidId1] [uniqueidentifier] NOT NULL,
[GuidId2] [uniqueidentifier] NOT NULL,
[Code] [nvarchar](50) NULL
)
ALTER TABLE [dbo].[Table02] ADD DEFAULT (GETDATE()) FOR [CreationDate]
GO
ALTER TABLE [dbo].[Table02] ADD DEFAULT (NEWSEQUENTIALID()) FOR [GuidId1]
GO
ALTER TABLE [dbo].[Table02] ADD DEFAULT (NEWID()) FOR [GuidId2]
GO
Part 03=>
/*03Add Data*/
DECLARE #I INT = 1
WHILE #I < 1000000
BEGIN
DECLARE #Title NVARCHAR(100) = 'TITLE '+ CAST(#I AS NVARCHAR(10)),
#CorporationID INT = CAST((RAND()*20) + 1 AS INT),
#Code NVARCHAR(50) = 'CODE '+ CAST(#I AS NVARCHAR(10)) ,
#MyID INT = CAST((RAND()*50) + 1 AS INT)
INSERT INTO Table01 (Title , CorporationID , Code , MyID )
VALUES ( #Title , #CorporationID , 'CODE '+ #Code , #MyID)
SET #I += 1
END
INSERT INTO Table02 ([Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code])
SELECT [Title], [CreationDate], [CorporationID], [MyID], [GuidId1], [GuidId2], [Code] FROM Table01
Part 04=>
/*04 CREATE INDEX*/
CREATE NONCLUSTERED INDEX IX_Table01_ALL
ON Table01 (CorporationID) INCLUDE (MyID) ;
DECLARE #QUERY NVARCHAR(MAX) = ''
DECLARE #J INT = 1
WHILE #J < 21
BEGIN
SET #QUERY += '
CREATE NONCLUSTERED INDEX IX_Table02_'+CAST(#J AS NVARCHAR(5))+'
ON Table02 (CorporationID) INCLUDE (MyID) WHERE CorporationID = '+CAST(#J AS NVARCHAR(5))+';'
SET #J+= 1
END
EXEC (#QUERY)
Part 05=>
/*05 READ DATA => PUSH Button CTRL + M ( EXECUTION PLAN) */
SET STATISTICS IO ON
SET STATISTICS TIME ON
SELECT * FROM [dbo].[Table01] WHERE CorporationID = 10 AND MyID = 25
SELECT * FROM [dbo].[Table01] WITH(INDEX(IX_Table01_ALL)) WHERE CorporationID = 10 AND MyID = 25
SELECT * FROM [dbo].[Table02] WITH(INDEX(IX_Table02_10)) WHERE CorporationID = 10 AND MyID = 25
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
Notice IO , TIME , and EXECUTION PLAN .
Good luck

How to update using select query from the same table with correct id?

I have a simple task (they said!), which I need to update the table column using a select statement.
Something like below:
So let's say this table A, I have a bad data on previous pcsProduces column,
right now I want to multiply the cavities and heatcyclecount and then I would like to update the pcsProduces column to a proper value.
The problem is, I have thousands of record, which I really appreciate if someone can help me by showing how to use simple update and select query.
Just fire SQL Update Command like :
update tablename set pcsProduces = cavities * heatcyclecount
You have at least 2 options:
Updating the whole table with an update statement (where clause is optional):
update TbYourTable
set pcsProduces = cavities * heatsOrCyclecount
where pcsProduces != cavities * heatsOrCyclecount
Using a computed column (MS SQL Syntax)
create table [TbYourTable]
(
[Id] int identity(1,1) not null
, [domainS] int not null
, [tStations] int not null
, [itemNo] int not null
, [defaultCavities] int not null
, [missingCavities] int not null
, [cavities] int not null
, [heatsOrCyclecount] int not null
, [shift] nvarchar (max) null
, [pcsProduces] as ([cavities] * [heatsOrCyclecount]) persisted not null -- peristed clause is optional
, constraint [PK_TbYourTable] primary key nonclustered
(
[Id] asc
)
) on [primary];

Prevent changes the default value of a column in a table sql server

How can i prevent changes in a column value set to default in sql server
i have a table TimeTable in which Column is date which is of type varchar(20) i want that column should be updated with getdate() all the time when user insert the value and prevent from modify/change the value in insert statement or update statement. please help!
Use view + trigger -
CREATE TABLE dbo.TestTable
(
ID INT IDENTITY(1,1) PRIMARY KEY
, ProductName VARCHAR(25)
, PurchargeDate DATETIME DEFAULT(GETDATE())
)
GO
CREATE VIEW dbo.vw_TestTable
AS
SELECT
ID
, ProductName
, PurchargeDate
FROM dbo.TestTable
GO
CREATE TRIGGER dbo.trg_IOIU_TestTable
ON dbo.vw_TestTable
INSTEAD OF INSERT, UPDATE
AS BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.TestTable(ProductName)
SELECT i.ProductName
FROM INSERTED i
LEFT JOIN dbo.TestTable t ON t.ID = i.ID
WHERE t.ID IS NULL
UPDATE t
SET ProductName = i.ProductName
FROM dbo.TestTable t
JOIN INSERTED i ON t.ID = i.ID
END
GO
Use trigger for that. It might look like this
CREATE TRIGGER tg_default_value ON TimeTable
FOR INSERT, UPDATE
AS
UPDATE t
SET t.[Column] = GETDATE()
FROM TimeTable t JOIN INSERTED i
ON t.ID = i.ID;
See SQLFiddle example
I think use of trigger will be good approach to do this. In this trigger if opration is insert then you can insert the gatdate() value after change the format to char by using to_char().
and if operation is update then you can prevent to change the value for this column.

optimal solution for lookup on large sql server table

I have a large table of user ids and another table of user records which contains a user post with user ids. The process is whenever a new feed post is retrieved,I do a request to the user id table for an id that is marked inactive ( I have that field ACTIVE because I have another process that creates these ids and inserts it continuously into table 1) and when an id is requested it is marked as inactive.
Then I check if the user exists in the user table(table 2) and if so return the user id associated with that user.
I was told that I can speed up this process but creating a hash table to do the lookup on table 2. I am not sure how to even start this and any links or samples will be appreciated.
Also I need to run a separate process that cleans table 1 and removes all inactive user ids.
When I call the procedure to insert into table 2, I pass the user id retrieved from table 1.
CREATE TABLE [dbo].[userforums]
(
[userid] [VARCHAR](16) NOT NULL CONSTRAINT [PK_forumssiteid] PRIMARY KEY CLUSTERED ,
[forumname] [VARCHAR](500) NOT NULL,
[exported] [INT] NULL,
[lastcrawled] [DATETIME] NULL,
[priority] [INT] NULL,
[origin] [VARCHAR](50) NULL,
[queryid] [VARCHAR](25) NULL,
[dateinserted] [DATETIME] NULL DEFAULT (getdate())
)
second table
CREATE TABLE [dbo].[userids]
(
[userid] [NVARCHAR](20) NOT NULL CONSTRAINT [PK_userids] PRIMARY KEY CLUSTERED,
[active] [NVARCHAR](20) NULL CONSTRAINT [IX_userids] UNIQUE NONCLUSTERED
)
get user id stored procedure
BEGIN TRANSACTION
SELECT TOP 1 #id = userid
FROM userids WITH (UPDLOCK, HOLDLOCK)
WHERE active = 'Y'
OR active IS NULL
UPDATE userids
SET active = 'N'
WHERE userid = #id
COMMIT TRANSACTION
check if userid exists
CREATE PROC Foo #forumname VARCHAR(500),
#userid VARCHAR(16),
#origin VARCHAR(50),
#queryid VARCHAR(25)
AS
SET NOCOUNT ON;
DECLARE #cnt INT
DECLARE #serverip VARCHAR(16)
DECLARE #mincnt INT
DECLARE #siteservercnt INT
SELECT #cnt = COUNT(*)
FROM userforums
WHERE forumname = #forumname
IF #cnt = 0
BEGIN
INSERT INTO userforums
(forumname,
userid,
exported,
origin,
queryid)
VALUES (#forumname,
#userid,
1,
#origin,
#queryid)
SELECT #siteservercnt = COUNT(*)
FROM siteserverip
WHERE userid = #userid
IF #siteservercnt = 0
BEGIN
SELECT TOP 1 #mincnt = COUNT(*),
#serverip = serverip
FROM siteserverip
GROUP BY serverip
ORDER BY COUNT(*)
SELECT TOP 1 #mincnt = sitecount,
#serverip = serverip
FROM serveripcounts
ORDER BY sitecount
INSERT INTO siteserverip
VALUES (#siteid,
#serverip)
UPDATE serveripcounts
SET sitecount = sitecount + 1
WHERE serverip = #serverip
END
END
SELECT userid
FROM userforums
WHERE forumname = #forumname
RETURN
Your existing dequeue query can be improved. Instead of
DECLARE #id INT
SELECT TOP 1 #id = userid
FROM userids WITH (UPDLOCK, HOLDLOCK)
WHERE active = 'Y'
OR active IS NULL
UPDATE userids
SET active = 'N'
WHERE userid = #id
Which is two operations (a clustered index scan followed by an index seek) you can do
UPDATE TOP (1) userids
WITH (ROWLOCK, READPAST)
SET active = 'N'
OUTPUT INSERTED.userid
WHERE active <> 'N'
Which is one operation and gives a plan with two range seeks.
A Hash table #TableName is a temporary object in tempdb that functions as a table. They are generally called 'temp tables'. I would NOT be using them as a first solution for retrieving data on the fly if this is a common occurrence. Instead I would create an index and see if that justifies your needs. Generally hash tables are used for intense operations where you want to get a set of things that may or may not be indexed and then relate it to something else and you want to keep it in memory.
I would create an index and that should improve speed. Also if you find is slow, a hash table won't speed that part up, it will just be putting a collection of that into a source to reuse seperated from the main table.
create index IX_[yourtableName]_[TableColumn(s)] on [Tablename]([Column(s)]
I would not create more objects unless necessary. Generally if your UserId's are valid ints you can search on them quite fast.

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
)