I am new to SQL Server. I'm using SQL Server in Azure, and I am looking for the best way to accomplish setting a status field when a new record is entered.
I have the following data:
I need to set\calculate the Quote_Status field.
When a new version is added, the new version's Quote_Status should be "Open"
For the previous version (or all other versions), Quote_Status should be "Versioned"
A new version is defined as Quote_System, Quote_Date, and Quote_ID are all equal.
When a new version is added, the Quote_Status should look like this:
I've considered triggers and calculated fields, but I've never done anything like this and don't know how to start the SQL. Thanks!
If the column can be calculated on the fly, something like the following could work:
with cte as (
select *,
row_number() over (
partition by Quote_System, Quote_Date, Quote_ID
order by QuoteVersion desc
) as rn
from dbo.yourTable
)
select *, case when rn = 1 then 'Open' else 'Versioned' end as Quote_Status
from cte;
Essentially, for each grouping of (Quote_System, Quote_Date, Quote_ID), I'm enumerating the versions in descending order. With that, the first (i.e. rn = 1) is the Open one while the rest are Versioned. In actual use, I'd add a where clause to the actual select so that there's a reasonable chance for it performing well.
If you need it to be persisted and Quote_Version is monotone increasing, I'd prefer to do it in a stored procedure. Like so:
create procedure dbo.insert_Quote (
#Quote_System varchar(20),
#Quote_Date date,
#Quote_ID varchar(20),
#Quote_Version int
)
as
begin
update dbo.yourTable
set Quote_Status = 'Versioned'
where Quote_System = #Quote_System
and Quote_Date = #Quote_Date
and Quote_ID = #Quote_ID
and Quote_Status <> 'Versioned';
insert into dbo.yourTable
(Quote_System, Quote_Date, Quote_ID, Quote_Version, Quote_Status)
values
(#Quote_System, #Quote_Date, #Quote_ID, #Quote_Version, 'Open');
end
If you really need a trigger, I can come up with something like that, too. But it's my least preferable solution.
Related
Lets say i have a small db table with only two fields. (MSSQL) Like this:
date (Date) daily_counter (Int)
-------------------------
2021-07-18 0
2021-07-18 1
2021-07-18 2
2021-07-19 0
I want to insert a new fifth row and insert value "2021-07-19" to the date field. And i want to know what the daily_counter is for my new row.
As you perhaps understand by the example, the daily_counter is supposed to auto increase, starting over each day.
So, since there is already a row with that date with the value 0 on the field daily_counter. I want to add 1 to daily_counter for my new row without sending the value 1 to the query.
How can i think when designing this type of table and data. Any hints appreciated. Thanks!
Kr
Gustav
UPDATE:
Ok, i think i got something that could work. The only downside would be when deleting and adding new rows, as the new id could be previosly used and deleted and added again.
A side from that i think i got something that i can use.
It might not be pretty now, but it looks like this.
It seems to work also when there is no row for the current day.
DECLARE #date DATE
SET #date = '2021-07-22'
DECLARE #daily_counter INT
SET #daily_counter = (SELECT MAX(daily_counter) from mytable where date = #date);
SET #daily_counter = #daily_counter + 1;
IF #daily_counter IS NULL
BEGIN
SET #daily_counter = 1;
END
INSERT INTO
mytable
(date, daily_counter)
OUTPUT #daily_counter
VALUES (#date, #daily_counter)
Thanks again for the help!
It's not possible to make the database do this automatically in the row itself. You must have a single counter across all dates (a SEQUENCE would be good for this).
What you can do is use the row_number() function to simulate this at the point where you query the data:
SELECT *, row_number() over (partition by [date] order by [date])
FROM ...
Unfortunately, this will still fail if you need to preserve the original position following deletes, but there's not a good way to do this right now in a database without triggers or application code.
I am well aware what a forwarded record within a heap is.
Since I want to keep forwarded records at 0, we decided to update only on columns that could not be extended.
Recently on my system I encountered forwarded records.
Table design is like this:
CREATE TABLE dbo.test (
HashValue BINARY(16) NOT NULL,
LoadTime DATETIME NOT NULL,
LoadEndTime DATETIME NULL,
[other columns that never get updates]
) WITH(DATA_COMPRESSION=PAGE);
The insert statements ALWAYS brings all the columns, so none is left NULL. I checked the query logs.
I insert a value of '9999-12-31' for the LoadEndTime.
Now system performs an update on LoadTime like this.
;WITH CTE AS (
SELECT *, COALESCE(LEAD(LoadTime) OVER(PARTITION BY HashValue ORDER BY LoadTime) ,'9999-12-31') as EndTimeStamp
)
UPDATE CTE SET LoadEndTime = EndTimeStamp;
since the LoadEntTime column is always filled there should be no extention of that column within the row when the update is executed. It should be an in place update. Still i get forwarded records always after that process... It doesn't make sense to me...
I'm not understanding why the variable, #NextURLId, in this cursor is not being updated. Here is the code
DECLARE #NextURLId INT = 1
DECLARE #varContact_Id INT
DECLARE GetURL_Cursor CURSOR FOR
SELECT DISTINCT(contact_id)
FROM obp.Contacts
OPEN GetURL_Cursor
FETCH NEXT FROM GetURL_Cursor INTO #varContact_id
WHILE ##FETCH_STATUS = 0
BEGIN
-- Available URLs have the used value as NULL. Used has value of 1.
SET #NextURLId = (SELECT MIN(id) FROM obp.URL WHERE used IS NULL)
UPDATE obp.Contacts SET URL = (
SELECT url from obp.URL WHERE id = #NextURLId)
UPDATE obp.URL SET
used = 1,
contact_id = #varContact_Id,
date = GETDATE()
WHERE id = #NextURLId
FETCH NEXT FROM GetURL_Cursor INTO #varContact_id
END;
CLOSE GetURL_Cursor
DEALLOCATE GetURL_Cursor
The code is supposed to retrieve a unique URL from a table (obp.URL), enter that URL in the Contacts table and then update the URL to indicated that the URL has been used. It seems to me that after the URL table is updated with 'used = 1' then the next iteration of the code should get a new URLId when I query for it.
However, when I run this code I get the same URL every time. No doubt I am missing something obvious but need some help to point it out.
As a side, if there is a set based solution for this, I'd be happy to hear it.
TIA
this
UPDATE obp.Contacts SET URL = (
SELECT url from obp.URL WHERE id = #NextURLId)
updates every row with the same. Add a proper WHERE clause like
WHERE contact_id=#varContact_id
About the requirement for this: I understand that you want to associate a Contact with a URL and that there is no logical rule for which with what. At first sight I would consider a match table the right way to do this. It feels better to me to put such associations into a seperate table, even if there is a strong belief in a 1:1-relationship between the two objects associated. obp.URL and obp.Contacts are dimensional tables (I assume/hope). Keeping the association in one different table requires one action if changes occur. In your model a change must be reflected in both those tables.
Here is an idea for such a table:
create table Contact_URL_match
(ID int identity (1,1)
,URL_id int not null unique
,contact_id int not null unique
,created datetime)
the unique constraints disallow insertion of the same URL or the same Contact_id twice. On each insert/update prior content is being checked for duplicates and if found the action is denied, thus uniqueness protected.
For manifesting new matches in a first large initial action try this (haven't tested, just an idea)
INSERT INTO
Contact_URL_match
(URL_id
,contact_id
,created)
SELECT
urls.id
,contacts.contact_id
,getdate()
FROM
(SELECT
DISTINCT(contact_id)
,ROW_NUMBER() over (ORDER BY contact_id asc) rn
FROM
obp.Contacts) contacts
INNER JOIN
(SELECT
id
,ROW_NUMBER() over (ORDER BY id) rn
FROM
obp.URL) urls
ON
contacts.rn=urls.rn
Within the subqueries this creates a row number in both the source tables based on the ORDER BY clauses. It then joins the resultsets of the subqueries by that rownumber which is an act of deliberate randomness. I hope you want that. The result of that join is inserted into the match table.
If later you want to manifest a single new association you could add WHERE clauses to the subqueries that specify what URL you want matched with what Contact. Before picking a URL or Contact check the match table with NOT EXISTS to make sure it is not used in there.
EDIT : syntax errors cleaned
Problem: to set row number in columns' value in Vertica.
For example:
Table T has two columns: Id, name
I want to use a script to add the row number in the value of the name. In mySQl, I run the following script to update:
set #i=0;
update T set name = (CONCAT(name, (#i:=#i+1)));
However, Vertica doesn't support variables.
Could you please provide a way to reach the target?
As Vertica supports window functions, something like this can be used to retrieve this data:
select name,
row_number() over (order by name) as rn
from T;
I am not sure how this could be moved into an UPDATE statement though - I don't have a vertica installation available:
update T
set name = name || tx.rn
from (
select id,
row_number() over (order by name) as rn
from T
) as tx
where tx.id = t.id;
I don't know if that qualifies as a "self-join" which isn't allowed. But maybe that points you into the right direction.
I've got the following rough structure:
Object -> Object Revisions -> Data
The Data can be shared between several Objects.
What I'm trying to do is clean out old Object Revisions. I want to keep the first, active, and a spread of revisions so that the last change for a time period is kept. The Data might be changed a lot over the course of 2 days then left alone for months, so I want to keep the last revision before the changes started and the end change of the new set.
I'm currently using a cursor and temp table to hold the IDs and date between changes so I can select out the low hanging fruit to get rid of. This means using #LastID, #LastDate, updates and inserts to the temp table, etc...
Is there an easier/better way to calculate the date difference between the current row and the next row in my initial result set without using a cursor and temp table?
I'm on sql server 2000, but would be interested in any new features of 2005, 2008 that could help with this as well.
Here is example SQL. If you have an Identity column, you can use this instead of "ActivityDate".
SELECT DATEDIFF(HOUR, prev.ActivityDate, curr.ActivityDate)
FROM MyTable curr
JOIN MyTable prev
ON prev.ObjectID = curr.ObjectID
WHERE prev.ActivityDate =
(SELECT MAX(maxtbl.ActivityDate)
FROM MyTable maxtbl
WHERE maxtbl.ObjectID = curr.ObjectID
AND maxtbl.ActivityDate < curr.ActivityDate)
I could remove "prev", but have it there assuming you need IDs from it for deleting.
If the identity column is sequential you can use this approach:
SELECT curr.*, DATEDIFF(MINUTE, prev.EventDateTime,curr.EventDateTime) Duration FROM DWLog curr join DWLog prev on prev.EventID = curr.EventID - 1
Hrmm, interesting challenge. I think you can do it without a self-join if you use the new-to-2005 pivot functionality.
Here's what I've got so far, I wanted to give this a little more time before accepting an answer.
DECLARE #IDs TABLE
(
ID int ,
DateBetween int
)
DECLARE #OID int
SET #OID = 6150
-- Grab the revisions, calc the datediff, and insert into temp table var.
INSERT #IDs
SELECT ID,
DATEDIFF(dd,
(SELECT MAX(ActiveDate)
FROM ObjectRevisionHistory
WHERE ObjectID=#OID AND
ActiveDate < ORH.ActiveDate), ActiveDate)
FROM ObjectRevisionHistory ORH
WHERE ObjectID=#OID
-- Hard set DateBetween for special case revisions to always keep
UPDATE #IDs SET DateBetween = 1000 WHERE ID=(SELECT MIN(ID) FROM #IDs)
UPDATE #IDs SET DateBetween = 1000 WHERE ID=(SELECT MAX(ID) FROM #IDs)
UPDATE #IDs SET DateBetween = 1000
WHERE ID=(SELECT ID
FROM ObjectRevisionHistory
WHERE ObjectID=#OID AND Active=1)
-- Select out IDs for however I need them
SELECT * FROM #IDs
SELECT * FROM #IDs WHERE DateBetween < 2
SELECT * FROM #IDs WHERE DateBetween > 2
I'm looking to extend this so that I can keep at maximum so many revisions, and prune off the older ones while still keeping the first, last, and active. Should be easy enough through select top and order by clauses, um... and tossing in ActiveDate into the temp table.
I got Peter's example to work, but took that and modified it into a subselect. I messed around with both and the sql trace shows the subselect doing less reads. But it does work and I'll vote him up when I get my rep high enough.