SQL Server Check Contraint and checking Signed In/Out state - sql

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.

Related

Update multiple latest record on table using loop

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;

Self joins and Not in combined

I need to write a query scanning 2 tables in a way that the record after self join in one table should not be present in second table if they have certain condition.
If an order is received we insert into UniqueReference Table that has unique constraints on reference combination (data1,data2,reference,identifier)
But it could be that order get Rejected or Cancelled and next order might come with same reference combination. I have to make sure I still allow it.
I give an example below for test data :
UniqueReference Table
-------
sequence data1 data2 key reference identifier
1 XYZB ABCD 234 Reference1 ID2
2 XYZB DCBF 456 Reference2 ID2
3 XYZB null 678 Reference3 null
4 XYZB ABCD 980 Reference1 ID2
Order Table
--------
sequence key status
1 234 Created
2 456 Rejected
3 789 Processed
4 980 Cancelled
5 678 Processing
Now with above two data set when I receive an order (so say key 980) with reference combination (XYZB,ABCD,Reference1,ID2) then system should not allow that order (it is duplicate) because earlier we received such order with key (234) whose status is not Rejected. But if we receive new order with combination (XYZB,DCBF,Reference2,ID2) then we should allow this order because we do have that order in system but its status is Rejected.
I am needing something like
Select count(*) from Order o where o.status <> 'Rejected' and o.key in
(select <self-join> on reference combination on key for newly received order)
I think you're mostly confusing yourself with this setup.
As for the UniqueReference table. If you say the combination data1, data2, reference, identifier is UNIQUE, well, then it should be just that: UNIQUE. It doesn't matter nor should care about what happens elsewhere.
Remark: I'm assuming the key field is unique too, and if it is, I do wonder why you don't simply use the autonumber/identity/... for it which you now seem to assign to the sequence column, which has no real value IMHO. Right now I can't tell how you produced these key values.
Now for the Order table, here you could foresee logic that says: if we don't have a previous record for this key yet, allow INSERT operation. If there is a previous one, find the most recent one and if it says 'Cancelled' then allow the INSERT too. Otherwise we always block the INSERT.
I'm not sure how you would like to do this, but I guess a stored procedure would make sense in this case.
PS: this code is far from perfect (for starters, it's NOT tested!), has no error-handling and probably can be optimized quite a bit (e.g. by merging certain operations, less branching, etc). But I think its verbosity makes it easier to read and since premature optimization is the root of all evil you might want to work it out like this first and optimize as needed when it turns out to be too slow.
(since you didn't mention RDBMS, I'm using T-SQL syntax, but I think it is sufficiently generic to run on most systems with minor tweaking...)
CREATE PROCEDURE p_insert_order (
#data1 varchar(100),
#data2 varchar(100),
#reference varchar(100),
#identifier varchar(100),
#status varchar(100)
)
AS
DECLARE #key int,
#is_new_key bit,
#allow_insert bit,
#last_status varchar(100)
SELECT #key = NULL,
#is_new_key = 0,
#allow_insert = 0,
#last_status = NULL
BEGIN TRANSACTION
-- do we already have a key for this combination?
SELECT #key = [key]
FROM [UniqueReference] WITH (UPDLOCK, HOLDLOCK)
WHERE data1 = #data1
AND data2 = #data2
AND reference = #reference
AND identifier = #identifier
IF #key IS NULL
BEGIN
SELECT #is_new_key = 1,
#key = 0 -- ??? -- I have no clue how you come up with these so I'm going to leave it open
INSERT [UniqueReference] (data1, data2, reference, identifier, [key])
VALUES (#data1, #data2, #reference, #identifier, #key)
END
-- do we allow insert?
SELECT #allow_insert = #is_new_key
IF #allow_insert = 0
BEGIN
SELECT TOP 1 #last_status = [status]
FROM [Order] WITH (UPDLOCK, HOLDLOCK)
WHERE [key] = #key
ORDER BY sequence DESC
SELECT #allow_insert = 1
WHERE #last_status IS NULL
OR #last_status = 'Rejected'
END
-- do we do insert?
IF #allow_insert = 1
BEGIN
INSERT [Order] ([key], [status])
VALUES (#key, #status)
END
COMMIT TRANSACTION
Return(0)
PS: Try to avoid using reserved words, Order, key, status, ...
First of all you have to add key column in UniqueReference table to the unique constraint so it will be (data1,data2,reference,identifier,key)
Then you can use this query
;with
old_orders as (
select distinct data1, data2, [reference], [identifier]
from UniqueReference u
where not exists(select top 1 1 from Order o where u.[key] = o.[key] and o.status in ('Cancelled','Rejected'))
)
insert into #UniqueReference (data1, data2, [reference], [identifier], [key])
select #data1, #data2, #reference, #identifier, #key
where exists (
select #data1, #data2, #reference, #identifier
except
select * from old_orders o
)
With these values the insert will be rejected
declare
#data1 varchar(4)='XYZB',
#data2 varchar(4)='ABCD',
#key int = 980,
#reference varchar(10)='Reference1',
#identifier varchar(4)='ID2'
With these values the insert will be accepted
declare
#data1 varchar(4)='XYZB',
#data2 varchar(4)='DCBF',
#key int = 999,
#reference varchar(10)='Reference2',
#identifier varchar(4)='ID2'

INSERT INTO from SELECT: The select list for the INSERT statement contains more items than the insert list

I am still getting a weird error:
The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.
Code:
INSERT INTO #tab (Phone)
select t2.Phone
from
(
SELECT DISTINCT top 999 t3.Phone, MIN(t3.Ord)
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
) t2
The idea is to select all phone numbers from #tabTemp with their row order. Then I wanna distinct them and insert distincted numbers into table #tab. Top 999 is here only for order by purpose, because I use it into a function (UDF).
Structures are following:
declare #tabTemp TABLE
(
Phone1 varchar(128) NULL,
Phone2 varchar(128) NULL,
Ord int
);
declate #tab TABLE
(
Phone varchar(max) NULL
);
EDITED:
FULL CODE
CREATE FUNCTION dbo.myFnc(#PID int, #VID int, #JID int, #ColumnNo int)
RETURNS #tab TABLE
(
Phone varchar(max) NULL
)
AS
BEGIN
if #PID is null and #VID is null and #JID is null
return;
if #ColumnNo is null or (#ColumnNo<>2 and #ColumnNo<>3 and #ColumnNo<>6)
return;
declare #catH int;
set #catH = dbo.fncGetCategoryID('H','tt'); -- just returning int value
declare #kvalP int;
set #kvalP = dbo.fncGetCategoryID('P','te');
declare #kvalR int;
set #kvalR = dbo.fncGetCategoryID('R','te');
declare #tabTemp TABLE
(
Phone1 varchar(128) NULL,
Phone2 varchar(128) NULL,
Ord int
);
-- finding parent subject + current one
WITH subj AS(
SELECT *
FROM Subjekt
WHERE
(ID = #PID and #PID is not null)
or
(ID = #VID and #VID is not null)
or
(ID = #JID and #JID is not null)
UNION ALL
SELECT t.*
FROM Subjekt t
INNER JOIN subj r ON r.ID = t.ID
)
INSERT INTO #tabTemp (Phone1,Phone2)
(select
(case when o.TYP1=#catH then o.TEL1 else null end) Phone1
,(case when o.TYP2=#catH then o.TEL2 else null end) Phone2
,so.POR_C
from
subj s
,SubjektPerson so
,Persons o
,recSetup idS
,recSetup idSO
,recSetup idO
where 1=1
and idO.isValid=1
and idSO.isValid=1
and idS.isValid=1
and idSO.ID0=so.ID
and idS.ID0=s.ID
and idO.ID0=o.ID
and so.ID_PERSON=o.ID
and so.ID_SUBJECT=s.ID
and (o.TYP=#kvalP or o.TYP=#kvalR)
)
INSERT INTO #tab (Phone)
select t2.Phone
from
(
SELECT DISTINCT top 999 t3.Phone, MIN(t3.Ord)
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
) t2
RETURN
END
Not sure why you have distinct AND a group by on the same query. You could greatly simplify this.
INSERT INTO #tab (Phone)
SELECT top 999 t3.Phone
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
Now for the error message you were receiving, it doesn't seem like it came from this block of code because the syntax is fine and the number of columns matches correctly. I suspect the error is somewhere earlier in your code.
Also, you might want to consider using temp tables instead of table variables since it seems like you have a lot of rows in these tables.
You've focussed on the wrong insert. This is the one with the mismatch:
INSERT INTO #tabTemp (Phone1,Phone2)
(select
(case when o.TYP1=#catH then o.TEL1 else null end) Phone1
,(case when o.TYP2=#catH then o.TEL2 else null end) Phone2
,so.POR_C
from
...
Two columns in the insert list, 3 columns in the subselect. I can't tell just from the naming whether POR_C was meant to end up in the Ord column or not.
On the surface, it appears you are maybe triggering a query planner bug or something. There are a number of iffy things going on:
The union all of the same table to itself
Using both group by and distinct
I'm not sure what you mean by
Top 999 is here only for order by purpose, because I use it into a function (UDF).
Do you mean this whole query is executed within a UDF? If so, are there other queries that might be giving that error?

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

sql query serial number

I have written a stored procedure in SQL Server 2000. I want a serial number for output table.
So when I run this stored proc I get this error:
An explicit value for the identity column in table
'#tmpSearchResults1' can only be specified when a column list is used
and IDENTITY_INSERT is ON.
I have tried with set IDENTITY_INSERT #tmpSearchResults1 on
Create Procedure dbo.usp_mobile_All_KeyWord(#searchkey varchar(30))
AS
CREATE TABLE #tmpSearchResults
(
property_id varchar(255),
property_number varchar(255),
auction_date_reason varchar(255)
)
INSERT INTO #tmpSearchResults
SELECT
p.property_id, p.property_number, p.auction_date_reason
FROM
Pr p
INNER JOIN
Au a ON p.auction_id = a.auction_id
INNER JOIN
PrAdd pa ON p.property_id = pa.property_id
INNER JOIN state AS s ON s.state_id=pa.state
where
(
(p.archive = 'N'
AND
a.show_on_site = 'Y'
AND
(
(
((p.auction_date >= CONVERT(datetime, CONVERT(varchar, GETDATE(), 103), 103) and (p.auction_date_reason is null or p.auction_date_reason = ''))
or
(p.auction_date <= CONVERT(datetime, CONVERT(varchar, GETDATE(), 103), 103) and ( p.auction_date_reason = 'Accepting Offers' )))
and
pa.property_address_type_id = 1 )) )
and
(state_abbreviation=#searchkey or s.state_name like '%'+''+ #searchkey +''+'%' or city like '%'+''+ #searchkey +''+'%' or pa.address1 like '%'+''+ #searchkey +''+'%'
or pa.address2 like '%'+''+ #searchkey +''+'%')
)
)
CREATE TABLE #tmpSearchResults1
(
i1 int identity,
property_id varchar(255),
property_number varchar(255),
auction_date_reason varchar(255)
)
insert into #tmpSearchResults1
select
property_id ,
property_number,
auction_date_reason
from #tmpSearchResults
order by
case when charindex(#searchkey,state) >0 then 1000 else 0 end desc,
case when charindex(#searchkey,statename) >0 then 1000 else 0 end desc,
case when charindex(#searchkey,city) >0 then 1000 else 0 end desc,
case when charindex(#searchkey,address2) >0 then 1000 else 0 end desc,
case when charindex(#searchkey,address1) >0 then 1000 else 0 end desc,
case when charindex(#searchkey,short_description) >0 then 1000 else 0 end desc
select * from #tmpSearchResults1
Plz do help me
The error code is very very very clear.
The relevant portion is ...when a column list is used....
You need to specify your column list in the INSERT statement.
INSERT INTO #tmpSearchResults
(i1,
property_id,
property_number,
auction_date_reason)
SELECT
p.property_id, p.property_number, p.auction_date_reason
FROM...
First, there is a comma too much in the SELECT part of your second statement:
insert into #tmpSearchResults1
select
property_id ,
property_number,
auction_date_reason , <-- THIS ONE!!
from #tmpSearchResults
The last column of a SELECT statement must be without a comma.
So this would be correct:
insert into #tmpSearchResults1
select
property_id ,
property_number,
auction_date_reason
from #tmpSearchResults
Second, did you read this part of the error message?
An explicit value [...] can only be specified when a column list is used
The "column list" part means that you have to specify the columns in the INSERT part:
insert into #tmpSearchResults1
(property_id, property_number, auction_date_reason)
select
property_id ,
property_number,
auction_date_reason
from #tmpSearchResults
You can get away with not specifying the columns when the number of columns in the SELECT statement is the same as in the table in which they should be inserted (and if the data types match).
If one of these conditions is not met, you need to specify the columns because otherwise SQL Server doesn't know which value to insert into which column.