How best to populate a subordinate numeric key [duplicate] - sql-server

This question already has answers here:
Database scheme, autoincrement
(2 answers)
Closed 3 years ago.
I have two tables: Items and Defects.
Each defect relates to one (and only one) item.
The PK for Items is an identity column (ItemID), which appears as a FK column in Defects, to relate the two tables.
The PK for Defects is an identity column (DefectID).
However there is also a subordinate numeric key in Defects called RefNo, which must be populated by a sequential number per ItemID, thus:
Defects
DefectID ItemID RefNo
1 1 1
2 1 2
3 1 3
4 2 1
5 2 2
6 3 1
7 4 1
8 3 2
9 1 4
What's the best way to populate the Ref column?
Currently the code I inherited accomplishes this in the front end which is obviously A Bad Idea.
I am starting to code an insert trigger (SQL Server 2008-R2) but wonder about atomicity and the need to potentially update more than one row when the trigger is called - and the likelihood of simultaneous inserts by different users to try and insert the same RefNo.
[Edit]
I'm maintaining an existing database and removing the RefNo column and repacing it with a value calculated on the fly is not an option.

You can use ROW_NUMBER as below to generate your refno-
WITH your_table(DefectID,ItemID,RefNo)
AS
(
SELECT 1,1,1 UNION ALL
SELECT 2,1,2 UNION ALL
SELECT 3,1,3 UNION ALL
SELECT 4,2,1 UNION ALL
SELECT 5,2,2 UNION ALL
SELECT 6,3,1 UNION ALL
SELECT 7,4,1 UNION ALL
SELECT 8,3,2 UNION ALL
SELECT 9,1,4
)
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ItemID ORDER BY DefectID) AS RefNo_new
FROM your_table
order by 2,1
Output-
DefectID ItemID RefNo RefNo_new
1 1 1 1
2 1 2 2
3 1 3 3
9 1 4 4
4 2 1 1
5 2 2 2
6 3 1 1
8 3 2 2
7 4 1 1

Related

Classifying rows into a grouping column that shows the data is related to prior rows

I have a set of data that I want to classify into groups based on a prior record id existing on the newer rows. The initial record of the group has a prior sequence id = 0.
The data is as follows:
customer id
sequence id
prior_sequence id
1
1
0
1
2
1
1
3
2
2
4
0
2
5
4
2
6
0
2
7
6
Ideally, I would like to create the following grouping column and yield the following results:
customer id
sequence id
prior sequence id
grouping
1
1
0
1
1
2
1
1
1
3
2
1
2
4
0
2
2
5
4
2
2
6
0
3
2
7
6
3
I've attempted to utilize island gap logic utilizing the ROW_NUMBER() function. However, I have been unsuccessful in doing so. I suspect the need here is more along the lines of a recursive CTE, which I am attempting at the moment.
I agree that a recursive CTE will do the job. Something like:
WITH reccte AS
(
/*query that determines starting point for recursion
*
* In this case we want all records with no prior_sequence_id
*/
SELECT
customer_id,
sequence_id,
prior_sequence_id,
/*establish grouping*/
ROW_NUMBER() OVER (ORDER BY sequence_id) as grouping
FROM yourtable
WHERE prior_sequence_id = 0
UNION
/*join the recursive CTe back to the table and iterate*/
SELECT
yourtable.customer_id,
yourtable.sequence_id,
yourtable.prior_sequence_id,
reccte.grouping
FROM reccte
INNER JOIN yourtable ON reccte.sequence_id = yourtable.prior_sequence_id
)
SELECT * FROM reccte;
It looks like you could use a simple correlated query, at least given your sample data:
select *, (
select Sum(Iif(prior_sequence_id = 0, 1, 0))
from t t2
where t2.sequence_id <= t.sequence_id
) Grouping
from t;
See Example Fiddle

SQLite Join Tables With Different Primary Key Values

I have two tables in SQLITE one table FastData records data at a high rate while the other table SlowData records data at a lower rate. FastData and SlowData share a primary key (PK) that represents time of data capture. As such the two tables could look like:
Fast Data Slow Data
Pk Value1 Pk Value2
2 1 1 1
3 2 4 2
5 3 7 3
6 4
7 5
9 6
I would like to create a Select statement that joins these two tables filling in the SlowData with the previous captured data.
Join Data
Pk Value1 Value2
2 1 1
3 2 1
5 3 2
6 4 2
7 5 3
9 6 3
You may try the following approach which uses row_number to determine the most recent entry as it relates to Pk as the ideal entry for Value2 after performing a left join.
SELECT
Pk,
Value1,
Value2
FROM (
SELECT
f.Pk,
f.Value1,
s.Value2,
ROW_NUMBER() OVER (
PARTITION BY f.Pk, f.Value1
ORDER BY s.Pk DESC
) rn
FROM
fast_data f
LEFT JOIN
slow_data s ON f.Pk >= s.Pk
) t
WHERE rn=1;
Pk
Value1
Value2
2
1
1
3
2
1
5
3
2
6
4
2
7
5
3
9
6
3
View working demo on DB Fiddle
You need a LEFT join of the tables and FIRST_VALUE() window function to pick Value2:
SELECT DISTINCT f.Pk, f.Value1,
FIRST_VALUE(s.Value2) OVER (PARTITION BY f.Pk ORDER BY s.Pk DESC) Value2
FROM FastData f LEFT JOIN SlowData s
ON s.Pk <= f.Pk;
See the demo.

MSSQL Can a column auto-increment only under certain circumstances

I have a table with 4 columns ID, c1, c2 and LOT. ID is the primary key. For every record when c1 is 5 I want to auto-generate a number for LOT which will be a sequence starting from 1 for each distinct value of c2.
So if c1 is not 5, LOT remains null. But if c1 is 5 then for every record where c2=1 I want to populate LOT with an auto-incrementing sequence starting from 1.
Ex:
ID c1 c2 LOT
1 3
2 5 1 1
3 5 1 2
4 5 1 3
5 4
Then do the same for a different value of c2. So if c2 is 2, have another bunch of auto-incrementing LOT numbers starting from 1:
ID c1 c2 LOT
6 3
7 5 2 1
8 5 1 4
9 5 2 2
10 5 2 3
We are using MSSQL 2014 Enterprise Ed. Would table-partitioning be useful or do I need to create special tables for each distinct value of C2?
not with an identity field, you can use a trigger instead.
There is no way of doing this using the Identity feature, however, consider using a Instead of trigger to manually manage the values like you want.
You could use the logic to generate LOT in a query or view:
SELECT ID, C1, C2,
CASE
WHEN C1<>5 OR C2 IS NULL THEN NULL
ELSE COUNT(*) OVER (PARTITION BY C1, C2 ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
END AS LOT
FROM D
ORDER BY ID
Given a table generated with (ID, C1, C2):
CREATE TABLE D (ID INT PRIMARY KEY IDENTITY, C1 INT, C2 INT)
INSERT D VALUES (3,NULL),
(5,1),
(5,1),
(5,1),
(4,NULL),
(3,NULL),
(5,2),
(5,1),
(5,2),
(5,2)
The query produces the output indicated above:
ID C1 C2 LOT
1 3
2 5 1 1
3 5 1 2
4 5 1 3
5 4
6 3
7 5 2 1
8 5 1 4
9 5 2 2
10 5 2 3
The statement used to generate LOT, COUNT(*) OVER (PARTITION BY C1, C2 ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, simply counts the number of rows before and including the current row where C1 and C2 are equal to the current row. For instance, for Row 4, the tuple (c1,c2)=(5,1) is observed in 3 records before and including that row (rows 2, 3, and 4), so LOT=3.
Thank you everyone for the suggestions to use a trigger (all up-voted). It turns out (as I mentioned in a comment above) an article that came up on the side-bar (SQL Server unique auto-increment column in the context of another column) shows a detailed construction of the proper INSTEAD OF INSERT trigger. The author mentions that it is "Not tested", and indeed there IS a slight error (a missing GROUP BY ParentEntityID in the WITH clause) but anybody copying the code would get an error that is obvious to fix. Probably not kosher to be correcting that post here, but the other question is 6 years old.

Partition by Quarterly Years

How can I add another column that would partition them quarterly (Jan-March, April-June, June-Sep) and then add another counter to keep track of the quarterly and define that Q1 2011 is not the same as Q1 2012. Essentially how would I add the Quarters and Tracker Column. I have looked at ROW_NUMBER(), NTILE functions but not sure how to combine them with months.
--- Period --- Quarters---Tracker
2012-05-06 2 1
2012-05-20 2 1
2012-06-03 2 1
2012-07-01 3 2
2012-08-12 3 2
2012-08-26 3 2
2012-09-09 3 2
2012-10-07 4 3
2012-10-21 4 3
2012-11-04 4 3
2012-11-18 4 3
2012-12-02 4 3
2012-12-16 4 3
2012-12-30 4 3
2013-01-13 1 4
2013-01-27 1 4
REALLY STUCK!
I put the quarter CASE logic in the table definition, but you could also put it in the query so you don't have to modify your table.
Create Table Blah
(
SampleDate Date Default(Convert(Date,Getdate())),
Quarters As ( Case
When Month(SampleDate) Between 1 And 3 Then 1
When Month(SampleDate) Between 4 And 6 Then 2
When Month(SampleDate) Between 7 And 9 Then 3
Else 4 End)
)
Insert Blah (SampleDate)
Select '2012-05-06'
Union All
Select '2012-05-20'
Union All
Select '2012-06-03'
Union All
Select '2012-07-01'
Union All
Select '2012-08-12'
Union All
Select '2012-09-09'
Union All
Select '2012-10-07'
Union All
Select '2012-11-04'
Union All
Select '2012-12-16'
Union All
Select '2013-01-13'
Union All
Select '2013-01-27'
Select *,
Dense_Rank() Over (Order By Year(SampleDate),Quarters) As Tracker
From Blah
So you want a simple column to represent your actual quarter?
2012-Q1, 2011-Q1, 2010-Q1 that you would like to use SQL Partitions on? Or you want 2 columns? One to be partitioned on, and another one to actually indicate the year?
Thinking about it, do you need a counter? Couldn't you just set up the other column to be the year?
so you would have 2 columns. One indicating the quarter, and the other the year
quarter year
1 2011
1 2012
1 2010

How to update group records for one field with increment number in Sybase?

I have a temp table which has two columns: one is Name and another RecordNumber. They looks like below:
Name RecordNumber Rownum
EMEA-1111-SCHD-1 0 1
EMEA-12362-SCHD-1 0 2
EMEA-12362-SCHD-1 0 3
EMEA-12362-SCHD-1 0 4
EMEA-12362-SCHD-1 0 5
EMEA-2191-SCHD-1 0 6
EMEA-2191-SCHD-1 0 7
EMEA-2191-SCHD-1 0 8
I need to update column "RecordNumber" with increment number starting with 1. Let say for EMEA-1111-SCHD-1 only one record, so RecordNumber should be updated to 1. For EMEA-12362-SCHD-1 four records, so RecordNumber should be updated to 1,2,3,4 accordingly. Basically, I need to have a result as:
Name RecordNumber Rownum
EMEA-1111-SCHD-1 1 1
EMEA-12362-SCHD-1 1 2
EMEA-12362-SCHD-1 2 3
EMEA-12362-SCHD-1 3 4
EMEA-12362-SCHD-1 4 5
EMEA-2191-SCHD-1 1 6
EMEA-2191-SCHD-1 2 7
EMEA-2191-SCHD-1 3 8
Is it possible to do it without cursor?
Thank you, Ed.
I added identity col rownum to make this records unique. Is any idea how to update result to have record number by group?
You could do this query only in Sybase IQ with analytical functions.
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc38151.1510/html/iqrefbb/BCGEEBHA.htm
I have't access to sybase IQ this time, so I can't check query, but I suppose the right query is something like
select name,
row_number() over (partition by name) as RecordNumber
from Table
AFAIK Sybase ASE has't this feature.
Update
I think you can create self join query like this
select t1.name,
t1.Rownum - t2.MinRowNum + 1
from Table as t1,
(select name, min (Rownum) as MinRowNum from Table group by name) as t2
where t1.name = t2.name
order by t1.name, t1.Rownum

Resources