Select latest record with some more conditions - sql

ID Level Effective Date ExpirationDate
000012-12 2 12/01/2005 NULL
000012-12 1 12/01/2005 NULL
000012-12 2 12/01/2005 01/01/2009
000012-A12 2 10/01/1994 11/30/2005
000012-A12 2 01/01/1999 11/30/2005
000012-A12 2 09/01/2001 11/30/2005
000012-A12 1 12/01/2005 12/31/2007
Only most current Records will be fetched. It means in the above scenario
Exp date - If null the record is still active.
If greater then current time stamp, its future exp date , which means still active.
If less then current time stamp , then terminated.
Most current is the most active or latest terminated record. If it has active and terminated then only active will be shown. Else last terminated record.
One ID can have 2 rows for same effective date and exp date but multiple levels. So in that case we would need to select only 1 record for level one.
So as per the data set above below is the intended output
Output
000012-12 1 12/01/2005 NULL
000012-A12 2 12/01/2005 01/01/2009
Please help
Thomas. Please look into the following data set.
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'1994-10-01',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'1999-01-01',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',2,'2001-09-01',NULL );
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000872-A24',1,'2003-01-01','2007-12-31');
When you run the query it should give
000872-A24 2 09/01/2001 NULL
but now it returns
000872-A24 1 01/01/2003 12/31/2007

It is difficult to provide an answer without knowing the database product.
1. if there is no auto_increment/identity column
2. and if there is no other primary key (which is a bad idea obviously)
3. and if the given database product supports `CURRENT_TIMESTAMP` (each DBMS will likely have some equivalent to the current date and time)
4. and if the target date by which you measure "latest" is the current date and time
Select Id, Level
From Table As T
Where T. EffectiveDate = (
Select Max(T2.EffectiveDate)
From Table As T2
Where T2.ID = T.ID
And ( T2.EffectiveDate Is Null
Or (
CURRENT_TIMESTAMP >= T2.EffectiveDate
And CURRENT_TIMESTAMP <= T2.ExpirationDate
)
)
)
You will note a number of caveats in my answer. That is an indicatation that we need more information:
What database product and version?
Is there an auto_incrementing, unique key on the table?
How does the Level fit into the results you want? (Please expand your sample data to include edge cases).
What should happen if the current date and time is prior to the effective date that has a null expiration date?
EDIT
Now that we know you are using SQL Server 2008, that makes the solution easier:
If object_id('tempdb..#Test') is not null
Drop Table #Test;
GO
Create Table #Test (
PkCol int not null identity(1,1) Primary Key
, Id varchar(50) not null
, Level int not null
, EffectiveDate datetime not null
, ExpirationDate datetime null
);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',2,'12/01/2005',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',1,'12/01/2005',NULL);
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-12',2,'12/01/2005','01/01/2009');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'10/01/1994','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'01/01/1999','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',2,'09/01/2001','11/30/2005');
Insert #Test( Id, Level, EffectiveDate, ExpirationDate ) Values ('000012-A12',1,'12/01/2005','12/31/2007');
With Items As
(
Select PkCol, Id, Level, EffectiveDate, ExpirationDate
, Row_Number() Over ( Partition By Id
Order By EffectiveDate Desc, Coalesce(ExpirationDate,'99991231') Desc, Level Asc ) As Num
From #Test
)
Select PkCol, Id, Level, EffectiveDate, ExpirationDate
From Items
Where Num = 1
In your sample output, you have the combination ('000012-A12',2,'12/01/2005','01/01/2009') which does not appear in your original data.
I'm using two features that were added in SQL Server 2005: common-table expressions and ranking functions. The common-table expression Item acts like a in-place view or query. The ranking function Row_Number is where the real magic happens. As the name implies, it returns a sequential list of numbers ordered by the Order By clause. However, it also restarts numbering for each Id value (that's the Partition By bit). By filtering on Num = 1, I'm returning the "top" value for each Id.

Related

Compare a single-column row-set with another single-column row set in Oracle SQL

Is there any Oracle SQL operator or function, which compares 2 result sets whether they are the exact same or not. Currently my idea is to use MINUS operator in both directions, but I am looking for a better and performanter solution to achieve. The one result set is fixed (see below), the other depends on the records.
Very important: I am not allowed to change the schema and structure. So CREATE TABLE and CREATE TYPE etc. are not allowed here for me. Also important that oracle11g version is used where the solution must be found.
The shema for SQL Fiddle is:
CREATE TABLE DETAILS (ID INT, MAIN_ID INT, VALUE INT);
INSERT INTO DETAILS VALUES (1,1,1);
INSERT INTO DETAILS VALUES (2,1,2);
INSERT INTO DETAILS VALUES (3,1,3);
INSERT INTO DETAILS VALUES (4,1,4);
INSERT INTO DETAILS VALUES (5,2,1);
INSERT INTO DETAILS VALUES (6,2,2);
INSERT INTO DETAILS VALUES (7,3,1);
INSERT INTO DETAILS VALUES (7,3,2);
Now this is my SQL query for doing the job well (selects MAIN_IDs of those, whose 'VALUE's are exactly the same as the given lists'):
SELECT DISTINCT D.MAIN_ID FROM DETAILS D WHERE NOT EXISTS
(SELECT VALUE FROM DETAILS WHERE MAIN_ID=D.MAIN_ID
MINUS
SELECT * FROM TABLE(SYS.ODCINUMBERLIST(1, 2)))
AND NOT EXISTS
(SELECT * FROM TABLE(SYS.ODCINUMBERLIST(1, 2))
MINUS
SELECT VALUE FROM DETAILS WHERE MAIN_ID=D.MAIN_ID)
The SQL Fiddle link: http://sqlfiddle.com/#!4/25dde/7/0
If you use a collection (rather than a VARRAY) then you can aggregate the values into a collection and directly compare two collections:
CREATE TYPE int_list AS TABLE OF INT;
Then:
SELECT main_id
FROM details
GROUP BY main_id
HAVING CAST( COLLECT( value ) AS int_list ) = int_list( 1, 2 );
Outputs:
| MAIN_ID |
| ------: |
| 2 |
| 3 |
db<>fiddle here
Update
Based on your expanded fiddle in comments, you can use:
SELECT B.ID
FROM BUSINESS_DATA B
INNER JOIN BUSINESS_NAME N
ON ( B.NAME_ID=N.ID )
WHERE N.NAME='B1'
AND EXISTS (
SELECT business_id
FROM ORDERS O
LEFT OUTER JOIN TABLE(
SYS.ODCIDATELIST( DATE '2021-01-03', DATE '2020-04-07', DATE '2020-05-07' )
) d
ON ( o.orderdate = d.COLUMN_VALUE )
WHERE O.BUSINESS_ID=B.ID
GROUP BY business_id
HAVING COUNT( CASE WHEN d.COLUMN_VALUE IS NULL THEN 1 END ) = 0
AND COUNT( DISTINCT o.orderdate )
= ( SELECT COUNT(DISTINCT COLUMN_VALUE) FROM TABLE( SYS.ODCIDATELIST( DATE '2021-01-03', DATE '2020-04-07', DATE '2020-05-07' ) ) )
)
(Note: Do not implicitly create dates from strings; it will cause the query to fail, without there being any changes to the query text, if a user changes their NLS_DATE_FORMAT session parameter. Instead use TO_DATE with an appropriate format model or a DATE literal.)
db<>fiddle here

How to insert a column which sets unique id based on values in another column (SQL)?

I will create table where I will insert multiple values for different companies. Basically I have all values that are in the table below but I want to add a column IndicatorID which is linked to IndicatorName so that every indicator has a unique id. This will obviously not be a PrimaryKey.
I will insert the data with multiple selects:
CREATE TABLE abc
INSERT INTO abc
SELECT company_id, 'roe', roevalue, metricdate
FROM TABLE1
INSERT INTO abc
SELECT company_id, 'd/e', devalue, metricdate
FROM TABLE1
So, I don't know how to add the IndicatorID I mentioned above.
EDIT:
Here is how I populate my new table:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT [the ID that I need], 'NI_3y' as 'Indicator', t.Company, avg(t.ni) over (partition by t.Company order by t.reportdate rows between 2 preceding and current row) as 'ni_3y',
t.reportdate
FROM table t
LEFT JOIN IndicatorIDs i
ON i.Indicator = roe3 -- the part that is not working if I have separate indicatorID table
I am going to insert different indicators for the same companies. And I want indicatorID.
Your "indicator" is a proper entity in its own right. Create a table with all indicators:
create table indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then, use the id only in this table. You can look up the value in the reference table.
Your inserts are then a little more complicated:
INSERT INTO indicators (indicator)
SELECT DISTINCT roevalue
FROM table1 t1
WHERE NOT EXISTS (SELECT 1 FROM indicators i2 WHERE i2.indicator = t1.roevalue);
Then:
INSERT INTO ABC (indicatorId, companyid, value, date)
SELECT i.indicatorId, t1.company, v.value, t1.metricdate
FROM table1 t1 CROSS APPLY
(VALUES ('roe', t1.roevalue), ('d/e', t1.devalue)
) v(indicator, value) JOIN
indicators i
ON i.indicator = v.indicator;
This process is called normalization and it is the typical way to store data in a database.
DDL and INSERT statement to create an indicators table with a unique constraint on indicator. Because the ind_id is intended to be a foreign key in the abc table it's created as a non-decomposable surrogate integer primary key using the IDENTITY property.
drop table if exists test_indicators;
go
create table test_indicators (
ind_id int identity(1, 1) primary key not null,
indicator varchar(20) unique not null);
go
insert into test_indicators(indicator) values
('NI'),
('ROE'),
('D/E');
The abc table depends on the ind_id column from indicators table as a foreign key reference. To populate the abc table company_id's are associated with ind_id's.
drop table if exists test_abc
go
create table test_abc(
a_id int identity(1, 1) primary key not null,
ind_id int not null references test_indicators(ind_id),
company_id int not null,
val varchar(20) null);
go
insert into test_abc(ind_id, company_id)
select ind_id, 102 from test_indicators where indicator='NI'
union all
select ind_id, 103 from test_indicators where indicator='ROE'
union all
select ind_id, 104 from test_indicators where indicator='D/E'
union all
select ind_id, 103 from test_indicators where indicator='NI'
union all
select ind_id, 105 from test_indicators where indicator='ROE'
union all
select ind_id, 102 from test_indicators where indicator='NI';
Query to get result
select i.ind_id, a.company_id, i.indicator, a.val
from test_abc a
join test_indicators i on a.ind_id=i.ind_id;
Output
ind_id company_id indicator val
1 102 NI NULL
2 103 ROE NULL
3 104 D/E NULL
1 103 NI NULL
2 105 ROE NULL
1 102 NI NULL
I was finally able to find the solution for my problem which seems to me very simple, although it took time and asking different people about it.
First I create my indicators table where I assign primary key for all indicators I have:
CREATE TABLE indicators (
indicator_id int identity(1, 1) primary key,
indicator varchar(255)
);
Then I populate easy without using any JOINs or CROSS APPLY. I don't know if this is optimal but it seems as the simplest choice:
INSERT INTO table(IndicatorID, Indicator, Company, Value, Date)
SELECT
(SELECT indicator_id from indicators i where i.indicator = 'NI_3y) as IndicatorID,
'NI_3y' as 'Indicator',
Company,
avg(ni) over (partition by Company order by reportdate rows between 2 preceding and current row) as ni_3y,
reportdate
FROM TABLE1

stuck in a problem statement which consist of comparing data for two tables

PFB the problem statement .
I tried with dense_rank ,keep function but somehow i am not able to crack it. can anyone please help . This is small data set for problem representation . Original tables have several million rows.
scripts for data set:
CREATE TABLE TRANSACTION (
ITEM VARCHAR2(25 BYTE),
LOCATION NUMBER(10,0),
TRAN_DATE DATE,
POST_DATE DATE
)
Insert into TRANSACTION (ITEM,LOCATION,TRAN_DATE,POST_DATE) values ('13252099',473,to_date('09-JUL-18','DD-MON-RR'),to_date('09-JUL-18','DD-MON-RR'));
Insert into TRANSACTION (ITEM,LOCATION,TRAN_DATE,POST_DATE) values ('13252099',473,to_date('25-JUL-18','DD-MON-RR'),to_date('25-JUL-18','DD-MON-RR'));
Insert into TRANSACTION (ITEM,LOCATION,TRAN_DATE,POST_DATE) values ('13252098',470,to_date('09-JUL-18','DD-MON-RR'),to_date('09-JUL-18','DD-MON-RR'));
Insert into TRANSACTION (ITEM,LOCATION,TRAN_DATE,POST_DATE) values ('13252098',470,to_date('28-JUL-18','DD-MON-RR'),to_date('28-JUL-18','DD-MON-RR'));
CREATE TABLE RETAIL_DESC (
ITEM VARCHAR2(25 BYTE),
LOC NUMBER(10,0),
UNIT_RETAIL NUMBER(20,4),
ACTION_DATE DATE
)
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252099',473,379.97,to_date('09-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252099',473,299.97,to_date('22-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252099',473,0.01,to_date('19-AUG-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252099',473,379.97,to_date('25-AUG-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252098',470,500.18,to_date('08-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252098',470,299.97,to_date('09-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252098',470,0.01,to_date('19-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252098',470,379.97,to_date('24-JUL-18','DD-MON-RR'));
Insert into RETAIL_DESC (ITEM,LOC,UNIT_RETAIL,ACTION_DATE) values ('13252098',470,300,to_date('24-JUL-18','DD-MON-RR'));
enter image description here
Note : I am using oracle 11g database .
1.Table Retail_desc contains unit_retail(price) for a item for a particular location. Action_date is the date at which unit_retail for that item gets updated to new price for that particular location .
an item/location combination can have multiple action_date based on days there price gets updated to new prices .
Table transaction contains date (tran_date) on which item was sold from a particular location to a customer (post_date is same as tran_date- can ignore it).
i want to know the unit_retail (price) of the item which was sold to customer in a particular location .
additional comments:
1. for each item/location combination i want to fetch the unit retail which item/loc combination has during time of transaction (tran_date)
2. action_date is date at which unit_retail for item/location changes with new retail.
3. if unit retail changes multiple times on a single action_date , then fetch the retail which has lower price.
You need a subquery with join and row_number:
select item, loc, unit_retail, tran_date
from (
select
r.item, r.loc, r.unit_retail, t.tran_date
, row_number() over (partition by r.item, r.loc, t.tran_date order by r.action_date desc, r.unit_retail) as rn
from transaction t
inner join retail_desc r on
t.item = r.item
and t.location = r.loc
where t.tran_date >= r.action_date
) t
where rn = 1
order by item desc, loc desc, tran_date
Output for your sample data:
ITEM LOC UNIT_RETAIL TRAN_DATE
13252099 473 379,97 09.07.2018 00:00:00
13252099 473 299,97 25.07.2018 00:00:00
13252098 470 299,97 09.07.2018 00:00:00
13252098 470 300 28.07.2018 00:00:00
I think first of a correlated subquery for this:
select t.*,
(select min(rd.unit_retail) keep (dense_rank first order by rd.acction_date desc)
from retail_desc rd
where rd.loc = t.location and rd.item = t.item and
rd.action_date <= t.post_date
) as unit_retail
from transaction t;
However, it might be better to use lead() and join:
select t.*, rd.unit_retail
from transaction t left join
(select rd.*,
lead(rd.action_date) over (partition by item, loc order by action_date) as next_action_date
from retail_desc rd
) rd
on t.location = rd.loc and t.item = rd.item and
t.post_date >= rd.action_date and
(t.post_date < rd.next_action_date or rd.next_action_date is null);
dense_rank() has no obvious connection to this problem.

Drop rows identified within moving time window

I have a dataset of hospitalisations ('spells') - 1 row per spell. I want to drop any spells recorded within a week after another (there could be multiple) - the rationale being is that they're likely symptomatic of the same underlying cause. Here is some play data:
create table hif_user.rzb_recurse_src (
patid integer not null,
eventdate integer not null,
type smallint not null
);
insert into hif_user.rzb_recurse_src values (1,1,1);
insert into hif_user.rzb_recurse_src values (1,3,2);
insert into hif_user.rzb_recurse_src values (1,5,2);
insert into hif_user.rzb_recurse_src values (1,9,2);
insert into hif_user.rzb_recurse_src values (1,14,2);
insert into hif_user.rzb_recurse_src values (2,1,1);
insert into hif_user.rzb_recurse_src values (2,5,1);
insert into hif_user.rzb_recurse_src values (2,19,2);
Only spells of type 2 - within a week after any other - are to be dropped. Type 1 spells are to remain.
For patient 1, dates 1 & 9 should be kept. For patient 2, all rows should remain.
The issue is with patient 1. Spell date 9 is identified for dropping as it is close to spell date 5; however, as spell date 5 is close to spell date 1 is should be dropped therefore allowing spell date 9 to live...
So, it seems a recursive problem. However, I've not used recursive programming in SQL before and I'm struggling to really picture how to do it. Can anyone help? I should add that I'm using Teradata which has more restrictions than most with recursive SQL (only UNION ALL sets allowed I believe).
It's a cursor logic, check one row after the other if it fits your rules, so recursion is the easiest (maybe the only) way to solve your problem.
To get a decent performance you need a Volatile Table to facilitate this row-by-row processing:
CREATE VOLATILE TABLE vt (patid, eventdate, exac_type, rn, startdate) AS
(
SELECT r.*
,ROW_NUMBER() -- needed to facilitate the join
OVER (PARTITION BY patid ORDER BY eventdate) AS rn
FROM hif_user.rzb_recurse_src AS r
) WITH DATA ON COMMIT PRESERVE ROWS;
WITH RECURSIVE cte (patid, eventdate, exac_type, rn, startdate) AS
(
SELECT vt.*
,eventdate AS startdate
FROM vt
WHERE rn = 1 -- start with the first row
UNION ALL
SELECT vt.*
-- check if type = 1 or more than 7 days from the last eventdate
,CASE WHEN vt.eventdate > cte.startdate + 7
OR vt.exac_type = 1
THEN vt.eventdate -- new start date
ELSE cte.startdate -- keep old date
END
FROM vt JOIN cte
ON vt.patid = cte.patid
AND vt.rn = cte.rn + 1 -- proceed to next row
)
SELECT *
FROM cte
WHERE eventdate - startdate = 0 -- only new start days
order by patid, eventdate
I think the key to solving this is getting the first date more than 7 days from the current date and then doing a recursive subquery:
with rrs as (
select rrs.*,
(select min(rrs2.eventdate)
from hif_user.rzb_recurse_src rrs2
where rrs2.patid = rrs.patid and
rrs2.eventdate > rrs.eventdate + 7
) as eventdate7
from hif_user.rzb_recurse_src rrs
),
recursive cte as (
select patid, min(eventdate) as eventdate, min(eventdate7) as eventdate7
from hif_user.rzb_recurse_src rrs
group by patid
union all
select cte.patid, cte.eventdate7, rrs.eventdate7
from cte join
hif_user.rzb_recurse_src rrs
on rrs.patid = cte.patid and
rrs.eventdate = cte.eventdate7
)
select cte.patid, cte.eventdate
from cte;
If you want additional columns, then join in the original table at the last step.

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.