SQL: Select lowest value that doesn't already exist - sql

In TableA I have an int column.
Is it possible using only a select statement to select the minimum value in the column that DOES NOT EXIST and is greater then 0?
For example, if the col has the values 1,2,9 the select statement will return 3.
If the col has 9,10,11 it will return 1.
I can achieve this using a temp table or using a loop, but I'm wondering if I can do it using just a select statement?

SELECT MIN(t1.ID+1) as 'MinID'
FROM table t1 LEFT JOIN table t2
On t1.ID+1=t2.ID
Where t2.OtherField IS NULL

select
min(nt.id)
from numbertable nt
left outer join originaldata od
on nt.id=od.id
where od.id is null
have a number table that goes from 1 to your max value (or higher)

SELECT DISTINCT x + 1 "val"
EXCEPT SELECT DISTINCT x "val"
ORDER BY "val" ASC
LIMIT 1
What about this?

SELECT Min(id)
FROM (SELECT 1 id
FROM tablea
WHERE 1 NOT IN (SELECT id
FROM tablea)
UNION
SELECT id + 1 id
FROM tablea
WHERE id + 1 NOT IN (SELECT id
FROM tablea)) AS min_ids;

try this:(Updated)
declare #dummy varchar(10) ;
set #dummy =(select top(1) id from dbo.b)
if( #dummy= '1')
begin
select top(1)l.id + 1 as start
from dbo.b as l
left outer join dbo.b as r on l.id + 1 = r.id
where r.id is null
end
else
begin
select '1'
end

Give this a try:
declare #TestTable table (
col int
)
/* Test Case 1: 1,2,9 */
insert into #TestTable
(col)
select 1 union all select 2 union all select 9
SELECT MinValue = (SELECT ISNULL(MAX(t2.col),0)+1
FROM #TestTable t2
WHERE t2.col < t1.col)
FROM #TestTable t1
WHERE t1.col - 1 NOT IN (SELECT col FROM #TestTable)
AND t1.col - 1 > 0
delete from #TestTable
/* Test Case 2: 9,10,11 */
insert into #TestTable
(col)
select 9 union all select 10 union all select 11
SELECT MinValue = (SELECT ISNULL(MAX(t2.col),0)+1
FROM #TestTable t2
WHERE t2.col < t1.col)
FROM #TestTable t1
WHERE t1.col - 1 NOT IN (SELECT col FROM #TestTable)
AND t1.col - 1 > 0

I duplicated my answer from here:
SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table UNION SELECT 0) a
LEFT JOIN table b ON b.id = a.id + 1
WHERE b.id IS NULL
This handles all cases I can think of - including no existing records at all.
The only thing I don't like about this solution is that additional conditions have to be included twice, like that:
SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table WHERE column = 4711 UNION SELECT 0) a
LEFT JOIN table b ON b.column = 4711 AND b.id = a.id + 1
WHERE b.id IS NULL
Please also notice the comments about locking and concurrency - the requirement to fill gaps is in most cases bad design and can cause problems. However, I had a good reason to do it: the IDs are to be printed and typed by humans and we don't want to have IDs with many digits after some time, while all the low ones are free...

Related

SQL - How to get duplicate records from one table with join on another table

I want to get records from Table1 where it has more than one record for same Access Number, but at the same time this Access Number should also be present in Table2.
Example
Table1
Access Number
- 1000
- 1000
- 1000
- 2000
- 3000
- 4000
- 5000
- 5000
Table2
AccessNumber Value
- 1000 -Value1000
- 1000 -Value9999
- 2000 -Value2000
- 3000 -Value3000
The result of the query should be 1000 - Value1000
This is what I've got so far, please suggest
SELECT a.AccessNumber, b.Valuefrom Table1 a
JOIN Table2 b on b.AccessNumber = a.AccessNumber
Group by a.AccessNumber, b.VAlue
HAVING COUNT(1) > 1;
The problem i am facing is the query returns duplicates from Table2.
1000 - Value1000
1000 - Value9999
Without much information as to why 1000 - Value1000 should be the result instead of 1000 - Value9999, we can just get the first record from the top:
select top 1 * from (
-- your original query
SELECT a.AccessNumber, b.Value from Table1 a
JOIN Table2 b on b.AccessNumber = a.AccessNumber
Group by a.AccessNumber, b.Value
HAVING COUNT(1) > 1
-- your original query
) as x;
If we are just looking for the AccessNumber that has duplicate records, you can remove the b.Value from the selected columns and remove that as well in the Group By clause.
SELECT a.AccessNumber from Table1 a
JOIN Table2 b on b.AccessNumber = a.AccessNumber
Group by a.AccessNumber
HAVING COUNT(1) > 1;
http://www.sqlfiddle.com/#!18/3f114/3
We might be able to get this working using SQL Server's EXCEPT operator. But one sensible way to go here would be to just aggregate both tables by access number, and then left join the first to the second, retaining only access numbers which appear in both tables and which appear in greater quantity in the first than the second.
SELECT t1.AccessNumber
FROM
(
SELECT AccessNumber, COUNT(*) AS cnt_1
FROM Table1
GROUP BY AccessNumber
) t1
LEFT JOIN
(
SELECT AccessNumber, COUNT(*) AS cnt_2
FROM Table2
GROUP BY AccessNumber
) t2
ON t1.AccessNumber = t2.AccessNumber
WHERE
t1.cnt_1 - COALESCE(t2.cnt_2, 0) > 0 AND t2.cnt_2 IS NOT NULL;
Demo
--Hope this query will help you
; with cte_accessnumber(Accessnumber, [value])
as
(
select
t1.Accessnumber, count(*)NoOfRecords
from table1 t1
where exists (
select top 1 1 from table2 t2 where t2.Accessnumber = t1.Accessnumber)
group by t1.Accessnumber
having count(*)>1
)
select t1.Accessnumber, t2.[value]
from cte_accessnumber t1
inner join table2 t2 on t2.Accessnumber = t1.Accessnumber and t2.[value] like '%' + convert(varchar(20),t1.Accessnumber) +'%'
you may try using nested for the criteria that AccessNumber rows > 1 and cross apply for criteria which only display 1 record
DECLARE #tblA AS TABLE
(
AccessNumber INT
)
DECLARE #tblB AS TABLE
(
AccessNumber INT,
colB NVARCHAR(50)
)
INSERT INTO #tblA SELECT 1000
INSERT INTO #tblA SELECT 1000
INSERT INTO #tblA SELECT 1000
INSERT INTO #tblA SELECT 1000
INSERT INTO #tblA SELECT 2000
INSERT INTO #tblA SELECT 3000
INSERT INTO #tblA SELECT 4000
INSERT INTO #tblB SELECT 1000,'hello'
INSERT INTO #tblB SELECT 1000,'hello2'
INSERT INTO #tblB SELECT 2000,'world'
-- Query --
SELECT tblB.* FROM (
SELECT AccessNumber,COUNT(1) AS cnt FROM #tblA GROUP BY AccessNumber ) ftblA
CROSS APPLY (SELECT TOP(1) * FROM #tblB itblB WHERE ftblA.AccessNumber = itblB.AccessNumber) tblB
WHERE ftblA.cnt >1
-- Only 1000 should be displayed only --
Use subquery with correlation approach
select access_no,
(select top 1 Value from table2 where access_no = t.access_no) as value
from Table1 t
where exists (
select 1 from table2 where access_no = t.access_no)
group by access_no
having count(*) > 1
However, you would need to specify order by clause in subquery in order to fetch the value from table2

Checking whether two tables have identical content

How can I check and store in variable whether two tables have identical content?
I have table variable with data like
declare #table1 table (id int)
insert into #table1 (id) values (1), (2), (3)
and as the second table I have query
select T.id from SomeTable T
inner join #table1 T1 on T.id = T1.id
the query returns data:
id
-----
1
2
In this case I need write false(0) into declare #HasAccess BIT variable.
When the query returns data:
id
-----
1
2
3
then I need write true(1) into #HasAccess
Hmmm. There are various ways.
Given that you have one column, you can do:
select (case when count(*) = 0 then 1 else 0 end)
from t1 full join
t2
on t1.id = t2.id
where t1.id is null or t2.id is null;
This checks if an id doesn't match in either table.
Another way uses union all:
select (case when count(*) = 0 then 1 else 0 end)
from (select id, sum(in_t1) as num_t1, sum(in_t2) as num_t2)
from ((select id, 1 as in_t1, 0 as in_t2 from table1) union all
(select id, 0, 1 from table2)
) tt
group by id
) tt
where num_t1 <> 1 or num_t2 <> 1;
Another option (just for fun). This will compare the entire table fields and values.
I suspect not the best option for LARGE tables
Example
Select IsIdentical = case when (Select * from Table1 Order by id For XML Raw)
= (Select * from Table2 Order by id For XML Raw)
then 1 else 0 end
EDIT - Option with Inner Join
Select IsIdentical = case when (Select * from #Table1 Order by id For XML Raw)
= (Select A.*
From SomeTable A
Join #Table1 B on A.ID=B.ID
Order By id For XML Raw)
then 1 else 0 end
Using EXCEPT:
SET #HasAccess = ISNULL(
( SELECT 0
WHERE EXISTS(
SELECT ID /* add more columns here if needed */
FROM #table1
EXCEPT
SELECT ID /* add more columns here if needed */
FROM SomeTable )), 1 )
Explanation:
Return all IDs from #table1, except those found in SomeTable
Return 0 (false) if any records have been returned by [1].
If no records returned by [1] the main query will return NULL, hence the ISNULL
Advanatages
Can easily be extended to comparisons on more than one column.

Querying two tables to filter data using select case

I have two tables
Table 1 looks like this
ID Repeats
-----------
A 1
A 1
A 0
B 2
B 2
C 2
D 1
Table 2 looks like this
ID values
-----------
A 100
B 200
C 100
D 300
Using a view I need a result like this
ID values Repeats
-------------------
A 100 NA
B 200 2
C 100 2
D 300 1
that means, I want unique ID, its values and Repeats. Repeats value should display NA when there are multiple values against single ID and it should display the Repeats value in case there is single value for repeats.
Initially I needed to display the max value of repeats so I tried the following view
ALTER VIEW [dbo].[BookingView1]
AS
SELECT bv.*, bd2.Repeats FROM Table1 bv
JOIN
(
SELECT distinct bd.id, bd.Repeats FROM table2 bd
JOIN
(
SELECT Id, MAX(Repeats) AS MaxRepeatCount
FROM table2
GROUP BY Id
) bd1
ON bd.Id = bd1.Id
AND bd.Repeats = bd1.MaxRepeatCount
) bd2
ON bv.Id = bd2.Id;
and this returns the correct result but when trying to implement the CASE it fails to return unique ID results. Please help!!
One method uses outer apply:
select t2.*, t1.repeats
from table2 t2 outer apply
(select (case when max(repeats) = min(repeats) then max(repeats)
else 'NA'
end) as repeats
from table1 t1
where t1.id = t2.id
) t1;
Two notes:
This assumes that repeats is a string. If it is a number, you need to cast it to a string.
repeats is not null.
For the sake of completeness, I'm including another approach that will work if repeats is NULL. However, Gordon's answer has a much simpler query plan and should be preferred.
Option 1 (Works with NULLs):
SELECT
t1.ID, t2.[Values],
CASE
WHEN COUNT(*) > 1 THEN 'NA'
ELSE CAST(MAX(Repeats) AS VARCHAR(2))
END Repeats
FROM (
SELECT DISTINCT t1.ID, t1.Repeats
FROM #table1 t1
) t1
LEFT OUTER JOIN #table2 t2
ON t1.ID = t2.ID
GROUP BY t1.ID, t2.[Values]
Option 2 (does not contain explicit subqueries, but does not work with NULLs):
SELECT DISTINCT
t1.ID,
t2.[Values],
CASE
WHEN COUNT(t1.Repeats) OVER (PARTITION BY COUNT(DISTINCT t1.Repeats), t1.ID) > 1 THEN 'NA'
ELSE CAST(t1.Repeats AS VARCHAR(2))
END Repeats
FROM #table1 t1
LEFT OUTER JOIN #table2 t2
ON t1.ID = t2.ID
GROUP BY t1.ID, t2.[Values], t1.Repeats
NOTE:
This may not give desired results if table2 has different values for the same ID.

SQL Server - Providing priority to where clause condtions

Please consider the following SQL.
declare #t1 table(site int, id int, name varchar(2))
declare #t2 table(site int, id int, mark int)
insert into #t1
select 1,1,'A'
union select 1,2,'B'
union select 1,3,'C'
union select 2,2,'D'
union select 2,3,'C'
insert into #t2
select 1,1,10
union select 1,2,20
union select 0,3,30
union select 1,3,40
union select 2,3,40
union select 2,3,40
select distinct a.site, a.id,a.name,b.mark
from #t1 a
inner join #t2 b
on (a.site =b.site or b.site = 0) and a.id = b.id
where a.site=1
It produces the following result
site id name mark
----------------------------
1 1 A 10
1 2 B 20
1 3 C 30
1 3 C 40
It's correct.
But I want a person's data exactly once. The SQL should first check whether there is an entry for a person in #t2 for a specific site. If entry is found, then use it. If not, the mark of that person will be the person's mark who has the same name in site 0.
In this case, I want the result as follows.
site id name mark
----------------------------
1 1 A 10
1 2 B 20
1 3 C 40
But if (1,3,40) isn't in #t2, The result should be as follows.
site id name mark
----------------------------
1 1 A 10
1 2 B 20
1 3 C 30
How can I do this?
I can do it using Common Table Expression.
So please provide me a faster way.
I'll run it on about 100 millions rows.
You can roll all of the conditions into the on clause:
declare #target_site as Int = 1
select distinct a.site, a.id, a.name, b.mark
from #t1 as a inner join
#t2 as b on a.site = #target_site and a.id = b.id and
( a.site = b.site or ( b.site = 0 and not exists ( select 42 from #t2 where site = #target_site and id = a.id ) ) )
Outer Join to the t2 table twice, and Use a subquery to ensure that only records that have a match or are zeroes are included.
Select distinct a.site, a.id, a.name,
coalesce(sm.mark, zs.mark) mark
from #t1 a
Left Join #t2 sm -- for site match
on sm.id = a.id
And sm.site = a.site
Left Join #t2 zs -- for zero site
on zs.id = a.id
And zs.site = 0
Where Exists (Select * From #t2
Where id = a.id
And Site In (a.Site, 0))
And a.site=1

Problem combining result of two different queries into one

I have two tables (TableA and TableB).
create table TableA
(A int null)
create table TableB
(B int null)
insert into TableA
(A) values (1)
insert into TableB
(B) values (2)
I cant join them together but still I would like to show the result from them as one row.
Now I can make select like this:
select
(select A from tableA) as A
, B from TableB
Result:
A B
1 2
But if I now delete from tableB:
delete tableB
Now when I run the same query as before:
select
(select A from tableA) as A
, B from TableB
I see this:
A B
But I was expecting seeing value from tableA
like this:
Expected Result:
A B
1
Why is this happening and how can I still see the value from TableA although selectB is returning 0 rows?
I am using MS SQL Server 2005.
Use a LEFT JOIN (although it's more of a cross join in your case).
If your db supports it:
SELECT a.a, b.b
FROM a
CROSS JOIN b
If not, do something like:
SELECT a.a, b.b
FROM a
LEFT JOIN b ON ( 1=1 )
However, once you have more rows in a or b, this will return the cartesian product:
1 1
1 2
2 1
2 2
This will actually give you what you're looking for, but if you only have one row per table:
select
(select A from tableA) as A
, (select B from TableB) as B
give this a try:
DECLARE #TableA table (A int null)
DECLARE #TableB table (B int null)
insert into #TableA (A) values (1)
insert into #TableB (B) values (2)
--this assumes that you don't have a Numbers table, and generates one on the fly with up to 500 rows, you can increase or decrease as necessary, or just join in your Numbers table instead
;WITH Digits AS
(
SELECT 0 AS nbr
UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6
UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
, AllNumbers AS
(
SELECT u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 AS Number
FROM Digits u1, Digits u2, Digits u3
WHERE u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 <= 500
)
, AllRowsA AS
(
SELECT
A, ROW_NUMBER() OVER (ORDER BY A) AS RowNumber
FROM #TableA
)
, AllRowsB AS
(
SELECT
B, ROW_NUMBER() OVER (ORDER BY B) AS RowNumber
FROM #TableB
)
SELECT
a.A,b.B
FROM AllNumbers n
LEFT OUTER JOIN AllRowsA a on n.Number=a.RowNumber
LEFT OUTER JOIN AllRowsB b on n.Number=b.RowNumber
WHERE a.A IS NOT NULL OR b.B IS NOT NULL
OUTPUT:
A B
----------- -----------
1 2
(1 row(s) affected)
if you DELETE #TableB, the output is:
A B
----------- -----------
1 NULL
(1 row(s) affected)
try this:
select a, (select b from b) from a
union
select b, (select a from a) from b
should retrieve you all the existing data.
you can filter it more by surrounding it with another select