we have a sybase ASE 15 DB and something weird happened last week, it looks like a select on a newly inserted row maybe missed right after the insert
our table uses start/end timestamp for milestoning; an example is
id number,
name varchar(32),
start timestamp,
end timestamp;
an "update" would be an update on existing end; and an insert of the new record; for example
id name start end
1 alice 2010-01-01 00:00:00 3000-01-01 00:00:00
on update will become
id name start end
1 alice 2010-01-01 00:00:00 2010-01-01 08:00:00
1 bob 2010-01-01 08:00:00 3000-01-01 00:00:00
and both update and insert is done in the same transaction
now with row locking and index on (id, start, end), when selecting using isolation level 1, with select query:
select * from table where id=1 and start<=getdate() and end>getdate()
ideally when select is run during update, it should block until commit and return the latest row.
However in our case we saw the blocking, but no rows returned!
This means to me that when the select is run, it sees the update but not the insert; is that possible?
is it possible somehow on commit, the new row is inserted but the index is not updated therefore the select did not see the new row?
Thanks
If this is Sybase ASE, then you must choose a different datatype than 'timestamp' -- that's a special datatype in ASE, unrelated to real-life date/time. Use 'bigdatetime' or 'datetime' instead.
Related
Thanks for looking. I'm trying to write a SQL Server trigger that when a new record is added containing date information, will add the day of the week to the DayOfWeek column. Here's my table, with the columns in order:
Food table:
FoodName **varchar(20)**
CategoryID (FK) **int**
Price **smallmoney**
StoreID (FK) **int**
Date **datetime**
DayOfWeek **varchar(9)**
ShopperID (FK) **int**
Week **int**
Here is the trigger I've written:
-- Create a trigger to update day of the week when a record is inserted
CREATE TRIGGER DOW
ON Food
FOR INSERT
AS
BEGIN
-- Declare a variable to hold the date ID
DECLARE #dateID DATETIME
-- Get the date from the new record and store it in #dateID
SELECT #dateID = Date FROM Food
-- Insert day of the week based on the inserted date
INSERT INTO Food (DayOfWeek)
SELECT DATENAME(dw, #dateID)
END
GO
SQL Server seemed to accept the procedure, but when I ran another procedure to insert a new record, I got this error:
Msg 515, Level 16, State 2, Procedure DOW, Line 8 [Batch Start Line 21]
Cannot insert the value NULL into column 'Week', table *******; column does not allow nulls. INSERT fails.
I am not sure why this trigger is affecting the 'Week' column at all. The code should take the value entered for the Date and use the DATENAME(dw,...) function to return the day of the week, which should go into the DayOfWeek column. I've written a stored procedure that accepts a date as input and inserts the corresponding day of the week into the record, and it works just fine, but this trigger doesn't seem to want to cooperate. I'm stumped!
What your trigger does:
it fetches a Date from your table (the last one that is returned) which is not necessarily the last inserted value.
it tries to insert a new record with just the DayOfWeek of that Date specified.
it fails, because at least the Week must also be specified.
I guess that you want to update the value of the DayOfWeek for the inserted row(s) instead. To be able to do so, there must be a way to identify the row(s) that need to be updated in the Food table by knowing the values of the inserted rows. To be sure to update the correct rows, there should be a primary key that allows you to identify them. For sure you have such a primary key, and I guess that it's named FoodID, so probably you wanted to do this:
CREATE TRIGGER DOW ON Food
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
-- update the day of the week for the inserted rows
UPDATE Food
SET [DayOfWeek] = DATENAME(dw, f.[Date])
FROM Food f
INNER JOIN inserted i ON f.FoodID = i.FoodID
END
GO
There are some major problems with your trigger. In triggers, there is an inserted table (on inserts and updates) and deleted table (on deletes and updates). You should be using this table's information to know what records need updated.
This is bad because a trigger can have multiple rows
This SQL simply will not work correctly if you insert multiple rows.
DECLARE #dateID DATETIME
SELECT #dateID = Date FROM Food
This SQL is trying to insert a new row which is causing your NULL error
It is not trying to update the row you are inserting
INSERT INTO Food (DayOfWeek)
SELECT DATENAME(dw, #dateID)
It would need to be an INSTEAD OF trigger to avoid the null constraint on the column. Wolfgang's answer will still cause a null constraint error, because after triggers run AFTER the data is inserted. An INSTEAD OF trigger will run in place of the the actual insert.
CREATE TRIGGER DOW ON Food
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
-- update the day of the week for the inserted rows
INSERT INTO Food (FoodName,CategoryID,Price,StoreID,[Date],ShopperID,[Week],[DayOfWeek])
SELECT
FoodName,CategoryID,Price,StoreID,[Date],ShopperID,[Week],DATENAME(dw, [Date]) AS [DayOfWeek]
FROM inserted
END
GO
Personally, I think storing the week and day of week is a bad idea. You already have a value that can derive that information (Date). Any time you have multiple columns that are essentially duplicate data, you will run into maintenance pain.
Table 1 - Escalation, Table2 - Data
The requirement is : for all record in data table, we need to perform escalation.
Example 1 - record 1 in data table, year is 2014 and economic year is 2018. So, we need to escalate the value(600) from 2014 to 2015, then to 2016, then to 2017.
So, final value will be 600*5*6*7
Example 2 - record 3 in data table will be escalated twice - from 2015 to 2016 and then to 2017. So, final value will be 1000*6*7
This has to be performed for all records in data table.
I don't want to use cursor as i have 3 million records to do this.
Please suggest some idea to perform using Set Operation
create table data
([year] int,
value int,
economic_year int,
modelid int,
shopid int)
create table escalation
([year] int,
shopid int,
value_es int
)
insert into data
values(
2014,600,2018,5,1),
(2014,600,2018,5,1),
(2015,1000,2018,5,1),
(2016,2000,2018,5,1),
(2017,3000,2018,5,1)
insert into escalation
values
(2014,1,4),
(2015,1,5),
(2016,1,6),
(2017,1,7)
select * from escalation
select * from data
One way you could do this is have a third table that you prepopulate with all the possible "update paths" and the respective values. For example, if your year is 2014 and economic year is 2018, then your multiplier (value_es) is 120 (4*5*6). Something like this:
You would join the data table to this new table on two columns data.year = start_year and data.economic_year = econ_year. Then, your new value would be data.value * val.
You could create a simple ETL process that initially populates this new table and updates it when the calendar rolls over to a new year. Let me know what you think or if you have any questions.
THE SCENARIO:
I have 2 different servers (SERVER_A and SERVER_B) running SQL Server Express on each one, with same table definition on both databases.
The table definition is:
KEY integer
VALUE nvarchar(30)
LAST_UPDATE datetime
Each time the user updates the VALUE, a trigger puts GETDATE() into LAST_UPDATE.
NEED:
I need to get the last edited "VALUE" for a given "KEY", from SERVER_A or SERVER_B.
To achieve this, I linked "SERVER_B" with "SERVER_A" and I ran:
SELECT
CASE
WHEN TA.LAST_UPDATE >= TB.LAST_UPDATE THEN TA.VALUE
ELSE TB.VALUE
END AS NEWERVALUE
FROM SERVER_A.DATABASEA.dbo.TABLEA TA
INNER JOIN SERVER_B.DATABASEB.dbo.TABLEB TB ON TA.KEY = TB.KEY
WHERE TA.KEY = 1234
THE PROBLEM:
Can occur that the user edits a row in SERVER_A for a certain KEY and a few seconds or minutes later, edit in SERVER_B for the same KEY. The difference in system datetime of servers machines, makes the query returns the SERVER_A VALUE incorrectly.
Example:
SERVER_A has the time: 2016-01-01 14:03:30
SERVER_B has the time: 2016-01-01 14:00:00
User updates and set the VALUE="one" for KEY=1234 in SERVER_A. The column LAST_UPDATE will be 2016-01-01 14:03:30.
One minute later, the user updates and set the VALUE="two" for KEY=1234 in SERVER_B. The column LAST_UPDATE will be 2016-01-01 14:01:00.
If I run my query, get the VALUE "one" as newest. This is erroneous.
I have a table here with following fields:
Id, Name, kind. date
Data:
id name kind date
1 Thomas 1 2015-01-01
2 Thomas 1 2015-01-01
3 Thomas 2 2014-01-01
4 Kevin 2 2014-01-01
5 Kevin 2 2014-01-01
5 Kevin 2 2014-01-01
5 Kevin 2 2014-01-01
6 Sasha 1 2014-01-01
I have an SQL statement like this:
Select name,kind,Count(*) AS RecordCount
from mytable
group by kind, name
I want to know how many records there are for any name and kind. Expected results:
name kind count
Thomas 1 2
Thomas 2 1
Kevin 2 2
Sasha 1 4
The problem is that it is a big table, with more than 50 Million records.
Also I'd like to know the result within the last hour, last day, last week and so on, for which I need to add this WHERE clause this:
Select name,kind,Count(*) AS RecordCount
from mytable
WHERE Date > '2015-26-07'
group by kind, name
I use T-SQL with the SQL Server Management Studio. All of the relevant columns have a non clustered index and the primary key is a clustered index.
Does somebody have ideas how to make this faster?
Update:
The execution plan says:
Select, Compute Scalar, Stream Aggregate, Sort, Parallelism: 0% costs.
Hash Match (Partial Aggregate): 12%.
Clustered Index Scan: 88%
Sorry, I forgot to check the SQL-statements.
50 million is just lot of rows
Not anything you can do to optimize that query that I can see
Possibly a composite index on kind, name
Or try name, kind
Or name only
I think the query optimizer is smart enough for this to not be a factor but but switch the group by to name, kind as name is more unique
If kind is not very unique (just 1 and 2) then you may be better off no index on that
I defrag the indexes you have
To query the last day is no big deal because you already have a date column on witch you can put an index on.
For last week I would create a seperate date-table witch contains one row per day with columns id, date, week
You have to pre-calculate the week. And now if you want to query a specific week you can look in the date table, get the Dates and query only those dates from your tabele mytable
You should test if it is more performant to join the date columns or if you better put the id column in your myTable an join with id. For big tables id might be the better choice.
To query last hour you could add the column [hour] in myTable an query it in combination with the date
I came across the timestamp datatype in sql server.What is the practical use of timestamp column in sql server with example?
I have used TIMESTAMP data type (ROWVERSION, SQL2005+) to avoid the lost update problem:
The lost update problem: A second transaction writes a second value of
a data-item (datum) on top of a first value written by a first
concurrent transaction, and the first value is lost to other
transactions running concurrently which need, by their precedence, to
read the first value. The transactions that have read the wrong value
end with incorrect results.
Example: lost update:
t : User 1 read payment order (PO) #1 (amount 1000)
t+1: User 2 read payment order (PO) #1 (amount 1000)
t+2: User 1 change the amount for PO #1 to 1005
t+3: User 2 change the amount for PO #1 to 1009 (change make by User 1 is lost because is overwritten by change make by User 2)
t+4: The amount is **1009**.
Example: How to prevent the lost update:
t : User 1 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1)
t+1: User 2 read payment order (PO) #1 (amount 1000, timestamp 0x00000000000007D1)
t+2: User 1 change the amount for PO #1 to 1005 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D1). The check succeeds and the change is `COMMIT`ed. This will change, also, the timestamp (column 'RW'). The new timestamp is 0x00000000000007D4.
t+3: User 2 change the amount for PO #1 to 1009 and it checks if row has the same `timestamp` (column `RW` in this case; 0x00000000000007D4). The checks fails because the initial timestamp (#rw=0x00000000000007D1) is <> than current timestamp (column `RW`=0x00000000000007D4). An error is raised the catch block "intercepts" the error and this transaction is cancelled (`ROLLBACK`).
t+4: The amount {remains|is} **1005**.
Example: T-SQL script for How to prevent the lost update (warning: you have to use two SSMS windows/two sessions)
CREATE DATABASE TestRowVersion;
GO
USE TestRowVersion;
GO
CREATE TABLE dbo.PaymentOrder(
PaymentOrderID INT IDENTITY(1,1) PRIMARY KEY,
PaymentOrderDate DATE NOT NULL,
Amount NUMERIC(18,2) NOT NULL,
CreateDate DATETIME NOT NULL DEFAULT (GETDATE()),
UpdateDate DATETIME NULL,
RW ROWVERSION NOT NULL -- R[ow] V[ersion]
);
GO
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-21',1000);
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-22',2000);
INSERT dbo.PaymentOrder (PaymentOrderDate,Amount)
VALUES ('2013-07-23',3000);
GO
SELECT * FROM dbo.PaymentOrder;
/*
PaymentOrderID PaymentOrderDate Amount CreateDate UpdateDate RW
-------------- ---------------- ------- ----------------------- ---------- ------------------
1 2013-07-21 1000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D1
2 2013-07-22 2000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D2
3 2013-07-23 3000.00 2013-07-21 09:35:38.750 NULL 0x00000000000007D3
*/
GO
-- User 1 (SQL Server Management Studio/SSMS window #1)
-- [t] Client app, user 1: it loads first PO
SET NOCOUNT ON;
GO
DECLARE #PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=#PaymentOrderID;
-- Client app, user 1: during 15 seconds it edit the amount from 1000.00 to 1005.00
WAITFOR DELAY '00:00:15';
GO
-- [t+2] Client app, user 1: it sends this change (new amount) from client app to database server
-- with the old row version value
DECLARE #PaymentOrderID INT=1; -- parameter
DECLARE #rw BINARY(8)=0x00000000000007D1; -- parameter
DECLARE #NewAmount NUMERIC(18,2)=1005.00; -- parameter
BEGIN TRY
BEGIN TRANSACTION
UPDATE dbo.PaymentOrder
SET Amount=#NewAmount
WHERE PaymentOrderID=#PaymentOrderID
AND RW=#rw; -- it checks the timestamp (current timestamp versus original timestamp)
DECLARE #rowcount INT=##ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ?
SELECT #rowcount AS [##ROWCOUNT];
IF #rowcount<>1
RAISERROR('Lost update or row deleted.', 16, 1);
COMMIT TRANSACTION
PRINT 'UPDATE succeded';
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
ROLLBACK;
DECLARE #ErrMsg NVARCHAR(2002);
SET #ErrMsg=ERROR_MESSAGE();
RAISERROR(#ErrMsg,16,1);
END CATCH;
GO
-- [t+4] Client app, user 1: it reloads first PO
DECLARE #PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=#PaymentOrderID;
GO
-- User 2 (warning: run this script in another SQL Server Management Studio window: File > New Database Engine Query !; SSMS window #2)
-- [t+1] Client app, user 1: it loads first PO
SET NOCOUNT ON;
GO
DECLARE #PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=#PaymentOrderID;
-- Client app, user 1: during 20 seconds it edit the amount from 1000.00 to 1005.00
WAITFOR DELAY '00:00:20';
GO
-- [t+4] Client app, user 1: it sends this change (new amout) from client app to database server
-- with the old row version value
DECLARE #PaymentOrderID INT=1; -- parameter
DECLARE #rw BINARY(8)=0x00000000000007D1; -- parameter
DECLARE #NewAmount NUMERIC(18,2)=1009.00; -- parameter
BEGIN TRY
BEGIN TRANSACTION
UPDATE dbo.PaymentOrder
SET Amount=#NewAmount
WHERE PaymentOrderID=#PaymentOrderID
AND RW=#rw; -- it checks the timestamp (current timestamp versus original timestamp)
DECLARE #rowcount INT=##ROWCOUNT; -- How many rows were affected by the last statement (UPDATE in this case) ?
SELECT #rowcount AS [##ROWCOUNT];
IF #rowcount<>1
RAISERROR('Lost update or row deleted.', 16, 1);
COMMIT TRANSACTION
PRINT 'UPDATE succeded';
END TRY
BEGIN CATCH
IF ##TRANCOUNT>0
ROLLBACK;
DECLARE #ErrMsg NVARCHAR(2002);
SET #ErrMsg=ERROR_MESSAGE();
RAISERROR(#ErrMsg,16,1);
END CATCH;
GO
-- [t+4] Client app, user 1: it reloads first PO
DECLARE #PaymentOrderID INT=1; -- parameter
SELECT po.PaymentOrderID,
po.PaymentOrderDate,
po.Amount,
po.RW
FROM dbo.PaymentOrder po
WHERE po.PaymentOrderID=#PaymentOrderID;
GO
Results for User 1 (Amount 1000 -> 1005):
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1000.00 0x00000000000007D1
##ROWCOUNT <- Timestamp check succeeds
-----------
1
UPDATE succeded
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1005.00 0x00000000000007D4
Results for User 2 (Amount 1000 -> 1009):
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1000.00 0x00000000000007D1
##ROWCOUNT <- Timestamp check fails
-----------
0
Msg 50000, Level 16, State 1, Line 27
Lost update.
PaymentOrderID PaymentOrderDate Amount RW
-------------- ---------------- --------------------------------------- ------------------
1 2013-07-21 1005.00 0x00000000000007D4
Note: changed the error message to RAISERROR('Lost update or row deleted.', 16, 1);
Let's take an example of a sale order table to illustrate what timestamp does.
create table saleorder (ordernumber int, amount int, timestamp);
insert into saleorder (ordernumber, amount) values (1, 100), (2, 100), (3, 200);
select * from saleorder
Notice the data in timestamp column. Documentation of timestamp (SQL Server 2005) says: This (i.e. timestamp) tracks a relative time within a database, not an actual time that can be associated with a clock...Every time that a row with a timestamp column is modified or inserted, the incremented database timestamp value is inserted in the timestamp column.
Let's see how the data looks like:
ordernumber amount timestamp
1 100 0x00000000000007D1
2 100 0x00000000000007D2
3 200 0x00000000000007D3
Alright. Order 1 was added first and order 3 was entered last. What happens if we were to update amount of order 1?
update saleorder set amount = 200 where ordernumber = 1
select * from saleorder
Ah, notice that order 1's timestamp is now 0x7D4 (Decimal 2004). In relation to other rows, we know that order 1 was updated most recently. But, more importantly, the value of timestamp comes when concurrent writes are happening.
ordernumber amount timestamp
1 200 0x00000000000007D4
2 100 0x00000000000007D2
3 200 0x00000000000007D3
Let's say John and Mary are both in sales attending to order 3 using a web application developed in, say, .NET. John pulls up the order and makes changes. John hasn't saved the data yet. Mary pulls the same order and changes it. John saves first. Mary attempts to save the data. .NET application can first look to see if the timestamp Mary pulled is still the same that the database has for order 3.
If the timestamp Mary pulled with order 3 is now different (because John saved data and timestamp automatically got changed), the .NET application can alert Mary and ask her to refresh the record on her screen to see the latest change (or probably highlight the change on-screen).
Think of timestamp as a row version. Interestingly, SQL Server's latest editions use rowversion datatype, which is synonymous with timestamp datatype. Documentation of rowversion (SQL Server 2012) has some interesting examples.
I have used the timestamp column to keep track of when data changes, specifically data that needs to be synced to one or more mobile applications. You can use the timestamp column to just give return the rows that have changed since a point in time (by providing the previous timestamp).