Autoincrement column based on query - sql-server

I'm trying a really novice MS Access form (linked to) with a SQL Server in the backend.
So I have a simple table for invoices created with
create table Invoice
(
ID INT not null primary key,
IssuerID int not null,
InvoiceID int not null,
CustomerID int not null
)
Maybe I even do not need an ID, since I can uniquely identify entries by pairs (IssuerID, InvoiceID).
All in all, I want to add invoice by IssuerID to some CustomerID and InvoiceID should naturally increment by 1. I can do something like
SELECT ISNULL(MAX(InvoiceID), 0) + 1 AS InvoiceID
FROM Invoice
WHERE IssuerID = #sp;
where #sp is some value.
In the UI logic, I have a combobox to pick IssuerID (ACCESS) and here my problem begin.
I've used a textbox for InvoiceID. I want it to update the table by the next ID (i.e. if IssuerID = 5, had 3 issued invoices, next invoice should be numbered 4).
I was not able to make direct SQL work, nor by using VBA in after_update on IssuerID combo.
So I've created a view (in SQL Server and linked to it) with all possible IssuerIds and their appropriate nextInvoiceIds.
SELECT
IssuerID, NextInvoiceID
FROM
(SELECT
Issuers.ID AS IssuerID,
ISNULL(MAX(Invoice.InvoiceID), 0) + 1 AS NextInvoiceID
FROM
Issuers
FULL OUTER JOIN
Invoice ON Issuers.ID = Invoice.IssuerID
GROUP BY
Issuers.ID) AS UNDATA
Next, I was able to pick nextInvoiceID, but now I need it to also update the actual column back.
Is it possible from the same textbox?
Should I instead fallback to update this (InvoiceID) with some trigger (or are there other auto-increment options available in SQL Server)?

Related

in-place update leads to forwarded records

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...

An Alternative to OFFSET... FETCH NEXT in SQL Server 2008

I have a posts table in SQL Server and I need to select (say) the first 10 rows ordered by the count of their upvotes, here is the DB script:
create database someforum
go
use someforum
go
create table users (
user_id int identity primary key,
username varchar(80) unique not null
);
create table posts (
post_id int identity primary key,
post_time datetime,
post_title nvarchar(32),
post_body nvarchar(255),
post_user int foreign key references users(user_id)
);
create table votes (
vote_id int identity primary key,
user_id int foreign key references users(user_id),
vote_type bit, --upvote=true downvote=false
post_id int foreign key references posts(post_id)
);
insert into users values ('foo'),('bar')
insert into posts values
(getdate(),N'a post by foo',N'hey',1),
(getdate(),N'a post by bar',N'hey!',2)
insert into votes values (1,0,1),(2,0,1),(1,1,2),(2,1,2) --first post downvoted by its poster (foo) and bar, second post was upvoted by both users
I need an efficient query to select the next top 10 rows from Posts based on count of upvotes. How can I achieve this in SQL Server 2008?
Important edit: I stupidly forgot to mention that I was using SQL Server 2008 R2 to which OFFSET... FETCH NEXT wasn't introduced yet. I also edited out what is currently irrelevant to my needs.
Here's what I wanted (without using the score column):
select top 10 p.post_title,sum(case when vote_type=1 then 1 else -1 end) as score
from posts p join votes v on p.post_id = v.post_id
group by p.post_title
And as to the alternative to OFFSET… FETCH NEXT, I found a great solution in DBA
There is no "best"; but a working command might involve select top 10 ... order by Score desc. I realize that your posts tables doesn't have a Score column (that aggregates and denormalizes the votes), but: you can change that
the OFFSET / FETCH clause
You could use a gridView object to display the results: this will allow users to sort on various columns with the minimum of code on your part and also allow pagination, the inclusion of numbered links at the bottom of the gridView allowing users to move through the list of results.
Using a gridView with 10 rows will allow the display of your top 10 and users will also have the option of moving through the rest of the sorted list.
1) You can calcucalte and filter by this query
SELECT * FROM (
SELECT *, COUNT(*) as upvotes FROM posts AS p INNER JOIN votes AS v ON (p.post_id = v.post_id) WHERE v.type = true
) as v_post OFFSET 10 ROWS
2) You can shift post by step count (10 at now) in the end of query: FETCH NEXT 10, FETCH NEXT 20 etc.

Cursor variable not updated

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

Merge query using two tables in SQL server 2012

I am very new to SQL and SQL server, would appreciate any help with the following problem.
I am trying to update a share price table with new prices.
The table has three columns: share code, date, price.
The share code + date = PK
As you can imagine, if you have thousands of share codes and 10 years' data for each, the table can get very big. So I have created a separate table called a share ID table, and use a share ID instead in the first table (I was reliably informed this would speed up the query, as searching by integer is faster than string).
So, to summarise, I have two tables as follows:
Table 1 = Share_code_ID (int), Date, Price
Table 2 = Share_code_ID (int), Share_name (string)
So let's say I want to update the table/s with today's price for share ZZZ. I need to:
Look for the Share_code_ID corresponding to 'ZZZ' in table 2
If it is found, update table 1 with the new price for that date, using the Share_code_ID I just found
If the Share_code_ID is not found, update both tables
Let's ignore for now how the Share_code_ID is generated for a new code, I'll worry about that later.
I'm trying to use a merge query loosely based on the following structure, but have no idea what I am doing:
MERGE INTO [Table 1]
USING (VALUES (1,23-May-2013,1000)) AS SOURCE (Share_code_ID,Date,Price)
{ SEEMS LIKE THERE SHOULD BE AN INNER JOIN HERE OR SOMETHING }
ON Table 2 = 'ZZZ'
WHEN MATCHED THEN UPDATE SET Table 1.Price = 1000
WHEN NOT MATCHED THEN INSERT { TO BOTH TABLES }
Any help would be appreciated.
http://msdn.microsoft.com/library/bb510625(v=sql.100).aspx
You use Table1 for target table and Table2 for source table
You want to do action, when given ID is not found in Table2 - in the source table
In the documentation, that you had read already, that corresponds to the clause
WHEN NOT MATCHED BY SOURCE ... THEN <merge_matched>
and the latter corresponds to
<merge_matched>::=
{ UPDATE SET <set_clause> | DELETE }
Ergo, you cannot insert into source-table there.
You could use triggers for auto-insertion, when you insert something in Table1, but that will not be able to insert proper Shared_Name - trigger just won't know it.
So you have two options i guess.
1) make T-SQL code block - look for Stored Procedures. I think there also is a construct to execute anonymous code block in MS SQ, like EXECUTE BLOCK command in Firebird SQL Server, but i don't know it for sure.
2) create updatable SQL VIEW, joining Table1 and Table2 to show last most current date, so that when you insert a row in this view the view's on-insert trigger would actually insert rows to both tables. And when you would update the data in the view, the on-update trigger would modify the data.

Computed column expression

I have a specific need for a computed column called ProductCode
ProductId | SellerId | ProductCode
1 1 000001
2 1 000002
3 2 000001
4 1 000003
ProductId is identity, increments by 1.
SellerId is a foreign key.
So my computed column ProductCode must look how many products does Seller have and be in format 000000. The problem here is how to know which Sellers products to look for?
I've written have a TSQL which doesn't look how many products does a seller have
ALTER TABLE dbo.Product
ADD ProductCode AS RIGHT('000000' + CAST(ProductId AS VARCHAR(6)) , 6) PERSISTED
You cannot have a computed column based on data outside of the current row that is being updated. The best you can do to make this automatic is to create an after-trigger that queries the entire table to find the next value for the product code. But in order to make this work you'd have to use an exclusive table lock, which will utterly destroy concurrency, so it's not a good idea.
I also don't recommend using a view because it would have to calculate the ProductCode every time you read the table. This would be a huge performance-killer as well. By not saving the value in the database never to be touched again, your product codes would be subject to spurious changes (as in the case of perhaps deleting an erroneously-entered and never-used product).
Here's what I recommend instead. Create a new table:
dbo.SellerProductCode
SellerID LastProductCode
-------- ---------------
1 3
2 1
This table reliably records the last-used product code for each seller. On INSERT to your Product table, a trigger will update the LastProductCode in this table appropriately for all affected SellerIDs, and then update all the newly-inserted rows in the Product table with appropriate values. It might look something like the below.
See this trigger working in a Sql Fiddle
CREATE TRIGGER TR_Product_I ON dbo.Product FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #LastProductCode TABLE (
SellerID int NOT NULL PRIMARY KEY CLUSTERED,
LastProductCode int NOT NULL
);
WITH ItemCounts AS (
SELECT
I.SellerID,
ItemCount = Count(*)
FROM
Inserted I
GROUP BY
I.SellerID
)
MERGE dbo.SellerProductCode C
USING ItemCounts I
ON C.SellerID = I.SellerID
WHEN NOT MATCHED BY TARGET THEN
INSERT (SellerID, LastProductCode)
VALUES (I.SellerID, I.ItemCount)
WHEN MATCHED THEN
UPDATE SET C.LastProductCode = C.LastProductCode + I.ItemCount
OUTPUT
Inserted.SellerID,
Inserted.LastProductCode
INTO #LastProductCode;
WITH P AS (
SELECT
NewProductCode =
L.LastProductCode + 1
- Row_Number() OVER (PARTITION BY I.SellerID ORDER BY P.ProductID DESC),
P.*
FROM
Inserted I
INNER JOIN dbo.Product P
ON I.ProductID = P.ProductID
INNER JOIN #LastProductCode L
ON P.SellerID = L.SellerID
)
UPDATE P
SET P.ProductCode = Right('00000' + Convert(varchar(6), P.NewProductCode), 6);
Note that this trigger works even if multiple rows are inserted. There is no need to preload the SellerProductCode table, either--new sellers will automatically be added. This will handle concurrency with few problems. If concurrency problems are encountered, proper locking hints can be added without deleterious effect as the table will remain very small and ROWLOCK can be used (except for the INSERT which will require a range lock).
Please do see the Sql Fiddle for working, tested code demonstrating the technique. Now you have real product codes that have no reason to ever change and will be reliable.
I would normally recommend using a view to do this type of calculation. The view could even be indexed if select performance is the most important factor (I see you're using persisted).
You cannot have a subquery in a computed column, which essentially means that you can only access the data in the current row. The only ways to get this count would be to use a user-defined function in your computed column, or triggers to update a non-computed column.
A view might look like the following:
create view ProductCodes as
select p.ProductId, p.SellerId,
(
select right('000000' + cast(count(*) as varchar(6)), 6)
from Product
where SellerID = p.SellerID
and ProductID <= p.ProductID
) as ProductCode
from Product p
One big caveat to your product numbering scheme, and a downfall for both the view and UDF options, is that we're relying upon a count of rows with a lower ProductId. This means that if a Product is inserted in the middle of the sequence, it would actually change the ProductCodes of existing Products with a higher ProductId. At that point, you must either:
Guarantee the sequencing of ProductId (identity alone does not do this)
Rely upon a different column that has a guaranteed sequence (still dubious, but maybe CreateDate?)
Use a trigger to get a count at insert which is then never changed.

Resources