Generating a Sequential Client Number - sql

I have a client based system that is needing a sequential client number in the form of the following.
First client would get A001, and then each new client through to A999. Once it hits A999, it would continue to B001-B999, and so on until Z001-Z999, when it would reset to AA001-AA999 and so on through the alphabet.
Does anyone see a way of how this could be achieved?

This will give you the exact numbers you asked for from A001 to ZZ999. If you want more numbers than that you will need to add logic for a third letter, etc. Note that you aren't getting 1000 numbers per letter, which makes things slightly more awkward.
WITH Numbers AS (
SELECT 1 AS number
UNION ALL
SELECT number + 1 AS number FROM Numbers WHERE number < 701298)
SELECT
number,
CASE WHEN number > 25974 THEN CHAR(64 + (number - 1) / 25974) ELSE '' END --This is the first letter (optional)
+ CHAR(65 + ((number - 1) / 999) % 26) --This is the second letter
+ FORMAT(CASE WHEN number < 1000 THEN number ELSE CASE WHEN number % 999 = 0 THEN 999 ELSE number % 999 END END, 'd3') --This is the three digit number
AS client_id
FROM
Numbers
OPTION (MAXRECURSION 0);
The Numbers CTE is just to get a suitable number of numbers (1 - 701,298). Once I have them I need to find the boundaries when the second letter changes (every 999 numbers) or the first letter changes (every 26 * 999 = 25974 numbers). Note that the first letter is suppressed until needed.
This gives you 27 * 26 * 999 client ids (the first letter can be blank or A-Z = so 27 options, the second letter can be A-Z = 26 options, the number can be 001-999 = 999 options). That's a grand total of 701,298 client ids.
I would suggest either using an IDENTITY column, or a SEQUENCE to get the "internal" id (which would be a primary key candidate), and then use a function to calculate the client id from this number. That's safer for multiple users, etc. You could use a calculated column, but that's a pretty big overhead?

I'd use simple integers as the key and a stored procedure (or calculated column) which translates to your desired format.
I't essentially a numeric operation, check this SQL which calculates the format.
It does assume that you have no more than 2 letters in the beginning, so number of clients is under 26 * 26 * 1000.
select tmp.num as client_num, CONCAT(
CASE WHEN tmp.num < 26000 THEN '' ELSE CHAR(ASCII('A') - 1 + (tmp.num / 26000)) END,
CHAR (ASCII('A') + (tmp.num / 1000) % 26),
RIGHT('000'+CAST(tmp.num % 1000 AS VARCHAR(3)),3)) as client_id
from
(select 1 as 'num'
union
select 10
union
select 150
union
select 1000
union
select 25999
union
select 26000
union
select 27000
union
select 100000) tmp
Returns table:
+------------+-----------+
| client_num | client_id |
+------------+-----------+
| 1 | A001 |
| 10 | A010 |
| 150 | A150 |
| 1000 | B000 |
| 25999 | Z999 |
| 26000 | AA000 |
| 27000 | AB000 |
| 100000 | CW000 |
+------------+-----------+

EXAMPLE from the comments: (may not be an answer, posted here just because it is long)
CREATE SEQUENCE Numbers
INCREMENT BY 1
MINVALUE 1
MAXVALUE 999
CYCLE
;
--DROP TABLE test_DL
Create table test_DL
(
VendorName varchar(50),
VendorId as LeadingCharacters + CAST(FORMAT(TailingNumbers,'000') as VARCHAR(10)),
LeadingCharacters VARCHAR(50),
TailingNumbers INT DEFAULT(NEXT VALUE FOR Numbers),
[Counter] INT IDENTITY(1,1)
)
--ALTER SEQUENCE Numbers RESTART WITH 1
DECLARE #CONTROL INT = 0
WHILE (#CONTROL < 250)
BEGIN
INSERT INTO test_DL (VendorName)
VALUES ('THIS'),('IS'),('AN'),('EXAMPLE')
SET #CONTROL = #CONTROL + 1
END
;
WITH CTE
AS
(
SELECT *, ROW_NUMBER()OVER(ORDER BY [Counter],TailingNumbers) as RowNumber
FROM test_DL
)
UPDATE CTE
SET LeadingCharacters = CASE WHEN RowNumber <= 999 THEN 'A' WHEN 999 < RowNumber AND RowNumber < 2* 999 THEN 'B' END --The MOST ANNOYING PART is here, you need to manually category all the possibles
SELECT * FROM test_DL --Run this to check the result
Above method will be very dumb for future updates. Just give you some ideas lol

Related

SQL: insert to unique column with shift

I need to store data with varchar name and Integer intValue. All integer values are unique and I need to keep up that contract
I need to write query to add the element using the following rule: if after insertion there is an intValue duplication - we need to increase intValue of existed element to resolve conflict. Repeat that operation until no conflict left.
Example:
B | 2 | | B | 2 |
C | 3 | | E | 3 |
D | 4 | => insert (E 3) => | C | 4 |
A | 1 | | D | 5 |
Z | 7 | | A | 1 |
| Z | 7 |
The only idea is to run update query in a loop but that looks too unefficient.
I need to write this query in Spring JPA, so the only requirement that the query should not be database specific
Business case:
Let's say there is a people in the queue. And intValue is position in the queue. So, "Add" means that some person come, pay money and say: I dont wanna be the last in the queue. I want to be, for example, the 3rd. So you take the money and put that person in a queue so other people after him - increments their position.
The only difference from the queue - that in my case there are gaps allowed
Aha, we might say that the gaps are occasioned by people leaving the queue.
Lets try this. Loops are inevitable--either server does them, or we can do as SQL.
-- prepare test data
declare #PeopleQueue table (pqname varchar(100), intValue int);
insert into #PeopleQueue
SELECT 'B' AS pqname, 2 as intValue UNION ALL
SELECT 'C' AS pqname, 3 as intValue UNION ALL
SELECT 'D' AS pqname, 4 as intValue UNION ALL
SELECT 'A' AS pqname, 1 as intValue UNION ALL
SELECT 'Z' AS pqname, 7 as intValue
;
--SELECT '' AS pqname, 0 as intValue UNION ALL
Select * from #PeopleQueue; - verify good test data
-- Solve the problem
Declare #pqnameNEW varchar(100) = 'E';
Declare #intNEW int = 3; -- 3 for conflict, or for no conflict, use 13
Declare #intHIGH int;
IF EXISTS ( SELECT 1 FROM #PeopleQueue WHERE intValue = #intNEW )
BEGIN
-- find the end of the sequence, before the gap
SET #intHIGH = (
SELECT TOP 1
intValue
FROM #PeopleQueue pq
WHERE NOT EXISTS
(
SELECT NULL
FROM #PeopleQueue pn
WHERE pn.intValue = pq.intValue + 1
)
AND pq.intValue >= #intNEW
)
;
-- now Update all from intNEW thru intHIGH
UPDATE #PeopleQueue
SET intValue = intValue + 1
WHERE intValue >= #intNEW
AND intValue <= #intHIGH
End;
-- finally insert the new item
INSERT into #PeopleQueue Values (#pqnameNEW, #intNEW);
Select * from #PeopleQueue; -- verify correct solution
Edited--11/28 17:00
Or, estimate the number of Bump-the-Line-Inserts (vs append to the end inserts), and design the intValues to be originally in multiples of ten (10) so that long sequences of updates are minimized.
update queue
SET intValue = intValue + 1
WHERE intValue >= 3
AND intValue <= (
SELECT q1.intValue
FROM queue as q1 LEFT JOIN queue AS q2 ON q1.intValue + 1 = q2.intValue
WHERE q2.name is NULL AND q1.intValue > 3
ORDER BY q1.intValue
LIMIT 1
)

SQL updating a record in a table concerning another record in the same table

I have a table that contains more than 16,000,000 records.
Each record has a primary key (formed by five fields "tsid, plisid, plifc, plisc, dt"), and two counter fields ("icount, aicount").
There is a relation between some of the records in the table.
To simplify the problem let's say we have only these two records
tsid, plisid, plifc, plisc, dt, icount, aicount
10 1 0 0 0 2 2
11 1 0 0 0 7 0
The requirement:
I want to update the "aicount" field in the second record to be 9 (i.e. "icount" in the second record + "aicount" in the first record).
The relation between the first and second record is that they have the same values in (plisid, plifc, plisc, dt), and the tsid value of the second record == the tsid of the first record + 1
The desired result after the update is:
tsid, plisid, plifc, plisc, dt, icount, aicount
10 1 0 0 0 2 2
11 1 0 0 0 7 9
I tried this SQL statement in PostgreSQL but I got a syntax error "ERROR: syntax error at or near "SELECT" Position: 59"
UPDATE table1 SET
table1.aicount = table1.icount + SELECT COALESCE( (SELECT CASE
WHEN table1temp.aicount IS NULL
THEN 0
ELSE table1temp.aicount
END
FROM table1 table1temp
WHERE table1temp.tsid = table1.tsid - 1
AND table1temp.plisid = table1.plisid
AND table1temp.plifc = table1.plifc
AND table1temp.plisc = table1.plisc
AND table1temp.dt = table1.dt), 0)
WHERE table1.tsid = 10;
What is the wrong in the statement above? Any idea or suggestions?
The error caused because you couldn't use select subquery to add an update column.
You seem to want to get the number, which this row icount number add with last recorded aicount number
I would use LAG function to get last recorded aicount number in subquery then update the number.
There are three parameters in LAG function.
First your column, which you want to get the last column value.
offset from this column value defaults to 1
default value. default to null
lag(value any [, offset integer [, default any ]])
returns value evaluated at the row that is offset rows after the current row within the partition; if there is no such row, instead return default. Both offset and default are evaluated with respect to the current row. If omitted, offset defaults to 1 and default to null
CREATE TABLE T(
tsid int,
plisid int,
plifc int,
plisc int,
dt int,
icount int,
aicount int
);
INSERT INTO T VALUES (10,1,0,0,0,2,2);
INSERT INTO T VALUES (11,1,0,0,0,7,0);
UPDATE T
SET aicount = t1.totle
FROM
(
SELECT *,(LAG(aicount,1,0) over(order by tsid) + icount) totle
FROM T
) t1
WHERE
T.tsid = t1.tsid
AND T.plisid = t1.plisid
AND T.plifc = t1.plifc
AND T.plisc = t1.plisc
AND T.dt = t1.dt
Query 1:
SELECT * FROM T
Results:
| tsid | plisid | plifc | plisc | dt | icount | aicount |
|------|--------|-------|-------|----|--------|---------|
| 10 | 1 | 0 | 0 | 0 | 2 | 2 |
| 11 | 1 | 0 | 0 | 0 | 7 | 9 |
Try the following query-:
update T
set aicount=mm.m
from(
select sum(iCount) over (partition by plisid,plifc,plisc,dt order by tsid) m from T
) mm
SQL Server

How to create end date that is one day less than the next start date created by another another query with sql?

I queried off of a table that pulls in anyone who has working time percentage of less than 100 and all their working time records if they met the less than 100 criteria.
This table contains the columns: id, eff_date (of working time percentage), and percentage. This table does not contain end_date.
Problem: how to build on top of the query below and add a new column called end_date that is one date less than the next eff_date?
Current query
select
j1.id, j1.eff_date, j1.percentage
from
working_time_table j1
where
exists (select 1
from working_time_table j2
where j2.id = j1.id and j2.percentage < 100)
Data returned from the query above:
ID | EFF_DATE| PERCENTAGE
------------------------
12 | 01-JUN-2012 | 70
12 | 03-MAR-2013 | 100
12 | 13-DEC-2014 | 85
The desired result set is:
ID | EFF_DATE | PERCENTAGE | END_DATE
-------------------------------------------
12 | 01-JUN-2012 | 70 | 02-MAR-2013
12 | 03-MAR-2013 | 100 | 12-DEC-2014
12 | 13-DEC-2014 | 85 | null
You didn't state your DBMS so this is ANSI SQL using window functions:
select j1.id,
j1.eff_date,
j1.percentage,
lead(j1.eff_date) over (partition by j1.id order by j1.eff_date) - interval '1' day as end_date
from working_time_table j1
where exists (select 1
from working_time_table j2
where j2.id = j1.id and j2.percentage < 100);
First off, curious if the "id" column is unique or it has duplicate values like the 12's in your sample, or is that a unique column or primary key possibly. It would be WAAAAY easier to do this if there was a unique
id column that held the order. If you don't have a unique ID column,
are you able to add one to the table? Again, would simplify this
tremendously.
This took forever to get right, I hope this helps, burned many hours on it.
Props to Akhil for helping me finally get the query right. He is a true SQL genius.
Here is the ..
SQLFIDDLE
SELECT
id,
firstTbl.eff_Date,
UPPER(DATE_FORMAT(DATE_SUB(
STR_TO_DATE(secondTbl.eff_Date, '%d-%M-%Y'),
INTERVAL 1 DAY), '%d-%b-%Y')) todate,
percentage FROM
(SELECT
(#cnt := #cnt + 1) rownum,
id, eff_date, percentage
FROM working_time_table,
(SELECT
#cnt := 0) s) firstTbl
LEFT JOIN
(SELECT
(#cnt1 := #cnt1 + 1) rownum,
eff_date
FROM working_time_table,
(SELECT
#cnt1 := 0) s) secondTbl
ON (firstTbl.rownum + 1) = secondTbl.rownum

select max value in a group of consecutive values

How do you do to retrieve only the max value of a group with only consecutive values?
I have a telephone database with only unique values and I want to get only the highest number of each telephone number group TelNr and I am struggling.
id | TeNr | Position
1 | 100 | SLMO2.1.3
2 | 101 | SLMO2.3.4
3 | 103 | SLMO2.4.1
4 | 104 | SLMO2.3.2
5 | 200 | SLMO2.5.1
6 | 201 | SLMO2.5.2
7 | 204 | SLMO2.5.5
8 | 300 | SLMO2.3.5
9 | 301 | SLMO2.6.2
10 | 401 | SLMO2.4.8
Result should be:
TelNr
101
104
201
204
301
401
I have tried almost every tip I could find so far and whether I get all TelNr or no number at all which is useless in my case.
Any brilliant idea to run this with SQLITE?
So you're searching for gaps and want to get the first value of those gaps.
This is probably the best way to get them, try to check for a row with the current TeNr plus 1 and if there's none you found it:
select t1.TeNr, t1.TeNr + 1 as unused_TeNr
from tab as t1
left join Tab as t2
on t2.TeNr = t1.TeNr + 1
where t2.TeNr is null
Edit:
To get the range of missing values you need to use some old-style SQL as SQLite doesn't seem to support ROW_NUMBER, etc.
select
TeNr + 1 as RangeStart,
nextTeNr - 1 as RangeEnd,
nextTeNr - TeNr - 1 as cnt
from
(
select TeNr,
( select min(TeNr) from tab as t2
where t2.TeNr > t1.TeNr ) as nextTeNr
from tab as t1
) as dt
where nextTeNr > TeNr + 1
It's probably not very efficient, but might be ok if the number of rows is small and/or there's a index on TeNr.
Getting each value in the gap as a row in your result set is very hard, if your version of SQLite supports recursive queries:
with recursive cte (TeNr, missing, maxTeNr) as
(
select
min(TeNr) as TeNr, -- start of range of existing numbers
0 as missing, -- 0 = TeNr exists, 1 = TeNr is missing
max(TeNr) as maxTeNr -- end of range of existing numbers
from tab
union all
select
cte.TeNr + 1, -- next TeNr, if it doesn't exists tab.TeNr will be NULL
case when tab.TeNr is not null then 0 else 1 end,
maxTeNr
from cte left join tab
on tab.TeNr = cte.TeNr + 1
where cte.TeNr + 1 < maxTeNr
)
select TeNr
from cte
where missing = 1
Depending on your data this might return a huge amount of rows.
You might also use the result of the previous RangeStart/RangeEnd query as input to this recursion.

Oracle how to partition data and get records at every 10%

I have a giant table that has billions of records like this:
ID | H | N | Q | other
-----+-----+------+-----+--------
AAAA | 0 | 7 | Y | ...
BBBB | 1 | 5 | Y | ...
CCCC | 0 | 11 | N | ...
DDDD | 3 | 123 | N | ...
EEEE | 6 | 4 | Y | ...
These four columns are part of an index. What I want to do is construct a query that gives me the 1st row, followed by the row at 10%, 20%, 30%, 40%, ... so that the query will always give me 10 rows regardless of how big the table is (as long as # rows >= 10).
Is this even possible with SQL? If so, how would I do it? What kind of performance characteristics does it have?
One option would be
SELECT id,
h,
n,
q
FROM (
SELECT id,
h,
n,
q,
row_number() over (partition by decile order by id, n) rn
FROM (
SELECT id,
h,
n,
q,
ntile(10) over (order by id, n) decile
FROM your_table
)
)
WHERE rn = 1
There is probably a more efficient approach using PERCENTILE_DISC or CUME_DIST that isn't clicking for me at the moment. But this should work.
You can use a histogram to get this information. The huge downside is that the results will only be approximate, and it's very difficult to say how approximate they will be. And you'll need to gather table statistics to refresh the results, but you're probably already doing that. On the positive side, the query to get the results will be very fast. And using statistics instead of a query would be so cool.
Here's a quick demo:
--Create a table with the IDs AA - ZZ.
create table test(id varchar2(100), h number, n number, q varchar2(100)
,other varchar2(100));
insert into test
select letter1||letter2 letters, row_number() over (order by letter1||letter2), 1, 1, 1
from
(select chr(65+level-1) letter1 from dual connect by level <= 26) letters1
cross join
(select chr(65+level-1) letter2 from dual connect by level <= 26) letters2
;
commit;
--Gather stats, create a histogram with 11 buckets (we'll only use the first 10)
begin
dbms_stats.gather_table_stats(user, 'TEST', cascade=>true,
method_opt=>'FOR ALL COLUMNS SIZE AUTO, FOR COLUMNS SIZE 10 ID');
end;
/
--Getting the values from user_histograms is kinda tricky, especially for varchars.
--There are problems with rounding, so some of the values may not actually exist.
--
--This query is from Jonathan Lewis:
-- http://jonathanlewis.wordpress.com/2010/10/05/frequency-histogram-4/
select
endpoint_number,
endpoint_number - nvl(prev_endpoint,0) frequency,
hex_val,
chr(to_number(substr(hex_val, 2,2),'XX')) ||
chr(to_number(substr(hex_val, 4,2),'XX')) ||
chr(to_number(substr(hex_val, 6,2),'XX')) ||
chr(to_number(substr(hex_val, 8,2),'XX')) ||
chr(to_number(substr(hex_val,10,2),'XX')) ||
chr(to_number(substr(hex_val,12,2),'XX')),
endpoint_actual_value
from (
select
endpoint_number,
lag(endpoint_number,1) over(
order by endpoint_number
) prev_endpoint,
to_char(endpoint_value,'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')hex_val,
endpoint_actual_value
from
user_histograms
where table_name = 'TEST'
and column_name = 'ID'
)
where
endpoint_number < 10
order by
endpoint_number
;
Here's a comparison of the histogram results with the real results from #Justin Cave's query:
Histogram: Real results:
A# AA
CP CQ
FF FG
HV HW
KL KM
NB NC
PR PS
SG SH
UU UW
XK XL