Generate random numbers in a specific range without duplicate values - sql

SELECT CEILING (RAND(CAST(NEWID() AS varbinary)) *275) AS RandomNumber
This creates random numbers. However, it spits out duplicates

Generate a numbers table with the range of your desire. In my case, I do it via recursive cte. Then order the numbers table using the newid function.
with numbers as (
select 0 as val union all
select val + 1 from numbers where val < 275
)
select ord = row_number() over(order by ap.nid),
val
into #rands
from numbers n
cross apply (select nid = newid()) ap
order by ord
option (maxrecursion 1000);
One run of the code above results in a table of 276 values that begins and ends as follows:
| ord | val |
+-----+-----+
| 1 | 102 |
| 2 | 4 |
| 3 | 127 |
| ... | ... |
| 276 | 194 |
Non duplicating ordering of random numbers.
You can select from it a variety of ways, but one way could be:
-- initiate these to begin with
declare #ord int = 1;
declare #val int;
declare #rand int;
-- do this on every incremental need for a random number
select #val = val,
#ord = #ord + 1
from #rands
where ord = #ord;
print #val;

In the comments to my other answer, you write:
The table I'm working with has an ID , Name , and I want to generate a 3rd column that assigns a unique random number between 1-275 (as there are 275 rows) with no duplicates.
In the future, please include details like this in your original question. With this information, we can help you out better.
First, let's make a sample of your problem. We'll simplify it to just 5 rows, not 275:
create table #data (
id int,
name varchar(10)
);
insert #data values
(101, 'Amanda'),
(102, 'Beatrice'),
(103, 'Courtney'),
(104, 'Denise'),
(105, 'Elvia');
Let's now add the third column you want:
alter table #data add rando int;
Finally, let's update the table by creating a subquery that orders the rows randomly using row_number(), and applying the output the the column we just created:
update reordered
set rando = rowNum
from (
select *,
rowNum = row_number() over(order by newid())
from #data
) reordered;
Here's the result I get, but of course it will be different every time it is run:
select *
from #data
| id | name | rando |
+-----+----------+-------+
| 101 | Amanda | 3 |
| 102 | Beatrice | 1 |
| 103 | Courtney | 4 |
| 104 | Denise | 5 |
| 105 | Elvia | 2 |

Related

SQL sort by closest string match in multiple columns

I have a search parameter that I am trying to search on multiple columns using the "Like % InputParam %" pattern matching which gives me the following result. ie - Matching OrderID and Ref no to the input parameter.
Consider I have the following table -
OrderId | Name | Ref No |
12345 | XYZ | 120545 |
1205 | ABC | 451003 |
00120505 | CDE | 000174 |
Here OrderID, Ref no are strings and the input query = '1205'. I want the result to be sorted from the most matched to the least matched.
Where most matched is the most accurate match like 1205 = 1205 here
and Least matched is a substring like 00120505 = 1205.
Output -
OrderId | Name | Ref no |
1205 | ABC | 451003 |
12345 | XYZ | 120545 |
00120505 | CDE | 000174 |
You can do it by defining a computed column e.g. MATCH_SCORE with a value that depends on comparisons between OrderId and the value you are looking for; and then use ORDER BY MATCH_SCORE.
Working example:
DECLARE #tbl TABLE (OrderId VARCHAR(10), Name VARCHAR(10), RefNo VARCHAR(10))
INSERT INTO #tbl VALUES ('12345','XYZ','120545'), ('1205','ABC','451003'), ('00120505','CDE','000174')
DECLARE #v VARCHAR(10) = '1205'
;WITH data AS
(SELECT *,
CASE WHEN OrderId = #v THEN 1
WHEN OrderId LIKE #v + '%' THEN 2
WHEN OrderId LIKE '%' + #v + '%' THEN 3
ELSE 4
END AS MATCH_SCORE
FROM #tbl
)
SELECT * FROM data ORDER BY MATCH_SCORE
Output:
OrderId
Name
RefNo
MATCH_SCORE
1205
ABC
451003
1
00120505
CDE
000174
3
12345
XYZ
120545
4
I used a Common Table Expression to construct data, which defines a temporary named result set that is used by the SELECT that follows it.
You need to define what you mean by the distance between two strings. I'll use #peter-b 's definition in the example below. Once you know how to measure how close a string is to the search parameter, you can transpose the columns to rows with cross apply (LATERAL is the standard name), and use the min distance to order the rows.
with t (orderid, refno) as (
select '12345','120545'
union all
select '1205','451003'
)
select t.orderid, t.refno
, min(case when u.s = '1205' then 1
when u.s like '1205'+'%' then 2
when u.s like '%' + u.s + '%' then 3
else 4
end) as distance
from t
cross apply (
select t.orderid
union all
select t.refno
) as u (s)
group by t.orderid, t.refno
order by 3
;
orderid refno distance
1205 451003 1
12345 120545 2
Fiddle

Splitting single row into multiple based on column

I have certain number of rows as below:
| Material No | Quantity | Weight | Unit |
--------------------------------------------
| 111-22283/4 | 2 | 53 | kg |
| 123-ABC45/7 | 5 | 41 | g |
| 133-67879/80 | 7 | 31 | g |
| 144-54628 | 1 | 14 | kg |
Now I want to produce output like below:
| Material No | Quantity | Weight | Unit |
--------------------------------------------
| 111-22283 | 2 | 53 | kg |
| 111-22284 | 2 | 53 | kg |
| 123-ABC45 | 5 | 41 | g |
| 123-ABC46 | 5 | 41 | g |
| 123-ABC47 | 5 | 41 | g |
| 133-67879 | 7 | 31 | g |
| 133-67880 | 7 | 31 | g |
| 144-54628 | 1 | 14 | kg |
Logic: Based on the material no I have to split the rows. If '/' at end of the material no then it needs to be spilt. Then we have to find the difference b/w last number in the material no and the number /. If it is 2 then I want 2 different rows with each number as material number(Means if the last digit is 83/4 then I want material number ends with 83 and 84). The tricky part is when we are having 89/90. It will contain 2 number after / (only for 10's,20's etc.,). All other column will remain as same for each material no.
To achieve this ,currently we have a very big procedure which contains around 50-80 lines of code( find the row with '/' and remove separately then find the index of / and so on). I would like to know if this can be done with simple query or a very short procedure.
the challenging part is probably splitting the material no to get the starting and ending number.
After that, it is just a simple recursive query to increment the number and concatenate back to form the material no.
;with rcte as
(
select MaterialNo,
base, st, en, n = st,
material = convert(varchar(20), base + isnull(convert(varchar(10), st), ''))
from material m
-- get the position of the `/`
cross apply
(
select split = charindex('/', MaterialNo)
) s
-- extract the ending number and convert to integer
cross apply
(
select en = case when split > 0
then convert(int, right(MaterialNo, len(MaterialNo) - split))
end
) en
-- extract the starting number and convert to integer
cross apply
(
select st = case when split > 0
then convert(int, substring(MaterialNo, split - len(en), len(en)))
end
) st
-- extract the base material no for concatenate
cross apply
(
select base = case when split > 0
then left(MaterialNo, split - len(en) - 1)
else MaterialNo
end
) b
union all
select MaterialNo, base, st, en,
n = n + 1,
material = convert(varchar(20), base + convert(varchar(10), n + 1))
from rcte
where n < en
)
select *
from rcte
order by MaterialNo, material
all this is based on the assumption that the ending of material no is purely numeric.
Note : if you have a tally table, you can use that to replace the recursive cte
UPDATE
When a value from MATERIALNO can represent a series of values (as the updated question) :
DDL
CREATE FUNCTION UFN_STRTOSERIES (#materialNo VARCHAR(80), #qte INT, #weight INT, #unit VARCHAR(50))
RETURNS #result TABLE (
MATERIALNO VARCHAR(80),
QTE INT,
Weight INT,
UNIT VARCHAR(50)
)
AS
BEGIN
DECLARE #base VARCHAR(50) = LEFT(#materialNo,CHARINDEX('/',#materialNo)-1-LEN(RIGHT(#materialNo,LEN(#materialNo)-CHARINDEX('/',#materialNo))));
DECLARE #start INT = CONVERT( INT , RIGHT( LEFT(#materialNo,CHARINDEX('/',#materialNo)-1) , LEN(RIGHT(#materialNo,LEN(#materialNo)-CHARINDEX('/',#materialNo))) ) );
DECLARE #end INT = CONVERT( INT , RIGHT(#materialNo,LEN(#materialNo)-CHARINDEX('/',#materialNo)) );
DECLARE #i INT = #start;
WHILE #i <= #end
BEGIN
INSERT #result
SELECT CONCAT(#base,#i) AS MATERIALNO, #qte, #weight, #unit;
SET #i = #i + 1;
END;
RETURN
END;
CREATE TABLE MATERIALS
(
MATERIALNO VARCHAR(80),
QTE INT,
Weight INT,
UNIT VARCHAR(50)
)
INSERT INTO MATERIALS VALUES
('111-22283/4',2,53,'kg'),
('123-33345/7',5,41,'g' ),
('123-ABC45/7',5,41,'g'),
('133-67879/80',7,31,'g'),
('144-54628',1,14,'kg')
DML
SELECT MATERIALNO,QTE,Weight,UNIT
FROM (
SELECT MATERIALNO,QTE,Weight,UNIT
FROM MATERIALS
WHERE CHARINDEX('/',MATERIALNO) < 1
UNION
SELECT series.MATERIALNO,series.QTE,series.Weight,series.UNIT
FROM MATERIALS m
CROSS APPLY UFN_STRTOSERIES(MATERIALNO,QTE,Weight,UNIT) series
WHERE CHARINDEX('/',m.MATERIALNO) > 1
) base
ORDER BY base.MATERIALNO
OLD ANSWER
When a value from MATERIALNO represents only two values :
UNION is the easiest answer (then you have to check performances) :
DDL
CREATE TABLE MATERIALS
(
MATERIALNO VARCHAR(80),
QTE INT,
Weight INT,
UNIT VARCHAR(50)
)
INSERT INTO MATERIALS VALUES
('111-22283/4',2,53,'kg'),
('123-33345/7',5,41,'g' ),
('133-67879/80',7,31,'g'),
('144-54628',1,14,'kg' )
Query
SELECT MATERIALNO,QTE,Weight,UNIT
FROM (
SELECT MATERIALNO,QTE,Weight,UNIT
FROM MATERIALS
WHERE CHARINDEX('/',MATERIALNO) < 1
UNION
SELECT LEFT(MATERIALNO,CHARINDEX('/',MATERIALNO)-1),QTE,Weight,UNIT
FROM MATERIALS
WHERE CHARINDEX('/',MATERIALNO) > 1
UNION
SELECT
CONCAT(LEFT(MATERIALNO,CHARINDEX('/',MATERIALNO)-1-LEN(RIGHT(MATERIALNO,LEN(MATERIALNO)-CHARINDEX('/',MATERIALNO)))),RIGHT(MATERIALNO,LEN(MATERIALNO)-CHARINDEX('/',MATERIALNO))) AS MATERIALNO
,QTE,Weight,UNIT
FROM MATERIALS
WHERE CHARINDEX('/',MATERIALNO) > 1
) BASE
ORDER BY BASE.MATERIALNO
Fiddle : http://sqlfiddle.com/#!18/8e768/2

Is there a way using SQL to insert duplicate rows into Table A depending upon the results of a number column in Table B?

I am using TSQL on SQL Server and have bumped into a challenge...
I am querying the data out of TableA and then inserting it into TableB. See my stored procedure code below for more info.
However as an added layer of complexity one of the Columns in TableA holds a numeric number (It can be any number from 0 to 50) and depending upon this number I have to make 'n' number of Duplicates for that specific row. (for example in TableA we have a column called TableA.RepeatNumber and this will dictate how many duplicate rows I need to create of this row in TableB. Its worth noting that some of the rows won't need any duplicates as they will have a value of 0 in TableA.RepeatNumber)
(This stored procedure below works fine to insert single rows into TableB.)
ALTER PROCEDURE [dbo].[Insert_rows]
#IDCode As NVarChar(20),
#UserName As NVarChar(20)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
Insert INTO TableB (Status, Number, Date, Time, User)
SELECT Status, Number, date, Time, User,
FROM TableA where Status = 1 AND RepeatNumber > 0 AND Code = #IDCode AND User = #UserName
END
Any pointers on where I should look to find a solution to this problem (if it exists would be greatly appreciated.)
Best wishes
Dick
You can use a recursive CTE:
with a as (
select a.Status, a.Number, a.date, a.Time, a.User, a.RepeatNumber, 1 as seqnum
from tablea a
where Status = 1 and RepeatNumber > 0 and Code = #IDCode and User = #UserName
union all
select Status, Number, date, Time, User, RepeatNumber, seqnum + 1
from a
where seqnum < RepeatNumber
)
insert INTO TableB (Status, Number, Date, Time, User)
select Status, Number, date, Time, User
from a;
You only need up to 50 duplicates, so you don't have to worry about maximum recursion.
A numbers table can also be used for this purpose.
To achieve this using a numbers table and avoiding recursion which may have a performance penalty, you can do the following (if you already have an actual numbers table in your database you can just join to that and avoid the cte):
declare #TableA table(Status nvarchar(10),RepeatNumber int,[date] date,Time time,[User] nvarchar(10));
insert into #TableA values('Status 0',0,'20190101','00:00:00','User 0'),('Status 1',1,'20190101','01:01:01','User 1'),('Status 2',2,'20190102','02:02:02','User 2'),('Status 3',3,'20190103','03:03:03','User 3');
with t(t)as(select t from(values(1),(1),(1),(1),(1),(1),(1),(1))as t(t))
,n(n)as(select top 50 row_number()over(order by(select null)) from t,t t2)
select Status
,RepeatNumber
,[date]
,Time
,[User]
,n.n
from #TableA as a
join n
on a.RepeatNumber >= n.n
where RepeatNumber > 0
order by a.Status
,n.n;
Output
+----------+--------------+------------+------------------+--------+---+
| Status | RepeatNumber | date | Time | User | n |
+----------+--------------+------------+------------------+--------+---+
| Status 1 | 1 | 2019-01-01 | 01:01:01.0000000 | User 1 | 1 |
| Status 2 | 2 | 2019-01-02 | 02:02:02.0000000 | User 2 | 1 |
| Status 2 | 2 | 2019-01-02 | 02:02:02.0000000 | User 2 | 2 |
| Status 3 | 3 | 2019-01-03 | 03:03:03.0000000 | User 3 | 1 |
| Status 3 | 3 | 2019-01-03 | 03:03:03.0000000 | User 3 | 2 |
| Status 3 | 3 | 2019-01-03 | 03:03:03.0000000 | User 3 | 3 |
+----------+--------------+------------+------------------+--------+---+

Partitioning function for continuous sequences

There is a table of the following structure:
CREATE TABLE history
(
pk serial NOT NULL,
"from" integer NOT NULL,
"to" integer NOT NULL,
entity_key text NOT NULL,
data text NOT NULL,
CONSTRAINT history_pkey PRIMARY KEY (pk)
);
The pk is a primary key, from and to define a position in the sequence and the sequence itself for a given entity identified by entity_key. So the entity has one sequence of 2 rows in case if the first row has the from = 1; to = 2 and the second one has from = 2; to = 3. So the point here is that the to of the previous row matches the from of the next one.
The order to determine "next"/"previous" row is defined by pk which grows monotonously (since it's a SERIAL).
The sequence does not have to start with 1 and the to - from does not necessary 1 always. So it can be from = 1; to = 10. What matters is that the "next" row in the sequence matches the to exactly.
Sample dataset:
pk | from | to | entity_key | data
----+--------+------+--------------+-------
1 | 1 | 2 | 42 | foo
2 | 2 | 3 | 42 | bar
3 | 3 | 4 | 42 | baz
4 | 10 | 11 | 42 | another foo
5 | 11 | 12 | 42 | another baz
6 | 1 | 2 | 111 | one one one
7 | 2 | 3 | 111 | one one one two
8 | 3 | 4 | 111 | one one one three
And what I cannot realize is how to partition by "sequences" here so that I could apply window functions to the group that represents a single "sequence".
Let's say I want to use the row_number() function and would like to get the following result:
pk | row_number | entity_key
----+-------------+------------
1 | 1 | 42
2 | 2 | 42
3 | 3 | 42
4 | 1 | 42
5 | 2 | 42
6 | 1 | 111
7 | 2 | 111
8 | 3 | 111
For convenience I created an SQLFiddle with initial seed: http://sqlfiddle.com/#!15/e7c1c
PS: It's not the "give me the codez" question, I made my own research and I just out of ideas how to partition.
It's obvious that I need to LEFT JOIN with the next.from = curr.to, but then it's still not clear how to reset the partition on next.from IS NULL.
PS: It will be a 100 points bounty for the most elegant query that provides the requested result
PPS: the desired solution should be an SQL query not pgsql due to some other limitations that are out of scope of this question.
I don’t know if it counts as “elegant,” but I think this will do what you want:
with Lagged as (
select
pk,
case when lag("to",1) over (order by pk) is distinct from "from" then 1 else 0 end as starts,
entity_key
from history
), LaggedGroups as (
select
pk,
sum(starts) over (order by pk) as groups,
entity_key
from Lagged
)
select
pk,
row_number() over (
partition by groups
order by pk
) as "row_number",
entity_key
from LaggedGroups
Just for fun & completeness: a recursive solution to reconstruct the (doubly) linked lists of records. [ this will not be the fastest solution ]
NOTE: I commented out the ascending pk condition(s) since they are not needed for the connection logic.
WITH RECURSIVE zzz AS (
SELECT h0.pk
, h0."to" AS next
, h0.entity_key AS ek
, 1::integer AS rnk
FROM history h0
WHERE NOT EXISTS (
SELECT * FROM history nx
WHERE nx.entity_key = h0.entity_key
AND nx."to" = h0."from"
-- AND nx.pk > h0.pk
)
UNION ALL
SELECT h1.pk
, h1."to" AS next
, h1.entity_key AS ek
, 1+zzz.rnk AS rnk
FROM zzz
JOIN history h1
ON h1.entity_key = zzz.ek
AND h1."from" = zzz.next
-- AND h1.pk > zzz.pk
)
SELECT * FROM zzz
ORDER BY ek,pk
;
You can use generate_series() to generate all the rows between the two values. Then you can use the difference of row numbers on that:
select pk, "from", "to",
row_number() over (partition by entity_key, min(grp) order by pk) as row_number
from (select h.*,
(row_number() over (partition by entity_key order by ind) -
ind) as grp
from (select h.*, generate_series("from", "to" - 1) as ind
from history h
) h
) h
group by pk, "from", "to", entity_key
Because you specify that the difference is between 1 and 10, this might actually not have such bad performance.
Unfortunately, your SQL Fiddle isn't working right now, so I can't test it.
Well,
this not exactly one SQL query but:
select a.pk as PK, a.entity_key as ENTITY_KEY, b.pk as BPK, 0 as Seq into #tmp
from history a left join history b on a."to" = b."from" and a.pk = b.pk-1
declare #seq int
select #seq = 1
update #tmp set Seq = case when (BPK is null) then #seq-1 else #seq end,
#seq = case when (BPK is null) then #seq+1 else #seq end
select pk, entity_key, ROW_NUMBER() over (PARTITION by entity_key, seq order by pk asc)
from #tmp order by pk
This is in SQL Server 2008

Getting the Next Available Row

How can I get a List all the JobPositionNames having the lowest jobPositionId when ContactId = 1
Tablel :
| JobPositionId | JobPositionName | JobDescriptionId | JobCategoryId | ContactId
---------------------------------------------------------------------------------
1 | Audio Cables | 1 | 1 | 1
2 |Audio Connections| 2 | 1 | 1
3 |Audio Connections| 2 | 1 | 0
4 |Audio Connections| 2 | 1 | 0
5 | Sound Board | 3 | 1 | 0
6 | Tent Pen | 4 | 3 | 0
eg the result of this table should be lines 1,3,5,6
I can't figure out the solution.
Only lack of something, but I can give some code for you view.
Maybe it can help you.
--create table
create table t
(
JobPositionId int identity(1,1) primary key,
JobPositionName nvarchar(100) not null,
JobDescriptionId int,
JobCategoryId int,
ContactId int
)
go
--insert values
BEGIN TRAN
INSERT INTO t VALUES ('AudioCables', 1,1,1)
INSERT INTO t VALUES ('AudioConnections',2,1,1)
INSERT INTO t VALUES ('AudioConnections',2,1,0)
INSERT INTO t VALUES ('AudioConnections',2,1,0)
INSERT INTO t VALUES ('SoundBoard',3,1,0)
INSERT INTO t VALUES ('TentPen',4,3,0)
COMMIT TRAN
GO
SELECT
Min(JobPositionId) AS JobPositionId, JobPositionName, ContactId
INTO
#tempTable
FROM
t
GROUP BY JobPositionName, ContactId
SELECT * FROM #tempTable
WHERE JobPositionId IN (
SELECT JobPositionId
FROM #tempTable
GROUP BY JobPositionName
--... lack of sth, I can't figure out ,sorry.
)
drop table t
GO
For per-group maximum/minimum queries you can use a null-self-join as well as strategies like subselects. This is generally faster in MySQL.
SELECT j0.JobPositionId, j0.JobPositionName, j0.ContactId
FROM Jobs AS j0
LEFT JOIN Jobs AS j1 ON j1.JobPositionName=j0.JobPositionName
AND (
(j1.ContactId<>0)<(j0.ContactId<>0)
OR ((j1.ContactId<>0)=(j0.ContactId<>0) AND j1.JobPositionId<j0.JobPositionId))
)
WHERE j1.JobPositionName IS NULL
This says, for each JobPositionName, find a row for which there exists no other row with a lower ordering value. The ordering value here is a composite [ContactId-non-zeroness, JobPositionId].
(Aside: shouldn't JobPositionName and JobCategoryId be normalised out into a table keyed on JobDescriptionId? And shouldn't unassigned ContactIds be NULL?)
SELECT jp.*
FROM (
SELECT JobPositionName, JobPositionId, COUNT(*) AS cnt
FROM JobPosisions
) jpd
JOIN JobPosisions jp
ON jp.JobPositionId =
IF(
cnt = 1,
jpd.JobPositionId,
(
SELECT MIN(JobPositionId)
FROM JobPositions jpi
WHERE jpi.JobPositionName = jpd.JobPositionName
AND jpi.ContactID = 0
)
)
Create an index on (JobPositionName, ContactId, JobPositionId) for this to work fast.
Note that if will not return the jobs having more than one position, neither of which has ContactID = 0