How to round robin by UUID in SQL database? - sql

I have a list of agents that I want to assign tasks to using round robin.
agents table:
id
uuid1
uuid2
uuid3
uuid4
How to get rows of the table above in a round robin fashion?
Desired outcome:
uuid1 -> uuid2 -> uuid3 -> uuid4 -> uuid1 (repeat)
I tried ordering uuids then selecting the next one based on the previous uuid
SELECT id FROM agents ORDER BY id; // When there is no previous
SELECT id FROM agents WHERE id > 'uuid1' ORDER BY id; // After the first query
But I don't know how to repeat when I reach the last uuid (when uuid4 is retrieved and uuid1 must be selected again)

select nxt
from (
select
-- for every id, find the next item, if not exists, use the earliest (min)
id, coalesce(lead(id) over (order by id),(select min(id) from Tbl)) as nxt
from Tbl
) as Agents
where id = 'uuid4'

I don't have PostgreSQL handy right now, but this works under SQL Server and I don't see an obvious reason why it shouldn't under any other DBMS:
COALESCE(
(SELECT TOP 1 id FROM agents WHERE #current_id < id ORDER BY id),
(SELECT TOP 1 id FROM agents ORDER BY id)
)
So it simply tries to get the next id (the first argument of COALESCE), and if there is no next id gets the first id (the second argument of COALESCE).
Here is a full T-SQL demo, something similar can probably be done under PostgreSQL...
CREATE TABLE agents (
id uniqueidentifier PRIMARY KEY
);
INSERT INTO agents VALUES (NEWID()), (NEWID()), (NEWID()), (NEWID());
DECLARE #current_id uniqueidentifier;
DECLARE #i int = 0;
WHILE #i < 10 BEGIN
SET #current_id = COALESCE(
(SELECT TOP 1 id FROM agents WHERE #current_id < id ORDER BY id),
(SELECT TOP 1 id FROM agents ORDER BY id)
);
PRINT CONCAT('#current_id = ', #current_id);
SET #i = #i + 1;
END

You can combine two queries where the second is only run if the first one didn't return anything:
with next_agent as (
SELECT id
FROM agents
WHERE id > $1 -- the last ID retrieved or NULL if it's the first
ORDER BY id
limit 1
)
select *
from next_agent
union all
(
select *
from agents
where not exists (select * from next_agent)
order by id
limit 1
)
So if $1 is null (first call) or 'uuid4' the next_agent CTE will not return anything. And in that case the second part of the UNION in the outer query will be run picking the "first" row

Related

Duplicate SQL records to the same table with manually auto increment id

I'm trying to write a query which will return those records:
select *
from [CloneConfiguration]
where InstrumentId = 2
and insert them into the same table with changing the following columns:
Id - the new record will need a unique id number (because it is the primary key without that it defined as auto increment)
Instrument id - change the instrument id to another number (3 for example)
I tried the following query which doesn't work.
INSERT INTO [CloneConfiguration]
SELECT
MAX(Id) + 1, 3,
[SourceCCy1Id], [SourceCCy2Id], [SourceProviderId],
[TargetCCy1Id], [TargetCCy2Id], [TargetProviderId], [Remark]
FROM
[CloneConfiguration]
WHERE
InstrumentId =2
Error:
Column 'CloneConfiguration.SourceCCy1Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
You can do what you want in a single query by doing:
INSERT INTO [CloneConfiguration]
SELECT COALESCE(m.maxid + 1, 1), 3, [SourceCCy1Id], [SourceCCy2Id],
[SourceProviderId], [TargetCCy1Id], [TargetCCy2Id],
[TargetProviderId], [Remark]
FROM [CloneConfiguration] CROSS JOIN
(SELECT max(id) as maxid FROM CloneConfiguration) m
WHERE InstrumentId = 2 ;
If you are inserting multiple rows, then use row_number() as well:
INSERT INTO [CloneConfiguration]
SELECT COALESCE(m.maxid, 0) + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
3, [SourceCCy1Id], [SourceCCy2Id],
[SourceProviderId], [TargetCCy1Id], [TargetCCy2Id],
[TargetProviderId], [Remark]
FROM [CloneConfiguration] CROSS JOIN
(SELECT max(id) as maxid FROM CloneConfiguration) m
WHERE InstrumentId = 2 ;
That said, the correct solution is to define the id to be an identity column. Then the database takes care of assigning a unique id. Your queries also will not have race conditions. So, the above work if there is only one user, but can fail if there are multiple users.
This is assuming sql-server, but I guess you get the point anyway:
DECLARE #MAXID INT = (SELECT MAX(Id) FROM [CloneConfiguration]) -- You probably want to number from the highest Id regardless of InstrumentId
INSERT INTO [CloneConfiguration]
SELECT #MAXID + ROW_NUMBER() OVER(ORDER BY Id)
, 3
, [SourceCCy1Id]
, [SourceCCy2Id]
, [SourceProviderId]
, [TargetCCy1Id]
, [TargetCCy2Id]
, [TargetProviderId]
, [Remark]
FROM [CloneConfiguration]
WHERE InstrumentId=2
The idea is to first get the MAX(Id) currently in the table, and add a ROW_NUMBER based on the selected Id's.
By the way, it's also a good idea to name the columns you want to insert into:
INSERT INTO [CloneConfiguration] (Id, InstrumentId...)
...

Select rows base on Subset

I've a scenario where I need to write sql query base on result of other query.
Consider the table data:
id attribute
1 a
1 b
2 a
3 a
3 b
3 c
I want to write query to select id base on attribute set.
I mean first I need to check attribute of id 1 using this query:
select attribute from table where id = 1
then base on this result I need to select subset of attribute. like in our case 1(a,b) is the subset of 3(a,b,c). My query should return 3 on that case.
And if I want to check base on 2(a) which is the subset of 1(a,b) and 3(a,b,c), it should return 1 and 3.
I hope, it's understandable. :)
You could use this query.
Logic is simple: If there isn't any item in A and isn't in B --> A is subset of B.
DECLARE #SampleData AS TABLE
(
Id int, attribute varchar(5)
)
INSERT INTO #SampleData
VALUES (1,'a'), (1,'b'),
(2,'a'),
(3,'a'),(3,'b'),(3,'c')
DECLARE #FilterId int = 1
;WITH temp AS
(
SELECT DISTINCT sd.Id FROM #SampleData sd
)
SELECT * FROM temp t
WHERE t.Id <> #FilterId
AND NOT EXISTS (
SELECT sd2.attribute FROM #SampleData sd2
WHERE sd2.Id = #FilterId
AND NOT EXISTS (SELECT * FROM #SampleData sd WHERE sd.Id = t.Id AND sd.attribute = sd2.attribute)
)
Demo link: Rextester
I would compose a query for that in three steps: first I'd get the attributes of the desired id, and this is the query you wrote
select attribute from table where id = 1
Then I would get the number of attributes for the required id
select count(distinct attribute) from table where id = 1
Finally I would use the above results as filters
select id
from table
where id <> 1 and
attribute in (
select attribute from table where id = 1 /* Step 1 */
)
group by id
having count(distinct attribute) = (
select count(distinct attribute) from table where id = 1 /* Step 2 */
)
This will get you all the id's that have a number of attributes among those of the initially provided id equal to the number the initial id has.

Need help writing an SQL query to count non duplicate rows (not a distinct count)

I have a table like below. I'm trying to do a count of IDs that are not duplicated. I don't mean a distinct count. A distinct count would return a result of 7 (a, b, c, d, e, f, g). I want it to return a count of 4 (a, c, d, f). These are the IDs that do not have multiple type codes. I've tried the following queries but got counts of 0 (the result should be a count in the millions).
select ID, count (ID) as number
from table
group by ID
having count (ID) = 1
Select count (distinct ID)
From table
Having count (ID) = 1
ID|type code
a|111
b|222
b|333
c|444
d|222
e|111
e|333
e|555
f|444
g|333
g|444
thanks to #scaisEdge! The first query you provided gave me exactly what I'm looking for in the above question. Now that that's figured out my leaders have asked for it to be taken a step further to show the count of how many times there is an ID within a single type code. For example, we want to see
type code|count
111|1
222|1
444|2
There are 2 instances of IDs that have a single type code of 444 (c, f), there is one instance of an ID that has a single type code of 111 (a), and 222 (d). I've tried modifying the query as such, but have been coming across errors when running the query
select count(admin_sys_tp_cd) as number
from (
select cont_id from
imdmadmp.contequiv
group by cont_id
having count(*) =1) t
group by admin_sys_tp_cd
If you want the count Could be
select count(*) from (
select id from
my_table
group by id
having count(*) =1
) t
if you want the id
select id from
my_table
group by id
having count(*) =1
Hou about this you do a loop in a temporary table?:
select
*
into #control
from tablename
declare #acum as int
declare #code as char(3)
declare #id as char(1)
declare #id2 as int
select #acum=0
while exists (select* from #control)
begin
select #code = (select top 1 code from #control order by id)
select #id = (select top 1 id from #control order by id)
select #id2 =count(id) from #control where id in (select id from tablename where id = #id and code <> #code)
if #id2=0
begin
select #acum = #acum+1
end
delete #control
where id = #id --and code = #code
end
drop table #control
print #acum

Find gaps in auto incremented values

Imagine having a table as the one below:
create table test (
id int auto_increment,
some int,
columns int
)
And then this table get used alot. Rows are inserted and rows are deleted and over time there might be gaps in the number that once was auto incremented. As an example, if I at some point make the following query:
select top 10 id from test
I might get something like
3
4
6
7
9
10
13
14
18
19
How do I design a query that returns the missing values 1,2,5,8 etc?
The easiest way is to get ranges of missing values:
select (id + 1) as firstmissing, (nextid - 1) as lastmissing
from (select t.id, lead(id) over (order by id) as nextid
from test t
) t
where nextid is not null and nextid <> id + 1;
Note this uses the lead() function, which is available in SQL Server 2012+. You can do something similar with apply or a subquery in earlier versions. Here is an example:
select (id + 1) as firstmissing, (nextid - 1) as lastmissing
from (select t.id, tt.id as nextid
from test t cross apply
(select top 1 id
from test t2
where t2.id > t.id
order by id
) tt
) t
where nextid is not null and nextid <> id + 1;
Simple way is by using cte..
;WITH cte
AS (SELECT 1 id
UNION ALL
SELECT id + 1 id from cte
WHERE id < (SELECT Max(id)
FROM tablename))
SELECT *
FROM cte
WHERE id NOT IN(SELECT id
FROM tablename)
Note: this will start from 1. If you want start from the min value of your table just replace
"SELECT 1 id" to "SELECT Min(id) id FROM tablename"
Why does it matter? I'm not trying to be snarky, but this question is usually asked in the context of "I want to fill in the gaps" or "I want to compress my id values to be contiguous". In either case, the answer is "don't do it". In your example, there was at some point a row with id = 5. If you're going to do either of the above, you'll be assigning a different, unrelated set of business data that id. If there's anything that references the id external to your database, now you've just invented a problem that you didn't have before. The id should be treated as immutable and arbitrary for all intents and purposes. If you really require it to be gapless, don't use identity and never do a hard delete (i.e. if you need to deactivate a row, you need a column which says whether it's active or not).

How do I get first unused ID in the table?

I have to write a query wherein i need to allocate a ID (unique key) for a particular record which is not being used / is not being generated / does not exist in database.
In short, I need to generate an id for a particular record and show it on print screen.
E. g.:
ID Name
1 abc
2 def
5 ghi
So, the thing is that it should return ID=3 as the next immediate which is not being generated yet, and after this generation of the id, I will store this data back to database table.
It's not an HW: I am doing a project, and I have a requirement where I need to write this query, so I need some help to achieve this.
So please guide me how to make this query, or how to achieve this.
Thanks.
I am not able to add comments,, so thats why i am writing my comments here..
I am using MySQL as the database..
My steps would be like this:-
1) Retrieve the id from the database table which is not being used..
2) As their are no. of users (website based project), so i want no concurrency to happen,, so if one ID is generated to one user, then it should lock the database, until the same user recieves the id and store the record for that id.. After that, the other user can retrieve the ID whichever is not existing.. (Major requirement)..
How can i achive all these things in MySQL,, Also i suppose Quassnoi's answer will be worth,, but its not working in MySQL.. so plz explain the bit about the query as it is new to me.. and will this query work in MySQL..
I named your table unused.
SELECT id
FROM (
SELECT 1 AS id
) q1
WHERE NOT EXISTS
(
SELECT 1
FROM unused
WHERE id = 1
)
UNION ALL
SELECT *
FROM (
SELECT id + 1
FROM unused t
WHERE NOT EXISTS
(
SELECT 1
FROM unused ti
WHERE ti.id = t.id + 1
)
ORDER BY
id
LIMIT 1
) q2
ORDER BY
id
LIMIT 1
This query consists of two parts.
The first part:
SELECT *
FROM (
SELECT 1 AS id
) q
WHERE NOT EXISTS
(
SELECT 1
FROM unused
WHERE id = 1
)
selects a 1 is there is no entry in the table with this id.
The second part:
SELECT *
FROM (
SELECT id + 1
FROM unused t
WHERE NOT EXISTS
(
SELECT 1
FROM unused ti
WHERE ti.id = t.id + 1
)
ORDER BY
id
LIMIT 1
) q2
selects a first id in the table for which there is no next id.
The resulting query selects the least of these two values.
Depends on what you mean by "next id" and how it's generated.
If you're using a sequence or identity in the database to generate the id, it's possible that the "next id" is not 3 or 4 but 6 in the case you've presented. You have no way of knowing whether or not there were values with id of 3 or 4 that were subsequently deleted. Sequences and identities don't necessarily try to reclaim gaps; once they're gone you don't reuse them.
So the right thing to do is to create a sequence or identity column in your database that's automatically incremented when you do an INSERT, then SELECT the generated value.
The correct way is to use an identity column for the primary key. Don't try to look at the rows already inserted, and pick an unused value. The Id column should hold a number large enough that your application will never run out of valid new (higher) values.
In your description , if you are skipping values that you are trying to use later, then you are probably giving some meaning to the values. Please reconsider. You probably should only use this field as a look up (a reference) value from another table.
Let the database engine assign the next higher value for your ID. If you have more than one process running concurrently, you will need to use LAST_INSERT_ID() function to determine the ID that the database generated for your row. You can use LAST_INSERT_ID() function within the same transaction before you commit.
Second best (but not good!) is to use the max value of the index field plus one. You would have to do a table lock to manage the concurrency issues.
/*
This is a query script I wrote to illustrate my method, and it was created to solve a Real World problem where we have multiple machines at multiple stores creating transfer transactions in their own databases,
that are then synced to other databases on the store (this happens often, so getting the Nth free entry for the Nth machine should work) where the transferid is the PK and then those are synced daily to a MainFrame where the maximum size of the key (which is the TransactionID and StoreID) is limited.
*/
--- table variable declarations
/* list of used transaction ids (this is just for testing, it will be the view or table you are reading the transaction ids from when implemented)*/
DECLARE #SampleTransferIDSourceTable TABLE(TransferID INT)
/* Here we insert the used transaction numbers*/
DECLARE #WorkTable TABLE (WorkTableID INT IDENTITY (1,1), TransferID INT)
/*this is the same table as above with an extra column to help us identify the blocks of unused row numbers (modifying a table variable is not a good idea)*/
DECLARE #WorkTable2 TABLE (WorkTableID INT , TransferID INT, diff int)
--- Machine ID declared
DECLARE #MachineID INT
-- MachineID set
SET #MachineID = 5
-- put in some rows with different sized blocks of missing rows.
-- comment out the inserts after two to the bottom to see how it handles no gaps or make
-- the #MachineID very large to do the same.
-- comment out early rows to test how it handles starting gaps.
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 1 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 2 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 4 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 5 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 6 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 9 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 10 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 20 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 21 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 24 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 25 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 30 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 31 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 33 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 39 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 40 )
INSERT #SampleTransferIDSourceTable ( TransferID ) VALUES ( 50 )
-- copy the transaction ids into a table with an identiy item.
-- When implemented add where clause before the order by to limit to the local StoreID
-- Zero row added so that it will find gaps before the lowest used row.
INSERT #WorkTable (TransferID)
SELECT 0
INSERT #WorkTable (TransferID)
SELECT TransferID FROM #SampleTransferIDSourceTable ORDER BY TransferID
-- copy that table to the new table with the diff column
INSERT #WorkTable2
SELECT WorkTableID,TransferID,TransferID - WorkTableID
FROM #WorkTable
--- gives us the (MachineID)th unused ID or the (MachineID)th id beyond the highest id used.
IF EXISTS (
SELECT Top 1
GapStart.TransferID + #MachineID - (GapStart.diff + 1)
FROM #WorkTable2 GapStart
INNER JOIN #WorkTable2 GapEnd
ON GapStart.WorkTableID = GapEnd.WorkTableID - 1
AND GapStart.diff < GapEnd.diff
AND gapEnd.diff >= (#MachineID - 1)
ORDER BY GapStart.TransferID
)
SELECT Top 1
GapStart.TransferID + #MachineID - (GapStart.diff + 1)
FROM #WorkTable2 GapStart
INNER JOIN #WorkTable2 GapEnd
ON GapStart.WorkTableID = GapEnd.WorkTableID - 1
AND GapStart.diff < GapEnd.diff
AND gapEnd.diff >= (#MachineID - 1)
ORDER BY GapStart.TransferID
ELSE
SELECT MAX(TransferID) + #MachineID FROM #SampleTransferIDSourceTable
Should work under MySql.
SELECT TOP 100
T1.ID + 1 AS FREE_ID
FROM TABLE1 T1
LEFT JOIN TABLE2 T2 ON T2.ID = T1.ID + 1
WHERE T2.ID IS NULL
are you allowed to have a utility table? if so i would create a table like so:
CREATE TABLE number_helper (
n INT NOT NULL
,PRIMARY KEY(n)
);
Fill it with all positive 32 bit integers (assuming the id you need to generate is a positive 32 bit integer)
Then you can select like so:
SELECT MIN(h.n) as nextID
FROM my_table t
LEFT JOIN number_helper h ON h.n = t.ID
WHERE t.ID IS NULL
Haven't actually tested this but it should work.