Update multiple latest record on table using loop - sql

CREATE TABLE [dbo].[masterTable]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[CID] [int] NOT NULL,
[PID] [int] NOT NULL,
[Description] [nvarchar](max) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[State] [nchar](20) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
INSERT INTO [dbo].[masterTable]
([CID]
,[PID]
,[Description]
,[CreatedOn]
)
VALUES
(189
,186
,'FC1_189'
,GETUTCDATE()),
(189
,186
,'FC2_189'
,DATEADD(D, +1, GETUTCDATE())),
(190
,186
,'FC1_190'
,DATEADD(d, +2, GETUTCDATE())),
(190
,186
,'FC2_190'
,DATEADD(d, +3, GETUTCDATE())),
(191
,186
,'FC1_191'
,DATEADD(d, +4, GETUTCDATE())),
(191
,186
,'FC2_191'
,DATEADD(d, +5, GETUTCDATE()))
I have a table with 6 records I am trying to update the latest created record based on CID and PID data with the state 'Latest data' and other old created on should update state with 'old data' I tried this but for old created on data not working. check the below query.
Explanation: I currently have 6 records based on CID and PID data.
FC2_189 row is the latest record based on createdOn column so, for this row state column should update with 'latest data' and the other record FC1_189 are old records based on created on date, so compared to FC2_189 row this record is old data based on createdon column so state column should update with 'old data'.
same should happen with FC2_190,FC1_190 and FC2_191,FC1_191 data
SELECT Description,
STATE,
CreatedOn,
PID,
CID,
ROW_NUMBER() OVER (
PARTITION BY ContractID ORDER BY CreatedOn DESC
) contractRN
INTO #ControlTable
FROM masterTable
DECLARE #i INT = 1
DECLARE #count INT
SELECT #count = Count(*)
FROM #ControlTable
WHILE #i <= #count
BEGIN
UPDATE masterTable SET State =
CASE WHEN CreatedOn = (SELECT MAX(CreatedOn) FROM masterTable)
THEN 'Latest data'
ELSE 'Old data'
END
SET #i = #i + 1
END
DROP TABLE #ControlTable

You don't need a loop or joins at all. You can simply calculate a row-number inside a CTE, then update the CTE.
WITH cte AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY PID, CID ORDER BY CreatedOn DESC)
FROM masterTable
)
UPDATE cte
SET State = CASE WHEN rn = 1
THEN 'Latest data'
ELSE 'Old data'
END;

Related

SQL order by needs to check if DATETIME2 is not null then return them first and after order by id

I have two tables and I have trouble figuring out how to do the order by statement to fit my needs.
Basically if the FeaturedUntil column if greater than now then these should be returned first ordered by the PurchasedAt column. Most recent purchases should be first. After these everything should be ordered by the item Id column descending.
Create Table Script
create table Items(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] nvarchar(200) null,
)
create table Feature(
[Id] [int] IDENTITY(1,1) NOT NULL,
[PurchasedAt] [datetime2](7) NOT NULL,
[FeaturedUntil] [datetime2](7) NOT NULL,
[ItemId] [int] NOT NULL,
)
Insert Script
insert into Items(Name) values ('test1')
insert into Feature(PurchasedAt, FeaturedUntil, ItemId) values (dateadd(day, -3, getdate()), dateadd(month, 1, getdate()), ##IDENTITY)
insert into Items(Name) values ('test2')
insert into Feature(PurchasedAt, FeaturedUntil, ItemId) values (dateadd(day, -2, getdate()), dateadd(month, 1, getdate()), ##IDENTITY)
insert into Items(Name) values ('test3')
insert into Feature(PurchasedAt, FeaturedUntil, ItemId) values (dateadd(day, -1, getdate()), dateadd(month, -1, getdate()), ##IDENTITY)
insert into Items(Name) values ('test4')
Select Script
select *
from Items i
left join Feature f on i.Id = f.ItemId
order by
case when f.FeaturedUntil is not null THEN f.PurchasedAt
else i.Id
end
The select should return test2 first as it's FeaturedUntil is greater than now and it is the most recently purchased, second row should be test1 as it is bought before test2. After these should be test4 and last one is test3, because these have no joining Feature table data or the FeatureUntil is not greater than now and these are order by their Item.Id descending.
SELECT *
FROM items i
LEFT JOIN feature f
ON i.id = f.itemid
ORDER BY CASE
WHEN f.featureduntil > getdate THEN purchasedat
ELSE '19000101'
END DESC,
id DESC
You need to order this in descending in order to get the most recent purchase first; the ID sort will still occur, so if you have two PurchasedAt's that are the same, it would sort those 2 by ID.
Based on what you've told us, I think this might be what you're after:
ORDER BY CASE WHEN FeaturedUntil > GETDATE THEN PurchasedAt ELSE '99991231' END ASC, --Future features first, and in date order
--(past have a silly in future date, so always last
Id; --Then ID
Try the following.
select *, case when f.FeaturedUntil is not null THEN f.PurchasedAt else NULL end AS PurchasedAtNew
from Items i
left join Feature f on i.Id = f.ItemId
order by PurchasedAtNew desc, i.Id

SQL Query Optimization with million of records

I am currently using below query to get record based on senderid. I have 2 million record in messagein table and also entries comes parallel in this table as well.
But it take morethen 5 sec to return result. This table having only one non-clustered index created on Providerid (include column priorityid, senderid,maskid)
Can any one sql expert help me out on this.
ALTER PROCEDURE [dbo].[GetNextSmsQueue] #NoOfRow int,
#GatewayId int
AS
BEGIN TRY
BEGIN TRAN;
CREATE TABLE #SmsIn ([Id] [bigint] NOT NULL,
[UserId] [bigint] NOT NULL,
[MaskId] [varchar](50) NOT NULL,
[Number] [varchar](20) NOT NULL,
[Message] [nvarchar](1300) NOT NULL,
[SenderId] [varchar](20) NOT NULL,
[UDH] [nvarchar](50) NULL,
[Credit] [int] NOT NULL,
[CurrentStatus] [int] NOT NULL,
[CheckDND] [bit] NULL,
[CheckFail] [bit] NULL,
[CheckBlackList] [bit] NULL,
[ProviderId] [int] NULL,
[PriorityId] [int] NULL,
[ScheduleDate] [datetime] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[EsmClass] [nvarchar](10) NOT NULL,
[DataCoding] [int] NOT NULL,
[Priority] [int] NOT NULL,
[Interface] [int] NOT NULL);
DECLARE #PriorityIn table ([PriorityId] [int] NOT NULL);
DECLARE #COUNT bigint;
INSERT INTO #PriorityIn
SELECT PriorityId
FROM PriorityProviders
WHERE ProviderId = #GatewayId
AND Type = 0;
SELECT #COUNT = COUNT(*)
FROM MessageIn m
LEFT JOIN #PriorityIn o ON m.PriorityId = o.PriorityId
WHERE ((ProviderId IS NULL
AND o.PriorityId IS NOT NULL)
OR ProviderId = #GatewayId);
IF #COUNT > 0
BEGIN
INSERT INTO #SmsIn ([Id],
[UserId],
[MaskId],
[Number],
[Message],
[SenderId],
[UDH],
[Credit],
[CurrentStatus],
[CheckDND],
[CheckFail],
[CheckBlackList],
[ProviderId],
[PriorityId],
[ScheduleDate],
[CreatedDate],
[EsmClass],
[DataCoding],
[Priority],
[Interface])
(SELECT [Id],
[UserId],
[MaskId],
[Number],
[Message],
[SenderId],
[UDH],
[Credit],
[CurrentStatus],
[CheckDND],
[CheckFail],
[CheckBlackList],
[ProviderId],
[PriorityId],
[ScheduleDate],
[CreatedDate],
[EsmClass],
[DataCoding],
[Priority],
[Interface]
FROM MessageIn
WHERE MaskId IN (SELECT MaskId
FROM (SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo,
MaskId
FROM MessageIn msg
LEFT JOIN #PriorityIn o ON msg.PriorityId = o.PriorityId
WHERE ((msg.ProviderId IS NULL
AND o.PriorityId IS NOT NULL)
OR msg.ProviderId = #GatewayId)) res
WHERE res.RowNo <= #NoOfRow));
DELETE msgin
FROM MessageIn msgin
INNER JOIN #SmsIn temp ON msgin.MaskId = temp.MaskId;
END;
SELECT *
FROM #SmsIn;
DROP TABLE #SmsIn;
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END;
END CATCH;
Execution plan is available in Here:
Execution Plan
Updated Query:
BEGIN TRY
begin tran;
CREATE TABLE #tmpMaskId (MaskId varchar(25) PRIMARY KEY)
INSERT INTO #tmpMaskId(MaskId)
SELECT DISTINCT MaskId From
(SELECT ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg
LEFT JOIN PriorityProviders o on o.ProviderId = #GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId
WHERE
((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = #GatewayId)
)as res WHERE res.RowNo <= #NoOfRow
Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus],[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
From MessageIn m inner join #tmpMaskId msk on m.MaskId = msk.MaskId
DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId
DROP TABLE #tmpMaskId
Commit;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH;
ALTER PROCEDURE [dbo].[GetNextSmsQueue]
#NoOfRow INT
,#GatewayId INT
AS
BEGIN TRY
BEGIN TRAN;
CREATE TABLE #tmpMaskId (MaskId INT PRIMARY KEY)
DECLARE #PriorityIn TABLE ([PriorityId] [INT] NOT NULL)
INSERT INTO #PriorityIn
SELECT PriorityId
FROM PriorityProviders
WHERE ProviderId=#GatewayId AND Type=0
INSERT INTO #tmpMaskId (MaskId)
SELECT DISTINCT MaskId
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY SenderId ORDER BY ScheduleDate) AS RowNo
,MaskId
FROM MessageIn msg
WHERE ((msg.ProviderId IS NULL AND o.PriorityId IS NOT NULL) OR msg.ProviderId=#GatewayId)
) res
WHERE res.RowNo<=#NoOfRow
SELECT [Id]
,[UserId]
,[MaskId]
,[Number]
,[Message]
,[SenderId]
,[UDH]
,[Credit]
,[CurrentStatus]
,[CheckDND]
,[CheckFail]
,[CheckBlackList]
,[ProviderId]
,[PriorityId]
,[ScheduleDate]
,[CreatedDate]
,[EsmClass]
,[DataCoding]
,[Priority]
,[Interface]
FROM MessageIn mi
WHERE EXISTS (SELECT 1 FROM #tmpMaskId AS tmi WHERE tmi.MaskId=mi.MaskId)
DELETE msgin
FROM MessageIn msgin
INNER JOIN #tmpMaskId temp ON msgin.MaskId=temp.MaskId
COMMIT;
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
BEGIN
ROLLBACK TRANSACTION;
END;
END CATCH;
DROP TABLE #tmpMaskId
IMO, As per your requirement, I will only return record from this proc to send sms.After sms is successfully I send only require id from Message table to another proc to delete those records.
Technically it sound good.your existing proc is not slow because of delete.but its not ok to delete before sending sms and again trying to insert.
In my previous scipt,I pointed that you do not need Join on PriorityProviders.
I have revise my script(INNER if possible),
SET NOCOUNT ON
BEGIN TRY
begin tran;
CREATE TABLE #tmpMaskId (MaskId varchar(25) not null)
INSERT INTO #tmpMaskId(MaskId)
SELECT MaskId From
(SELECT ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo, MaskId FROM MessageIn msg with(nolock)
LEFT JOIN PriorityProviders with(nolock)
o on o.ProviderId = msg.ProviderId and o.ProviderId= #GatewayId AND o.Type = 0 and msg.PriorityId = o.PriorityId
WHERE
((msg.ProviderId is null AND o.PriorityId is not null) OR msg.ProviderId = #GatewayId)
)as res WHERE res.RowNo <= #NoOfRow
CREATE TABLE #tmpMaskId (MaskId INT not null)
create clusetered index ix_mask on #tmpMaskId
Select [Id],[UserId],m.[MaskId],[Number],[Message],[SenderId],[UDH],[Credit],[CurrentStatus]
,[CheckDND],[CheckFail],[CheckBlackList],[ProviderId]
,[PriorityId],[ScheduleDate],[CreatedDate],[EsmClass],[DataCoding],[Priority],[Interface]
From MessageIn m
inner join
#tmpMaskId msk
on m.MaskId = msk.MaskId
DELETE msgin
FROM MessageIn msgin
where exists(select 1 from #tmpMaskId temp where msgin.MaskId=temp.MaskId)
DROP TABLE #tmpMaskId
Commit;
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
BEGIN
ROLLBACK TRANSACTION;
END
END CATCH;
notice how I hv remove PK from Temp table and made it clustered Index.
How I hv remove distinct ?
Now main culprit is this statement,
ROW_NUMBER() OVER ( PARTITION BY SenderId ORDER BY scheduledate ) AS RowNo
I think once if you comment it then proc will perform better.
Now you need only index.
Whichever column is most Selective make that column as Clustered Index.
Since I do not know selectivity of each column I can't say whether you should make composite clustered or composite Non clustered index.
If you go for Composite Non Clustered index then make ID as clustered index(PK) and keep the most selective column on left side and so on
Composite Non Clustered index can be (maskid,ProviderId,SenderId,PriorityId)Include(other columns of message table which are require in Resultset)
I am not telling you to remove Row_number().Create composite non clustered index and rebuild index as I have describe above.
With (nolock) :It has nothing to do with data duplicity.
If there is no chance of getting uncommitted data.
If there is not much concurrency issue in message table and is not very frequently insert/updated.
Then you can safely use it.you can Google this "Advantage and disadvantage of with (Nolock)".
In one or two places you can use it if it improve your important query.
Like you said if you create index on maskid then it create deadlock.That is because of faulty script in Insert.

how to insert multiple rows with check for duplicate rows in a short way

I am trying to insert multiple records (~250) in a table (say MyTable) and would like to insert a new row only if it does not exist already.
I am using SQL Server 2008 R2 and got help from other threads like SQL conditional insert if row doesn't already exist.
While I am able to achieve that with following stripped script, I would like to know if there is a better (short) way to do this as I
have to repeat this checking for every row inserted. Since we need to execute this script only once during DB deployment, I am not too much
worried about performance.
INSERT INTO MyTable([Description], [CreatedDate], [CreatedBy], [ModifiedDate], [ModifiedBy], [IsActive], [IsDeleted])
SELECT N'ababab', GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM MyTable WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
UNION ALL
SELECT N'xyz', 1, GETDATE(), 1, NULL, NULL, 1, 0
WHERE NOT EXISTS(SELECT * FROM [dbo].[TemplateQualifierCategoryMyTest] WITH (ROWLOCK, HOLDLOCK, UPDLOCK)
WHERE
([InstanceId] IS NULL OR [InstanceId] = 1)
AND [ChannelPartnerId] IS NULL
AND [CreatedBy] = 1)
-- More SELECT statements goes here
You could create a temporary table with your descriptions, then insert them all into the MyTable with a select that will check for rows in the temporary table that is not yet present in your destination, (this trick in implemented by the LEFT OUTER JOIN in conjunction with the IS NULL for the MyTable.Description part in the WHERE-Clause):
DECLARE #Descriptions TABLE ([Description] VARCHAR(200) NOT NULL )
INSERT INTO #Descriptions ( Description )VALUES ( 'ababab' )
INSERT INTO #Descriptions ( Description )VALUES ( 'xyz' )
INSERT INTO dbo.MyTable
( Description ,
CreatedDate ,
CreatedBy ,
ModifiedDate ,
ModifiedBy ,
IsActive ,
IsDeleted
)
SELECT d.Description, GETDATE(), 1, NULL, NULL, 1, 0
FROM #Descriptions d
LEFT OUTER JOIN dbo.MyTable mt ON d.Description = mt.Description
WHERE mt.Description IS NULL

Duplicate a row multiple times

Basically I want to duplicate a row a variable number of times.
I have a table with the following structure:
CREATE TABLE [dbo].[Start](
[ID] [int] NOT NULL,
[Apt] [int] NOT NULL,
[Cost] [int] NOT NULL)
I want to duplicate each row in this table (Apt-1) times so in the end there will be #Apt rows. Moreover for each new row the value of Cost is decremented until it reaches 0. ID will be the same as there are no primary keys. If I have a record like this:
1 5 3
I need 4 new rows inserted in the same table and they should look like this
1 5 2
1 5 1
1 5 0
1 5 0
I have tried so far a lot of ways but I cannot make it work. Many thanks!
try this
DECLARE #Start TABLE (
[ID] [int] NOT NULL,
[Apt] [int] NOT NULL,
[Cost] [int] NOT NULL)
INSERT #Start (ID, Apt, Cost)
VALUES (1, 5, 3)
; WITH CTE_DIGS AS (
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS rn
FROM master.sys.all_columns AS a
)
INSERT #Start (ID, Apt, Cost)
SELECT ID, Apt, CASE WHEN Cost - rn < 0 THEN 0 ELSE Cost - rn END
FROM #Start
INNER JOIN CTE_DIGS
ON Apt > rn
Try:
;with cte as
(select [ID], [Apt], [Cost], 1 counter from [Start]
union all
select [ID],
[Apt],
case sign([Cost]) when 1 then [Cost]-1 else 0 end [Cost],
counter+1 counter
from cte where counter < [Apt])
select [ID], [Apt], [Cost]
from cte

SQL Server Check Contraint and checking Signed In/Out state

I have a table that functions as an event log and stores the users signed in state, 'In', 'Out', or 'Rejected' (sometimes users my be 'Rejected' based on external criteria).
Here is some sample data so you can get an idea of what the table looks like:
Table MyTable
PersonID - State - DateTime
// data sample
156 - 'Out' - 02-14-2010 13:04:15
156 - 'In' - 02-21-2010 09:01:13
16 - 'In' - 02-21-2010 09:05:01
58 - 'Rejected' - 02-21-2010 11:04:58
156 - 'Out' - 02-21-2010 11:10:02
Here is some pseduo check restraint code outlining what I'd like to do:
CHECK(
CASE
WHEN (
[State] = 'In' AND
(Select TOP 1 State FROM MyTable WHERE PersonID=#PersonID_ToUpdate)!='In' ORDER BY DateTime DESC)
)
THEN 'T'
WHEN (
[State] = 'Out' AND
(Select TOP 1 State FROM MyTable WHERE PersonID=#PersonID_ToUpdate)!='Out' ORDER BY DateTime DESC)
)
THEN 'T'
WHEN (
[State] = 'Rejected' AND
(Select TOP 1 State FROM MyTable WHERE PersonID=#PersonID_ToUpdate)!='In' ORDER BY DateTime DESC)
)
THEN 'T'
ELSE 'F'
END = 'T'
)
Basically:
A person can sign IN if their last state was not 'In'
A person can sign OUT if their last state was not 'Out'
A person can be REJECTED if their last state was not 'In'
I don't know if a Check Constraint is the best way to do this or if my database design will allow for this level of constraint; please let me know if I'm out of my mind (and kindly suggest a more suitable method for storing the data and/or ensuring data integrity)
note: I'm using SQL-Server 2008
Here's a sample trigger. It assumes that you're only going to insert 1 row at a time (which is probably the case here), and I haven't bothered with indexes, etc.
I added a clause for when the state is 'Out' so it ignores 'Rejected' states - this was to prevent multiple Out's. Its very basic but you get the idea.
if object_id('dbo.MyTable') is not null
drop table dbo.MyTable;
create table dbo.MyTable (
PersonID int not null,
[State] varchar(20) not null,
[DateTime] datetime not null default(getdate())
);
if object_id('dbo.ins_MyTable_status_validation') is not null drop trigger dbo.ins_MyTable_status_validation;
go
create trigger dbo.ins_MyTable_status_validation
on dbo.MyTable
instead of insert
as
begin
set nocount on;
-- assuming you're only inserting 1 row at a time (which makes sense for an event log)
if (select count(*) from inserted) > 1 begin
print 'Multiple rows inserted - raise some kind of error and die'
return
end
declare #personid_toupdate int,
#state varchar(20);
select #personid_toupdate = personid,
#state = [state]
from inserted;
if case
when (
#state = 'In' and
isnull((select top 1 [State] from dbo.MyTable where personid = #personid_toupdate order by [datetime] desc), 'Blah') != 'In'
)
then 'T'
when (
#state = 'Out' and
isnull((select top 1 [State] from dbo.MyTable where personid = #personid_toupdate and [State] != 'Rejected' order by [datetime] desc), 'Blah') != 'Out'
)
then 'T'
when (
#state = 'Rejected' and
isnull((select top 1 [State] from dbo.MyTable where personid = #personid_toupdate order by [datetime] desc), 'Blah') != 'In'
)
then 'T'
else 'F'
end = 'T'
begin
-- data is valid, perform the insert
insert dbo.MyTable (PersonID, [State])
select PersonID, [State]
from inserted;
end
else
begin
-- data is invalid, return an error (something a little more informative than this perhaps)
raiserror('bad data...', 16, 1)
end
end
go
-- test various combinations to verify constraints
insert dbo.MyTable (PersonID, [State]) values (1, 'In')
insert dbo.MyTable (PersonID, [State]) values (1, 'Out')
insert dbo.MyTable (PersonID, [State]) values (1, 'Rejected')
select * from dbo.MyTable
You have to use a trigger.
You can use a udf in a check constraint to hide the table access. But don't.