Minimum and max of two column - sql-server

I want to find the minimum value present in 2 columns and max value present in 2 column
EX: A :2 3 4
B: 5 6 7
Ans should be 2 for min and 7 for max.
A and B are columns in same table.
min() inbuilt function doesn't exists.

I think the simplest way is apply:
select min(x.val), max(x.val)
from t cross apply
(select val
from (values (t.col1), (t.col2)) v(val)
) x;
The cross apply simply unpivots the values into a single column, before running the aggregation functions.

You could go with:
SELECT MIN(val), MAX(val) FROM (
SELECT a val FROM Table
UNION
SELECT b FROM Table
) d

Self-contained example.
declare #t table(a int, b int)
insert into #t values(2,5)
insert into #t values(3,6)
insert into #t values(4,7)
select min(a) as a,max(b) as b from #t
--if for some reason min() and max() are 'not available' (???)
select
(select top 1 a from #t order by a asc) as a
,(select top 1 b from #t order by a desc) as b

If there is indexing which is appropriate, it might be more efficient to do the MIN/MAX independently first on columns A and B since it would hopefully use some seeks into appropriate indexes on those columns:
SELECT MIN(mn), MAX(mx)
FROM (
SELECT MIN(A) AS mn, MAX(A) AS mx FROM tbl
UNION ALL
SELECT MIN(B) AS mn, MAX(B) AS mx FROM tbl
) x;

you can use Max and Min function to determine the max n min values for that column.
Select Min(A),Max(B) from Table

Related

Get all Numbers that are not existing from a Microsoft SQL-VarChar-Column

I have a table with a Column for ID-Numbers that are not increased in single steps.
So there are Numbers that are not used and this i need. The Column is a VarChar-Column.
For Example:
Used Numbers in the table = 2, 5, 7, 9, 10 etc.
So i need a Query that gives me = 1, 3, 4, 6, 8 etc.
Pseudo-Code something like:
select numbers from Table NOT IN (select numbers from table)!
I have tried with NOT IN and NOT EXISTS, but nothing works.
Can someone help me to achieve this?
EDIT: Range of Numbers is from 0 to 99999999 !!!
DECLARE #Table AS TABLE
(
Id VARCHAR(5)
)
INSERT INTO #Table
VALUES
('1')
,('3')
,('5')
,('7')
,('10')
DECLARE #Range AS TABLE
(
RangeId VARCHAR(10)
)
INSERT INTO #Range
SELECT TOP (1000000) n = CONVERT(VARCHAR(10), ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1)
select
MissingId = RangeId
from
#Range AS R
LEFT OUTER JOIN #Table AS T ON T.Id = R.RangeId
WHERE
CONVERT(INT,R.RangeId) <= (SELECT MAX(CONVERT(INT,Id)) FROM #Table)
AND T.Id IS NULL
order by MissingId
As you don't mention what the upper limit is, and recursive Common Table Expressions are inherently slow, you would likely be better off with a Tally to achieve this:
CREATE TABLE dbo.YourTable (ID int);
INSERT INTO dbo.YourTable (ID)
VALUES(1),(3),(5),(7),(9),(11),(13),(15),(216); --Big jump on purpose
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT MAX(ID) FROM dbo.YourTable) --Limit the tally for performance
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3) --1000 rows, add more Ns for more rows
SELECT I AS ID
FROM Tally T
LEFT JOIN dbo.YourTable YT ON T.I = YT.ID
WHERE YT.ID IS NULL;
Warning: Based on the comment on another answer:
This is the right Direction. When I do it in my Situation, I get only 100 Numbers. But the Numbers have 8 Digits and there are MUCH more then 100 Numbers!
8 digits means you have IDs with a value of 10,000,000 (10 Million) plus. Creating over 10 million rows in a tally will be very IO intensive. I strongly suggest putting this into batches.
Edit2: Ok, the max (from a comment on this answer) is 99,999,999! This is information that definitely should have been in the question. This process must be batched or you will kill your transaction Logs.
With out using CTE you can use the master.dbo.spt_values trick. Not sure the actual purpose of this table in msdb, but it contains the values we need. Give it a try. If you have bigger values than spt_values, then divide your max id by spt_value's max, and replace number+1 with number+1+(#currentbatch*#maxsptvalues) (first batch is batch 0). I haven't tested it nor written then code for it, but something of that sort should certainly work. You can do it in a while loop for instance.
IF OBJECT_ID('tmptbl') IS NOT null
DROP TABLE tmptbl
GO
SELECT * INTO tmptbl
FROM
(
SELECT '1' [id]
UNION
SELECT '3'
UNION
SELECT '5' ) t
DECLARE #maxid INT = 0
SELECT #maxid = MAX(id) FROM tmptbl
SELECT number+1
FROM master.dbo.spt_values
WHERE number < #maxid
AND Type = 'p'
AND NOT EXISTS ( SELECT 1
FROM dbo.tmptbl
WHERE CONVERT(INT,[id]) = (number+1))
ORDER BY number
The Result:
2,4

SQL update column with random numbers from set list of values

I have a table that gets rewritten each night. I have two columns that need to be populated with a random number from a set list of values. Angle column needs to be populated with 15,30,45,60,90 and Distance needs to populated with 9,15,21. The insert statements may process up to 700 records.
I have tried creating a temp table (#MyRandomVal1) with
select 15 union
select 30 union
select 45 etc...
Then use (select top 1 val from #MyRandomVal1 order by newid()). This populates the column with the same random number for all rows. It seems that I might need to loop through the inserted rows so it runs (select top 1 val from #MyRandomVal1 order by newid()) for each row, however in my research I have read that Loops are not recommended. Is there another method for populating 700+ rows with a random sampling from a set list during an insert?
Below is my existing code (for angle only). SQL Server 2012.
DECLARE #MyRandomVal1 Table (
id int identity (1,1),
val int not null)
INSERT INTO #MyRandomVal1 (val)
SELECT 15
union
SELECT 30
union
SELECT 45
union
SELECT 60
union
SELECT 90
INSERT INTO MyTable (AUTO_KEY,E3_KEY,EMPID,ENAME,COLOR,ANGLE)
SELECT dbo.getautokey(),dbo.GetAutoKey(),[EMPID],[ENAME],abs(checksum(NewId()) % 256),(select top 1 val from #MyRandomVal1 order by newid())
FROM MyTable2 WHERE [JOBLEVEL]='SVP'
Thanks.
One way to do it is with a cte. Join your #MyRandomVal1 to MyTable2 on true. Add a row number that is ordered by newid(). Then get all the rownumber 1's. You'll want to check the logic in the PARTITION BY. I didn't know if there was a column that was unique. If not you may have to partition by all columns since we are joining each row to every row in the random value table.
DECLARE #MyRandomVal1 Table (
id int identity (1,1),
val int not null)
INSERT INTO #MyRandomVal1 (val)
SELECT 15
union
SELECT 30
union
SELECT 45
union
SELECT 60
union
SELECT 90
;WITH cte AS (
SELECT
dbo.getautokey() AS AUTO_KEY
, dbo.GetAutoKey() AS E3_KEY
, [EMPID]
, [ENAME]
, ABS(checksum(NewId()) % 256) AS COLOR
, a.val
, ROW_NUMBER() OVER (PARTITION BY empid ORDER BY NEWID()) AS rn
FROM MyTable2
JOIN #MyRandomVal1 a ON 1 = 1
WHERE [JOBLEVEL]='SVP')
INSERT INTO MyTable (AUTO_KEY, E3_KEY, EMPID,ENAME, COLOR, ANGLE)
SELECT * FROM cte
WHERE rn = 1
Here's a simple DEMO since we don't have example data.

SQL Server: How do I get the highest value not set of an int column?

Let's take an example. These are the rows of the table I want get the data:
The column I'm talking about is the reference one. The user can set this value on the web form, but the system I'm developing must suggest the lowest reference value still not used.
As you can see, the smallest value of this column is 35. I could just take the smaller reference and sum 1, but, in that case, the value 36 is already used. So, the value I want is 37.
Is there a way to do this without a loop verification? This table will grow so much.
This is for 2012+
DECLARE #Tbl TABLE (id int, reference int)
INSERT INTO #Tbl
( id, reference )
VALUES
(1, 49),
(2, 125),
(3, 35),
(4, 1345),
(5, 36),
(6, 37)
SELECT
MIN(A.reference) + 1 Result
FROM
(
SELECT
*,
LEAD(reference) OVER (ORDER BY reference) Tmp
FROM
#Tbl
) A
WHERE
A.reference - A.Tmp != -1
Result: 37
Here is yet another place where the tally table is going to prove invaluable. In fact it is so useful I keep a view on my system that looks like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a cross join E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Next of course we need some sample data and table to hold it.
create table #Something
(
id int identity
, reference int
, description varchar(10)
)
insert #Something (reference, description)
values (49, 'data1')
, (125, 'data2')
, (35, 'data3')
, (1345, 'data4')
, (36, 'data5')
, (7784, 'data6')
Now comes the magic of the tally table.
select top 1 t.N
from cteTally t
left join #Something s on t.N = s.reference
where t.N >= (select MIN(reference) from #Something)
and s.id is null
order by t.N
This is ugly, but should get the job done:
select
top 1 reference+1
from
[table]
where
reference+1 not in (select reference from [table])
order by reference
I used a table valued express to get the next value. I first left outer joined the table to itself (shifting the key in the join by +1). I then looked only at rows that had no corresponding match (b.ID is null). The minimum a.ReferenceID + 1 gives us the answer we are looking for.
create table MyTable
(
ID int identity,
Reference int,
Description varchar(20)
)
insert into MyTable values (10,'Data')
insert into MyTable values (11,'Data')
insert into MyTable values (12,'Data')
insert into MyTable values (15,'Data')
-- Find gap
;with Gaps as
(
select a.Reference+1 as 'GapID'
from MyTable a
left join MyTable b on a.Reference = b.Reference-1
where b.ID is null
)
select min(GapID) as 'NewReference'
from Gaps
NewReference
------------
13
I hope the code was clearer than my description.
CREATE TABLE #T(ID INT , REFERENCE INT, [DESCRIPTION] VARCHAR(50))
INSERT INTO #T
SELECT 1,49 , 'data1' UNION ALL
SELECT 2,125 , 'data2' UNION ALL
SELECT 3,35 , 'data3' UNION ALL
SELECT 4,1345, 'data4' UNION ALL
SELECT 5,36 , 'data5' UNION ALL
SELECT 6,7784, 'data6'
SELECT TOP 1 REFERENCE + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
ORDER BY T1.REFERENCE
--- OR
SELECT MIN(REFERENCE) + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
How about using a Tally table. The following illustrates the concept. It would be better to use a persisted numbers table as opposed to the cte however the code below illustrates the concept.
For further reading as to why you should use a persisted table, check out the following link: sql-auxiliary-table-of-numbers
DECLARE #START int = 1, #END int = 1000
CREATE TABLE #TEST(UsedValues INT)
INSERT INTO #TEST(UsedValues) VALUES
(1),(3),(5),(7),(9),(11),(13),(15),(17)
;With NumberSequence( Number ) as
(
Select #start as Number
union all
Select Number + 1
from NumberSequence
where Number < #end
)
SELECT MIN(Number)
FROM NumberSequence n
LEFT JOIN #TEST t
ON n.Number = t.UsedValues
WHERE UsedValues IS NULL
OPTION ( MAXRECURSION 1000 )
You could try using a descending order:
SELECT DISTINCT reference
FROM `Resultsados`
ORDER BY `reference` ASC;
As far as I know, there is no way to do this without a loop. To prevent multiple values from returning be sure to use DISTINCT.

Insert row for each integer between 0 and <value> without cursor

I have a source table with id and count.
id count
a 5
b 2
c 31
I need to populate a destination table with each integer up to the count for each id.
id value
a 1
a 2
a 3
a 4
a 5
b 1
b 2
c 1
c 2
etc...
My current solution is like so:
INSERT INTO destination (id,value)
source.id
sequence.number
FROM
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) AS sequence(number)
INNER JOIN
source ON sequence.number <= source.count
This solution has an upper limit and is plain lame. Is there anyway to replace the sequence with a set of all integers? Or another solution that does not use looping.
this should work:
WITH r AS (
SELECT id, count, 1 AS n FROM SourceTable
UNION ALL
SELECT id, count, n+1 FROM r WHERE n<count
)
SELECT id,n FROM r
order by id,n
OPTION (MAXRECURSION 0)
Unfortunately, there is not set of all integers in SQL Server. However, using a little trickery, you can easily generate such a set:
select N from (
select ROW_NUMBER() OVER (ORDER BY t1.object_id) AS N
from sys.all_objects t1, sys.all_objects t2
) AS numbers
where N between 1 and 1000000
will generate a set of all numbers from 1 through 1000000. If you need more than a few million numbers, add sys.all_objects to the cross join a third time.
You can find many examples in this page:
DECLARE #table TABLE (ID VARCHAR(1), counter INT)
INSERT INTO #table SELECT 'a', 5
INSERT INTO #table SELECT 'b', 3
INSERT INTO #table SELECT 'c', 31
;WITH cte (ID, counter) AS (
SELECT id, 1
FROM #table
UNION ALL
SELECT c.id, c.counter +1
FROM cte AS c
INNER JOIN #table AS t
ON t.id = c.id
WHERE c.counter + 1 <= t.counter
)
SELECT *
FROM cte
ORDER BY ID, Counter

How to join sequential numbers to unrelated data (SQL Server)

This question is a followup to a previous question I had about discovering unused sequential number ranges without having to resort to cursors (Working with sequential numbers in SQL Server 2005 without cursors). I'm using SQL Server 2005.
What I need to do with those numbers is to assign those numbers to records in a table. I just can't seem to come up with a way to actually relate the numbers table with the records that need those numbers.
One possible solution that came to mind was insert the records in a temp table using an identity and using the beginning of the number range as an identity seed. The only problem with this approach is that if there are gaps in the number sequence then I'll end up with duplicate control numbers.
This is how my tables look like (overly simplified):
Numbers table:
Number
-------
102314
102315
102319
102320
102324
102329
Data table:
CustomerId PaymentAmt ControlNumber
---------- ---------- -------------
1001 4502.01 NULL
1002 890.00 NULL
9830 902923.34 NULL
I need a way to make it so i end up with:
CustomerId PaymentAmt ControlNumber
---------- ---------- -------------
1001 4502.01 102314
1002 890.00 102315
9830 902923.34 102319
Is this possible without having to use cursors? The reason I'm avoiding cursors is because our current implementation uses cursors and since its so slow (8 minutes over 12,000 records) I was looking for alternatives.
Note: Thanks to all who posted answers. All of them were great, I had to pick the one that seemed easier to implement and easiest to maintain for whomever comes after me. Much appreciated.
Try this:
;WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER(ORDER BY CustomerId) Corr
FROM DataTable
)
UPDATE CTE
SET CTE.ControlNumber = B.Number
FROM CTE
JOIN ( SELECT Number, ROW_NUMBER() OVER(ORDER BY Number) Corr
FROM NumberTable) B
ON CTE.Corr = B.Corr
Buidling on Martin's code from the linked question, you could give all rows without control number a row number. Then give all unused numbers a row number. Join the two sets together, and you get a unique number per row:
DECLARE #StartRange int, #EndRange int
SET #StartRange = 790123401
SET #EndRange = 790123450;
; WITH YourTable(ControlNumber, CustomerId) AS
(
SELECT 790123401, 1000
UNION ALL SELECT 790123402, 1001
UNION ALL SELECT 790123403, 1002
UNION ALL SELECT 790123406, 1003
UNION ALL SELECT NULL, 1004
UNION ALL SELECT NULL, 1005
UNION ALL SELECT NULL, 1006
)
, YourTableNumbered(rn, ControlNumber, CustomerId) AS
(
select row_number() over (
partition by IsNull(ControlNumber, -1)
order by ControlNumber)
, *
from YourTable
)
, Nums(N) AS
(
SELECT #StartRange
UNION ALL
SELECT N+1
FROM Nums
WHERE N < #EndRange
)
, UnusedNums(rn, N) as
(
select row_number() over (order by Nums.N)
, Nums.N
from Nums
where not exists
(
select *
from YourTable yt
where yt.ControlNumber = Nums.N
)
)
select ytn.CustomerId
, IsNull(ytn.ControlNumber, un.N)
from YourTableNumbered ytn
left join
UnusedNums un
on un.rn = ytn.rn
OPTION (MAXRECURSION 0)
All you need is a deterministic order in data table. If you have that, you can use ROW_NUMBER() as a join condition:
with cte as (
select row_number() over (order by CustomerId) as [row_number],
ControlNumber
from [Data Table]
where ControlNumber is null),
nte as (
select row_number() over (order by Number) as [row_number],
Number
from [Numbers])
update cte
set ControlNumber = Number
from cte
join nte on nte.[row_number] = cte.[row_number];
If you need it to be concurency proof, it does get more complex.
EDITED added in code to remove used values from #Number, via the OUTPUT caluse of the UPDATE and a DELETE
try using ROW_NUMBER() to join them:
DECLARE #Number table (Value int)
INSERT #Number VALUES (102314)
INSERT #Number VALUES (102315)
INSERT #Number VALUES (102319)
INSERT #Number VALUES (102320)
INSERT #Number VALUES (102324)
INSERT #Number VALUES (102329)
DECLARE #Data table (CustomerId int, PaymentAmt numeric(10,2),ControlNumber int)
INSERT #Data VALUES (1001, 4502.01 ,NULL)
INSERT #Data VALUES (1002, 890.00 ,NULL)
INSERT #Data VALUES (9830, 902923.34 ,NULL)
DECLARE #Used table (Value int)
;WITH RowNumber AS
(
SELECT Value,ROW_NUMBER() OVER(ORDER BY Value) AS RowNumber FROM #Number
)
,RowData AS
(
SELECT CustomerId,ROW_NUMBER() OVER(ORDER BY CustomerId) AS RowNumber, ControlNumber FROM #Data WHERE ControlNumber IS NULL
)
UPDATE d
SET ControlNumber=r.Value
OUTPUT r.Value INTO #Used
FROM RowData d
INNER JOIN RowNumber r ON d.RowNumber=r.RowNumber
DELETE #Number WHERE Value IN (SELECT Value FROM #Used)
SELECT * FROM #Data
SELECT * FROM #Number
OUTPUT:
CustomerId PaymentAmt ControlNumber
----------- --------------------------------------- -------------
1001 4502.01 102314
1002 890.00 102315
9830 902923.34 102319
(3 row(s) affected)
Value
-----------
102320
102324
102329
(3 row(s) affected)
You'll need something to join the two tables together. Some data value that you can match between the two tables.
I'm assuming there's more to your numbers table than just one column of numbers. If there's anything in there that you can match to your data table you can get away with an update.
How are you updating the data table using cursors?

Resources