Adding Random Id for each unique value in table - sql

I have the table like
ID RANDOM_ID
1 123
10 456
25 789
1 1112
55 1314
10 1516
I want the result to be like :
ID RANDOM_ID
1 123
10 456
25 789
1 123
55 1314
10 456
The same ID should have same random_ids. I'm using the update statement to generate the Random_IDs after creating the table.
CREATE TABLE [RANDOMID_TABLE]([ID] [int] NULL, [RANDOM_ID] [int] NULL)
GO
INSERT INTO [RANDOMID_TABLE] ([ID])
select distinct ABC_ID from RANDOMID_ABC
GO
******** This is the update statement for the RANDOM_ID column in
[RANDOMID_TABLE] table ************
UPDATE [RANDOMID_TABLE]
SET RANDOM_ID = abs(checksum(NewId()) % 1000000)
Is there something else that I need to add to the update statement?
Please advise.

Why would you use update for this? Just generate the values when you insert them:
insert into [RANDOMID_TABLE] (ID, RANDOM_ID)
select ABC_ID, abs(checksum(NewId()) % 1000000)
from RANDOMID_ABC
group by ABC_ID;
EDIT:
If your problem is collisions, then fix how you do the assignment. Just assign a number . . . randomly:
insert into [RANDOMID_TABLE] (ID, RANDOM_ID)
select ABC_ID, row_number() over (order by newid())
from RANDOMID_ABC
group by ABC_ID;
This is guaranteed to not return duplicates.

At a total guess, are you simpling wanting to UPDATE the table so that all the values of a specific ID to have the same value for Random_ID? Like this?
CREATE TABLE YourTable (ID int, Random_ID int);
INSERT INTO YourTable
VALUES(1 ,123),
(10,456),
(25,789),
(1 ,1112),
(55,1314),
(10,1516);
GO
WITH CTE AS(
SELECT ID,
Random_ID,
MIN(Random_ID) OVER (PARTITION BY ID) AS Min_Random_ID
FROM YourTable)
UPDATE CTE
SET Random_ID = Min_Random_ID;
GO
SELECT *
FROM YourTable;
GO
DROP TABLE YourTable;

Here is the script you need with use of temporary table (you need it to persist your random results for each unique ID):
DECLARE #Tbl TABLE (ID INT, RANDOM_ID INT)
INSERT #Tbl (Id) VALUES(1), (10), (25), (1), (55), (10)
SELECT Id, abs(checksum(NewId()) % 1000000) AS Random_Id INTO #distinctData FROM #Tbl GROUP BY Id
SELECT D.* FROM #Tbl T JOIN #distinctData D ON D.ID = T.ID
DROP TABLE #distinctData
Obviously, you don't need the first two rows where I create and initialize data table
Result:
Id Random_Id
1 354317
1 62026
10 532304
10 604768
25 874209
55 718643

You want one random value per ID. So one should think that the following would work:
with ids as
(
select distinct id
from randomid_table
)
, ids_with_rnd as
(
select id, abs(checksum(NewId()) % 1000000) as rnd
from ids
)
update randomid_table
set random_id =
(
select rnd
from ids_with_rnd
where ids_with_rnd.id = randomid_table.id
);
It doesn't however. SQL Server is somewhat buggy here and still creates different numbers for the same ID.
So, your best bet may be: do your update that does create different values (your original update statement). Then correct the data as follows:
update randomid_table
set random_id =
(
select min(random_id)
from randomid_table rt2
where rt2.id = randomid_table.id
);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=504236db66fba0f12dc7e407a51451f8

Related

Generate Row Count, increasing in 10,000 per row

I'm needing to return a generated ID where for each row it increases by 10,000.
For example, the ExpectedResult column in the below, and if there are more rows, it would increase by 10,000 each time.
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
)
insert into #temp
(
ID,
ExpectedResult
)
select
NEWID(),
10000
union
select
NEWID(),
20000
union
select
NEWID(),
30000
union
select
NEWID(),
40000
union
select
NEWID(),
50000
select * from #temp
order by ExpectedResult
drop table #temp
I've found the example below, but I'm not sure how to increase the count by 10,000 each time
ROW_NUMBER() OVER (ORDER BY (SELECT 100))
If you are using SQL Server 2012 or later (including SQL Server 2017), you could create a numbering sequence using CREATE SEQUENCE.
To create SEQUENCE with increment of 10000, add the clause INCREMENT BY.
For example:
CREATE SEQUENCE Test.CountBy1
START WITH 10000
INCREMENT BY 10000
For more information, please consult this SQL Server documentation on CREATE SEQUENCE:
https://learn.microsoft.com/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-2017
you can use cte to generate N number for guids. Below is a sample cte to generate 100 rows for your guid.
Drop table #temp
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
);
with cte as(
select newid() as new_id, 10000 as ctr
union all
select new_id, ctr + 10000 from cte where ctr/10000 < 100
)
insert into #temp
select * from cte option (MaxRecursion 0 );
select * from #temp;
You need to divide ROW_NUMBER() by 10000 and then multiply by 10000
You can write something like this
select *, rowNum10K = 10000 * (1 + (row_number() over (order by object_id)) / 10000)
from #temp
As suggested by Nick in the original question comments, have done this:
ROW_NUMBER() OVER (ORDER BY (SELECT 100))*10000
You can define the column as an identity to do this for you:
Create Table temp (
ID uniqueidentifier,
ExpectedResult int identity (10000, 10000)
);
insert into temp (ID)
select v.id
from (values (NEWID()), (NEWID()), (NEWID()), (NEWID()), (NEWID())) v(id);
Here is a db<>fiddle.

How to create an auto increment column that is segmented by an other column

I need to create a table that will contain a incremental id, but I would like the ids be automatically segmented according to an other column. Here is what I want :
CREATE TABLE dbo.MyTable (
myKey INT IDENTITY PRIMARY KEY,
category INT,
incrementalId INT
);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
SELECT *
FROM dbo.MyTable;
I would like this to display something like :
myKey category incrementalId
----------- ----------- -------------
1 100 1
2 200 1
3 100 2
4 100 3
5 100 4
6 200 2
Meaning I want the incrementalId to be automatically incremented per category and restart from 1 for any new category inserted. I want this to be done by itself on any inserts in the table (I don't want to have to remember to do that when I insert in this table).
I think this might be done with window functions and maybe a trigger, but I just can't figure how.
EDIT:
I would like the data to be persisted to avoid incrementalId to be shifted if data deletion happens. Also, ideally the same ID would not be re-given in the event of rows deletion (the same way that sequences or IDENTITY works)
Any idea ?
CREATE TABLE dbo.MyTable (
myKey INT IDENTITY PRIMARY KEY,
category INT,
incrementalId INT
);
GO
create table dbo.nextCategoryID (
category int,
nextidvalue int,
constraint PK_nextCategoryID primary key clustered( category, nextidvalue )
);
GO
create trigger numberByCategory on dbo.MyTable
after insert as
-- Automatically add any net new category
insert into dbo.nextCategoryID ( category, nextidvalue )
select distinct category, 1 as nextidvalue
from inserted
where not exists ( select * from dbo.nextCategoryID s
where s.category = inserted.category );
-- Number the new rows in each incoming category
with numberedrows as (
select
i.myKey,
i.category,
n.nextidvalue - 1 + row_number() over ( partition by i.category order by i.category ) as incrementalId
from inserted i
join dbo.nextCategoryID n on i.category = n.category
)
update m
set incrementalId = n.incrementalId
from dbo.MyTable m
join inserted i on m.myKey = i.myKey
join numberedrows n on n.myKey = i.myKey;
update dbo.nextCategoryID
set nextidvalue = 1 + ( select max( m.incrementalId )
from inserted i
join dbo.MyTable m on i.myKey = m.myKey
where i.category = nextCategoryID.category
)
where exists ( select *
from inserted i
where i.category = nextCategoryID.category
);
GO
-- Test data
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (100);
INSERT INTO dbo.MyTable (category) VALUES (200);
insert into dbo.MyTable (category)
values
( 200 ),
( 200 ),
( 100 ),
( 300 ),
( 400 ),
( 400 )
SELECT *
FROM dbo.MyTable;
You can easily achieved this via a trigger:
CREATE TRIGGER dbo.UpdateIncrementalID
ON dbo.MyTable
AFTER INSERT
AS
UPDATE x
SET incrementalId = ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey DESC)
FROM dbo.MyTable x
I think you don't need to add additional column 'IncrementalID' in your table.
You can make it in your select statement.
SELECT myKey,category,ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey )incrementalId
FROM MyTable
ORDER BY myKey
sample output.
Else you can create a view from your actual table.
CREATE VIEW dbo.VIEW_MyTable
AS
SELECT myKey,category,ROW_NUMBER() OVER(PARTITION BY category ORDER BY myKey )incrementalId
FROM MyTable
ORDER BY myKey
You can update the same table using below Update query
;with cte as (
select mykey, category, incrementalid, row_number() over (partition by category order by mykey,category) as rn from MyTable
)
update cte
set incrementalId = rn
Extending #Kannan's solution into a UDF that's called from a compute column:
create function dbo.fnsPartId(#mykey int)
returns int
as
begin
declare #Ret int
;
with
enum as
(
select mykey, category, incrementalid,
row_number() over (partition by category order by mykey, category) as rn
from MyTable
)
select #Ret = rn from enum where mykey = #mykey
return #Ret
end
And modify the table as:
CREATE TABLE dbo.MyTable (
myKey INT IDENTITY PRIMARY KEY,
category INT,
incrementalId AS ([dbo].[fnsPartId]([mykey]))
);
Try to create a default constraint on the column. Use a function to generate next value for the row as a default row returned by the function.
Please try this (added after insert trigger on this table)-
create trigger InsertIncrementalID
on dbo.MyTable
after insert
as
begin
update mt
set incrementalId = (select count(mt1.category) from dbo.MyTable mt1 where mt1.category = mt.category)
from dbo.MyTable mt
inner join inserted i on i.myKey = mt.myKey
end
Please remember two points while using trigger -
1. We are updating table from inside trigger so if you have any other trigger(after update) on this table, that trigger will be executed too.
2. While inserting multiple rows in this table with single select query, this trigger will be executed only once.

Find missing numbers in a column

I have this column in T-SQL:
1
2
3
7
10
have SQl a function for detect the missing numbers in the sequence 4,5,6 and 8,9
I have try
something like
if ( a-b >1 ) then we have a missing number
with coalesce but i dont understand .
Thanks by any orientation
You can try this:
DELCARE #a
SET #a = SELECT MIN(number) FROM table
WHILE (SELECT MAX(number) FROM table ) > #a
BEGIN
IF #a NOT IN ( SELECT number FROM table )
PRINT #a
SET #a=#a+1
END
The following query will identify where each sequence starts and the number that are missing:
select t.col + 1 as MissingStart, (nextval - col - 1) as MissingSequenceLength
from (select t.col,
(select min(t.col) from t t2 where t2.col > t.col) as nextval
from t
) t
where nextval - col > 1
This is using a correlated subquery to get the next value in the table.
I know this is a late answer, but here is a query that uses recursive table expressions to get the missing values between the minimum and maximum values in a table:
WITH CTE AS
(
--This is called once to get the minimum and maximum values
SELECT nMin = MIN(t.ID), MAX(t.ID) as 'nMax'
FROM Test t
UNION ALL
--This is called multiple times until the condition is met
SELECT nMin + 1, nMax
FROM CTE
WHERE nMin < nMax
)
--Retrieves all the missing values in the table.
SELECT c.nMin
FROM CTE c
WHERE NOT EXISTS
(
SELECT ID
FROM Test
WHERE c.nMin = ID
)
This was tested with the following schema:
CREATE TABLE Test
(
ID int NOT NULL
)
INSERT INTO Test
Values(1)
INSERT INTO Test
Values(2)
INSERT INTO Test
Values(3)
INSERT INTO Test
Values(7)
INSERT INTO Test
Values(10)

How to increment a second primary key column in a table automatically when a new entry is added for the first primary key column

I am trying to find a way to increment a second primary key column in a table automatically when a new entry is added for the first primary key column. I suppose an example would be best here so here goes.
Suppose I have a table:
CREATE TABLE T
(
SecNum INT NOT NULL,
EntryID INT NOT NULL,
Value FLOAT,
) CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED
(
[SecNum] ASC,
[EntryID] ASC
)
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (0, 10)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (0, 10)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
0 1 10
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (1, 20)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
0 1 10
1 0 20
This is possible using an INSTEAD OF trigger:
CREATE TRIGGER TriggerName
ON T
INSTEAD OF INSERT
AS
-- THIS TOP BIT IS OPTIONAL, IT WILL ALLOW ENTRY ID TO BE OVERRIDDEN IF
-- IT IS SUPPLIED TO THE INSERT AND WILL NOT VIOLATE THE PRIMARY KEY
IF NOT EXISTS
( SELECT 1
FROM T
INNER JOIN inserted i
ON i.SecNum = T.secNum
AND i.EntryID = T.EntryID
UNION
SELECT 1
FROM inserted
WHERE EntryID IS NULL
)
BEGIN
INSERT T (SecNum, EntryID, Value)
SELECT SecNum, EntryID, Value
FROM inserted
END
ELSE
-- IF OVERRIDE ABILITY IS NOT REQUIRED JUST USE THE BELOW INSERT
BEGIN
INSERT T (SecNum, EntryID, Value)
SELECT i.SecNum, COALESCE(LastID, 0), i.Value
FROM inserted I
LEFT JOIN
( SELECT SecNum, MAX(T.EntryID) + 1 [LastID]
FROM T
GROUP BY SecNum
) T
ON T.SecNum = i.SecNum
END
Example here
HOWEVER this is not very elegant. It could be worth asking is it really necessary? Could you get away with using a surrogate primary key, and use ROW_NUMBER() to create Entry ID's on the fly?
How about something like this:
INSERT INTO T (SecNum, Value, EntryId)
SELECT 0, 10, count(*)
FROM T WHERE SecNum = 0
It is not the cleanest solution and will perform pretty poorly too. But it should get the job done.
This is how to do it without storing the value in the table (I'm not sure why you want to store it)
TABLE
DECLARE #T TABLE
(
SecNum INT NOT NULL,
EntryID INT,
Value FLOAT
)
DATA
INSERT INTO #T
( SecNum, Value )
VALUES ( 0, 10 )
INSERT INTO #T
( SecNum, Value )
VALUES ( 0, 10 )
INSERT INTO #T
( SecNum, Value )
VALUES ( 1, 20 )
QUERY
SELECT SecNum,
ROW_NUMBER() OVER ( PARTITION BY value ORDER BY Value ) - 1 AS EntryID,
Value
FROM #T
RESULT
SecNum EntryID Value
0 0 10
0 1 10
1 0 20
If the EntryID changes with SecNum AND Value use this query:
SELECT SecNum,
ROW_NUMBER() OVER ( PARTITION BY Value,SecNum ORDER BY Value, SecNum ) - 1 AS EntryID,
Value
FROM #t
RESULT 2
SecNum EntryID Value
0 0 10
0 1 10
1 0 10
1 0 20
Your problem can be solved by using an instead of insert trigger
create trigger Trigger1 on T INSTEAD OF INSERT
as
begin
insert into T(SecNum,EntryID,Value)
select SecNum,
(select count(*) from T where SecNum = i.SecNum) as EntryID,
value
from inserted i
end

SQL Query to return rows where a list of numbers is between start and end values

There is a table in Oracle with the columns:
id | start_number | end_number
---+--------------+------------
1 | 100 | 200
2 | 151 | 200
3 | 25 | 49
4 | 98 | 99
5 | 49 | 100
There is a list of numbers (50, 99, 150).
I want an sql statement that returns all the ids where any of the numbers in the list of numbers is found equal to or between the start_number and the end_number.
Using the above example; 1, 4 and 5 should be returned.
1 - 150 is between or equal to 100 and 200
2 - none of the numbers are between or equal to 151 and 200
3 - none of the numbers are between or equal to 25 and 49
4 - 99 is between or equal to 98 and 99
5 - 50 and 99 are between or equal to 49 and 100
drop table TEMP_TABLE;
create table TEMP_TABLE(
THE_ID number,
THE_START number,
THE_END number
);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (1, 100, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (2, 151, 200);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (3, 25, 49);
insert into TEMP_TABLE(THE_ID, THE_START, THE_END) values (4, 98, 99);
insert into TEMP_TABLE(the_id, the_start, the_end) values (5, 49, 100);
The following is the solution I came up with based on the comments and answers below plus some additional research:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(SYS.DBMS_DEBUG_VC2COLL(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
This works too:
SELECT
*
from
TEMP_TABLE
where
EXISTS (select * from(
select column_value as id
from table(sys.ku$_vcnt(50,99,150))
)
where id
BETWEEN TEMP_TABLE.the_start AND TEMP_TABLE.the_end
)
Here is a full example:
create table #list (
number int
)
create table #table (
id int,
start_number int,
end_number int
)
insert into #list values(50)
insert into #list values(99)
insert into #list values(150)
insert into #table values(1,100,200)
insert into #table values(2,151,200)
insert into #table values(3,25,49)
insert into #table values(4,98,99)
insert into #table values(5,49,100)
select distinct a.* from #table a
inner join #list l --your list of numbers
on l.number between a.start_number and a.end_number
drop table #list
drop table #table
You'll simply need to remove the code about #table (create, insert and drop) and put your table in the select.
It partly depends on how your are storing your list of numbers. I'll assume that they're in another table for now, as even then you have many options.
SELECT
*
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourList WHERE number BETWEEN yourTable.start_number AND yourTable.end_number)
Or...
SELECT
*
FROM
yourTable
INNER JOIN
yourList
ON yourList.number BETWEEN yourTable.start_number AND yourTable.end_number
Both of those are the simplest expressions, and work well for small data sets. If your list of numbers is relatively small, and your original data is relatively large, however, this may not scale well. This is because both of the above scan the whole of yourTable and then check each record against yourList.
What may be preferable is to scan the list, and then attempt to use indexes to check against the original data. This would require you to be able to reverse the BETWEEN statement to yourTable.start_number BETWEEN x and y
This can only be done if you know the maximum gap between start_number and end_number.
SELECT
*
FROM
yourList
INNER JOIN
yourTable
ON yourTable.end_number >= yourList.number
AND yourTable.start_number <= yourList.number
AND yourTable.start_number >= yourList.number - max_gap
To achieve this I would store the value of max_gap in another table, and update it as the values in yourTable change.
You will want to create a temporary table to hold your numbers, if the numbers aren't already in one. Then it becomes relatively simple:
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN TempTable tt --your list of numbers
ON tt.number Between mt.start_number and mt.end_number
To create the table based on an array of passed values, you can use table definitions in your procedure. I'm light on Oracle syntax and don't have TOAD handy, but you should be able to get something like this to work:
CREATE OR REPLACE PROCEDURE FindIdsFromList
AS
DECLARE
TYPE NumberRecord IS RECORD (Number int NOT NULL)
TYPE NumberList IS TABLE OF NumberRecord;
NumberList myNumberList;
BEGIN
myNumberList := (50,99,150);
SELECT DISTINCT mt.ID FROM MyTable mt
INNER JOIN myNumberList nt --your list of numbers
ON nt.Number Between mt.start_number and mt.end_number
END