SQL Server - Providing priority to where clause condtions - sql

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

Related

SQL - Get one of each in a join

I've got 2 tables. Table A and B.
Table A has an id and some data which isn't important for the question.
Table B has an id and an A_id. The last one is used to combine the 2 of them. There can be either multiple rows with the same A_id, only 1 or none at all.
I need a query which will do the following:
Get only 1 of each row from table A
Join table B into it
No duplicates from table A
I know it might sound complicated, so here is an example
Table A
id other info
1 ...
2 ...
3 ...
4 ...
Table B
id A_id
1 2
2 3
3 3
4 3
Output
A.id other info B.id A_id
1 ... NULL NULL
2 ... 1 2
3 ... 2 3
4 ... NULL NULL
So, even though there are multiple rows in table B of which A_id is 3, I only need the one of them. And even though there is no row in table B of which the A_id is 1 or 4, I still need both of them to show up.
This is as clear as I can possibly describe my question, please give feedback on how I can improve this question.
I think the simplest way is to use a correlated subquery:
select a.*,
(select max(b.id) from b where b.a_id = a.id)
from a;
I can't test it right now but it seems that you want something like this
SELECT * FROM A
LEFT JOIN B ON A.ID = B.A_ID
UPDATE:
WITH tmp AS (
SELECT MIN(ID) ID FROM B GROUP BY A_id
)
SELECT A.*, B.* FROM B
INNER JOIN tmp ON B.Id = tmp.ID
RIGHT JOIN A ON A.Id = B.A_Id
Assuming your database supports ANSI SQL and when there are multiple rows in B you want the last one based on ID:
with lastB (B_Id) as (
select max(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Oops. You edited your question and wnat the first one. Then simply make max(), min().
with lastB (B_Id) as (
select min(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Here is the MS SQL sample I promised for:
DECLARE #tableA TABLE ( id INT, other VARCHAR(10) );
DECLARE #tableB TABLE
(
id INT ,
A_Id INT ,
other VARCHAR(10)
);
INSERT #tableA
( id, other )
VALUES ( 1, 'v1' ),
( 2, 'v2' ),
( 3, 'v3' ),
( 4, 'v4' );
INSERT #tableB
( id, A_Id, other )
VALUES ( 1, 2, 'v21' ),
( 2, 3, 'v31' ),
( 3, 3, 'v32' ),
( 4, 3, 'v33' );
WITH fromB ( B_Id )
AS ( SELECT MIN(id)
FROM #tableB
GROUP BY A_Id
),
BRows
AS ( SELECT *
FROM #tableB
WHERE id IN ( SELECT B_Id
FROM fromB )
)
SELECT a.id AS A_Id ,
a.other AS A_Other ,
b.id AS B_Id ,
b.other AS B_Other
FROM #tableA a
LEFT JOIN BRows b ON a.id = b.A_Id;
Result:
A_Id A_Other B_Id B_Other
1 v1 NULL NULL
2 v2 1 v21
3 v3 2 v31
4 v4 NULL NULL
Big thank you for Gordon Linoff for pushing me in the right direction. The initial answer was:
select A.*,
(select count(B.A_id) from B where B.A_id = A.id)
from A
I'm sorry for not telling this in my question, but all I actually needed was to get every row from A and at the same time check if there was any row in table B which had the same value in A_id as the A.id had.
This query counts all rows which have the same value of A_id as A.id.
To be clear, the output will give:
A.id other info count
1 ... 0
2 ... 1
3 ... 3
4 ... 0

SQL joining on >=

I have a table like this in ORACLE
a b
-- --
1000 1
100 2
10 3
1 4
My other table has numbers like '67' or '112' in a column called numbers for example.
How can I join to this table using those values and get the correct result where >=1000 would be 1 and >= 100 would be 2 >=10 would be 3 etc.
I tried to do a
select g.b
from table o
join table2 g on o.column >= g.a
when I do this say 1002 was the value of g I would get the back these results.
1
2
3
4
when I just want 1
Easiest would be if your lookup table had ranges instead of just one number, such as row 1 = 1000,9999,1 and row 2 = 100,999,2 etc.
Then your join might be
SELECT OtherTable.n, lookup.b
from OtherTable
LEFT JOIN lookup on OtherTable.n between lookup.low and lookup.high
But, if you really want to use your original table, then on SQL Server, do this:
/*CREATE TABLE #A (a int,b int)
INSERT INTO #A VALUES (1000,1),(100,2),(10,3),(1,4)
CREATE TABLE #B (n INT)
INSERT INTO #B VALUES (67),(112),(4),(2001)
*/
SELECT B.n, A1.b
FROM #B B OUTER APPLY (SELECT TOP 1 a,b FROM #A A WHERE A.a<B.n ORDER BY A.a DESC) A1
Here's one way to do it using a subquery to get the MAX of column a, and then rejoining on the same table to get b:
select t.numericvalue, t2.b
from (
select t.numericvalue, max(t2.a) maxb
from table1 t
join table2 t2 on t.numericvalue >= t2.a
group by numericvalue
) t join table2 t2 on t.maxb = t2.a
SQL Fiddle Demo

SQL find identical group

Given a table like:
id key val
---- ---- -----
bob hair red
bob eyes green
And another table like:
id key val
---- ---- -----
fred hair red
fred eyes green
fred shoe 42
joe hair red
joe eyes green
greg eyes blue
greg hair brown
I'd like to find people in table b who match people in table a exactly, in this case Bob and Joe. Fred doesn't count because he also has a shoe size. This is in Sybase so there's no full outer join. I've come up with a select of a select with a union that returns people who definitely aren't the same, but I'm not sure how to efficiently select people who are.
Alternatively, if it's simpler, how can I check which groups in a occur in b more than once?
Try this
select a.id,b.id
from a
join b on a.[key] = b.[key] and a.val = b.val -- match all rows
join (select id,count(*) total from a group by id) a2 on a.id = a2.id -- get the total keys for table a per id
join (select id,count(*) total from b group by id) b2 on b.id = b2.id -- get the total keys for table b per id
group by a.id,b.id,a2.total,b2.total
having count(*) = a2.total AND count(*) = b2.total -- the matching row's total should be equal with each tables keys per id
After #t-clausen.dk comments I made a revision of the original sql code.
In this case i count each distinct pair/value that matches on both tables, with each tables distinct pair/value.
select td.aid,td.bid
from (
select a.id as aid,b.id as bid, count(distinct a.[key]+' '+a.val) total
from a
join b on a.[kry] = b.[key] and a.val = b.val
group by a.id,b.id
) td -- match all distinct attribute rows
join (select id,count(distinct [key]+' '+val) total from a group by id) a2 on td.aid = a2.id -- get the total distinct keys for table a per id
join (select id,count(distinct [key]+' '+val) total from b group by id) b2 on td.bid = b2.id -- get the total keys for table b per id
where td.total = a2.total AND td.total = b2.total -- the matching distinct attribute total should be equal with each tables distinct key-val pair
Tested on
Table a
bob hair red
bob eyes green
nick hair red
nick eyes green
nick shoe 45
Table b
fred hair red
fred eyes green
joe hair red
joe eyes green
fred shoe 42
You can emulate a full outer join by grabbing all ids in a subquery, and then left joining them in two directions:
select ids.id
from (
select distinct id
from #a
union
select id
from #b
) as ids
left join
#a a1
on a1.id = ids.id
left join
#b b1
on a1.id = b1.id
and a1.[key] = b1.[key]
and a1.val = b1.val
left join
#b b2
on b2.id = ids.id
left join
#a a2
on b2.id = a2.id
and b2.[key] = a2.[key]
and b2.val = a2.val
group by
ids.id
having sum(case when b1.id is null or a2.id is null then 1 else 0 end) = 0
Example at SE DATA.
This syntax will find the exact matches on different names in #t1 and #t2. I appologize because is written in MSSQL. I hope it can be converted to Sybase. After playing with it all day I want to share this beauty. I know these long scripts are not popular pointwise. I hope someone will appriciate it anyway.
This select make an exact match on #t2 within #t1.
I have populated the tables in this link https://data.stackexchange.com/stackoverflow/q/108035/
DECLARE #t1 TABLE(id varchar(10), [key] varchar(10), val varchar(10))
DECLARE #t2 TABLE(id varchar(10), [key] varchar(10), val varchar(10))
;WITH t1 AS (
SELECT t1.id, t1.[key], t1.val, count(*) count1, sum(count(*)) OVER(PARTITION BY t1.id) sum1 FROM #t1 t1
GROUP BY t1.id, t1.[key], t1.val
), t2 as (
SELECT t2.id, t2.[key], t2.val, count(*) count1, sum(count(*)) OVER(PARTITION BY t2.id) sum1 FROM #t2 t2
GROUP BY t2.id, t2.[key], t2.val
), t3 AS (
SELECT t1.*, sum(t1.count1) OVER(PARTITION BY t1.id) sum2
FROM t1
JOIN t2 on t1.val = t2.val AND t1.[key]=t2.[key]
AND t1.count1 = t2.count1 AND t1.sum1 = t2.sum1
)
SELECT t3.id, t3.[key], t3.val FROM t3
JOIN #t2 t ON t3.[key] = t.[key] AND t3.val = t.val
WHERE t3.sum2 = t3.sum1
Don't try the script, it doesn't contain data, use the link where the tables are populated.

SQL: Select lowest value that doesn't already exist

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...

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