How to left join to first row in SQL Server - sql

How to left join two tables, selecting from second table only the first row?
My question is a follow up of:
SQL Server: How to Join to first row
I used the query suggested in that thread.
CREATE TABLE table1(
id INT NOT NULL
);
INSERT INTO table1(id) VALUES (1);
INSERT INTO table1(id) VALUES (2);
INSERT INTO table1(id) VALUES (3);
GO
CREATE TABLE table2(
id INT NOT NULL
, category VARCHAR(1)
);
INSERT INTO table2(id,category) VALUES (1,'A');
INSERT INTO table2(id,category) VALUES (1,'B');
INSERT INTO table2(id,category) VALUES (1,'C');
INSERT INTO table2(id,category) VALUES (3,'X');
INSERT INTO table2(id,category) VALUES (3,'Y');
GO
------------------
SELECT
table1.*
,FirstMatch.category
FROM table1
CROSS APPLY (
SELECT TOP 1
table2.id
,table2.category
FROM table2
WHERE table1.id = table2.id
ORDER BY id
)
AS FirstMatch
However, with this query, I get inner join results. I want to get left join results. The tabel1.id in desired results should have '2' with NULL. How to do it?

use row_number and left join
with cte as(
select id,
category,
row_number() over(partition by id order by category) rn
from table2
)
select t.id, cte.category
from table1 t
left outer join cte
on t.id=cte.id and cte.rn=1
OUTPUT:
id category
1 A
2 (null)
3 X
SQLFIDDLE DEMO

select table1.id,
(SELECT TOP 1 category FROM table2 WHERE table2.id=table1.id ORDER BY category ASC) AS category
FROM table1

SELECT table1.id ,table2.category
FROM table1 Left join table2
on table1.id = table2.id
where table2.category = ( select top 1 category from table2 t where table1.id = t.id)
OR table2.category is NULL

Following the comment of t-clausen.dk this does the job:
change CROSS APPLY to OUTER APPLY

Related

Get the list of name column values which are not common in both the tables?

recently i gave an interview where the question was
suppose there are two tables in database.
Table T1 has a column named "name" in it and few other columns
Table T2 also has a column name "name" and few other columns
suppose table T1 has values in name column as
[n1,n2,n3,n4,n5]
and values in the "name" column of table T2 are
[n2,n4]
then output should be
[n1,n3,n5] as n2 and n4 are common in both tables
we needs to find the list of names which are not common in both the tables.
The solution that i provided him was using join in the below form
select name from table1 where name not in (select t1.name from table1 t1 join table2 t2 on t1.name=t2.name)
UNION
select name from table2 where name not in (select t1.name from table1 t1 join table2 t2 on t1.name=t2.name)
But he said there is still a better solution. I was not able to come up with any different and more efficient solution. What is the other efficient way to get the list of names if there is any?
If the NAME column does not have NULL values, there is also
select distinct(coalesce(a.name, b.name)) name
from table1 a
full join table1 b on a.name = b.name
where a.name is null or b.name is null
(Corrected WHERE condition, sorry...)
Use FULL OUTER JOIN:
SELECT DISTINCT(COALESCE(t1.NAME, t2.NAME)) AS NAME
FROM TABLE1 t1
FULL OUTER JOIN TABLE2 t2
ON t2.NAME = t1.NAME
WHERE t1.NAME IS NULL OR
t2.NAME IS NULL
A FULL OUTER JOIN is similar to a LEFT OUTER JOIN unioned with a RIGHT OUTER JOIN - it returns rows where data exists in the first table but not the second, or where it data exists in the second table but not the first. You could get the same effect by using
SELECT t1.NAME
FROM TABLE1 t1
LEFT OUTER JOIN TABLE2 t2
ON t2.NAME = t1.NAME
WHERE t2.NAME IS NULL
UNION
SELECT t2.NAME
FROM TABLE1 t1
RIGHT OUTER JOIN TABLE2 t2
ON t2.NAME = t1.NAME
WHERE t1.NAME IS NULL
and in fact the above is what you'd need to do if you were using a database which doesn't support the FULL OUTER JOIN syntax (e.g. MySQL, the last time I looked).
See this dbfiddle
Union the tables and return the values that don't have a count of 2:
create table t1 (
c1 int
);
create table t2 (
c1 int
);
insert into t1 values ( 1 );
insert into t1 values ( 3 );
insert into t2 values ( 2 );
insert into t2 values ( 3 );
commit;
select c1 only_in_one_table
from (
select 'T1' t, c1 from t1
union
select 'T2' t, c1 from t2
)
group by c1
having count(*) <> 2;
ONLY_IN_ONE_TABLE
1
2
I'm not a fan of not in with subqueries, because it behaves unexpectedly with null values. And the person asking would have to explain what "better" means. Your version is actually reasonable.
I might be inclined to approach this using aggregation:
select name
from ((select distinct name, 1 as in_table1, 0 as in_table2
from table1
) union all
(select distinct name, 0 as in_table1, 0\1 as in_table2
from table2
)
) t
group by name
having max(in_table1) <> max(in_table2);
In a real world case, you would probably have a separate table with all names. If so:
select n.*
from names n
where (not exists (select 1 from table1 t1 where t1.name = n.name) and
exists (select 1 from table2 t2 where t2.name = n.name
) or
(exists (select 1 from table1 t1 where t1.name = n.name) and
not exists (select 1 from table2 t2 where t2.name = n.name
);
This is usually the fastest approach because it does not involve any aggregation or duplicate removal.
If you want to use SET operator then find the solution as below:
CREATE TABLE TABLE1(NAME VARCHAR2(100));
CREATE TABLE TABLE2(NAME VARCHAR2(100));
INSERT INTO TABLE1 VALUES('A');
INSERT INTO TABLE1 VALUES('B');
INSERT INTO TABLE1 VALUES('C');
INSERT INTO TABLE2 VALUES('A');
INSERT INTO TABLE2 VALUES('B');
INSERT INTO TABLE2 VALUES('D');
SELECT
NAME
FROM
(
SELECT
NAME
FROM
TABLE1
UNION
SELECT
NAME
FROM
TABLE2
)
WHERE
NAME NOT IN (
SELECT
NAME
FROM
TABLE1
JOIN TABLE2 USING ( NAME )
);
Cheers!!
Yet another possible solution:
Find those that are in the first table but not in the second table using the MINUS operator (which is Oracle's implementation of the standard EXCEPT). Then UNION that with those that are in the second but not in the first.
(
select name
from t1
minus
select name
from t2
)
union all
(
select name
from t2
minus
select name
from t1
);
Given this setup:
create table t1
(
name varchar(10)
);
insert into t1 values ('Arthur');
insert into t1 values ('Zaphod');
create table t2
(
name varchar(10)
);
insert into t2 values ('Tricia');
insert into t2 values ('Zaphod');
This returns:
NAME
------
Arthur
Tricia
select id from((select id from table1)
union all
(select id from table2)) as t1
group by id having count(id)=1
Using the basic set operations the following query should work.
(
select name from table1
union all
select name from table2
)
minus
(
select name from table1
intersect
select name from table2
)
;
Regards
Akash

Get all records using join

I have two tables like the following:
Table1
Id Table1_Col
1 A
2 B
3 C
4 D
5 E
Table2
Id Table1_Col Table2_Col
1 A Test
I want the count of (Table1_Col) in Table2 and I need query for the following output:
Expected Output
Table1_Col Count_Table2_Col
A 1
B 0
C 0
D 0
E 0
What I have tried so far:
select Table1_Col,Count(Table2_Col) from table1 t1
Left outer join table2 t2 on t1.Table1_Col = t2.Table1_Col
Please provide me a proper solution for this.
You need GROUP BY, when using aggregate methods. Also Table1_Col existing in both tables, so please use with the proper table alias for the columns.
The query below will return your expected result. Please find the demo too.
select T1.Table1_Col, Count(T2.Table2_Col) AS Table2_Col
from table1 t1
Left outer join table2 t2 on t1.Table1_Col = t2.Table1_Col
GROUP BY T1.Table1_Col
Demo on db<>fiddle
UPDATE: As per the comment in the post, based on your fiddle, the condition t3.visitno=1 should be in the LEFT OUTER JOIN and not in the WHERE clause, so the following query will work:
select t3.pvisitno, t1.DocName, count(t2.vdocid) as [count]
from Document_type t1
left outer join visitdocs t2 on t2.DocId = t1.DocId
left outer join visittbl t3 on t3.visitno = t2.visitno and t3.visitno=1
group by t3.pvisitno,t1.DocName
order by count(t2.vdocid) desc
db<>fiddle demo for the revised fiddle
Try this query:
select t1.Table1_Col,
sum(case when Table2_Col is null then 0 else 1 end) Count_Table2_Col
from Table1_Col t1
left join Table2 t2 on t1.Table1_Col = t2.Table1_Col
group by t1.Table1_Col
You can try this:
Declare #t table ( id int ,col varchar(50))
insert into #t values (1,'A')
insert into #t values (2,'B')
insert into #t values (3,'C')
Declare #t1 table ( id int ,col varchar(50),col2 varchar(50))
insert into #t1 values (1,'A','TEST')
select t.col,count(t1.id) countT2 from #t t left join #t1 t1
on t.id=t1.id
group by t.col
Here's another option:
select t1.Table1_Col, coalesce(x.cnt, 0) cnt
from table1 t1
left outer join (select Table2_Col, count(*) cnt from table2 group by Table2_Col) x
on x.Table2_Col = t1.Table1_Col;
The idea here is to create an inline view of table2 with its counts and then left join that with the original table.
The "coalesce" is necessary because the inline view will only have records for the rows in table2, so any gaps would be "null" in the query, while you specified you want "0".

Use multiple WITH tablename AS (...) statements SQL Server

I'm trying to create two temporary tables and join them with a permanent table. For example:
WITH temp1 AS (COUNT(*) AS count_sales, ID FROM table1 GROUP BY ID)
WITH temp2 AS (COUNT(*) AS count_somethingelse, ID FROM table2 GROUP BY ID)
SELECT *
FROM table3 JOIN table2 JOIN table1
ON table1.ID = table2.ID = table3.ID
but there seems to be an issue having multiple WITH tablename AS (...) statments. I tried a semicolon.
Your query should look more like this:
WITH temp1 AS (
SELECT COUNT(*) AS count_sales, ID
FROM table1
GROUP BY ID
),
temp2 AS (
SELECT COUNT(*) AS count_somethingelse, ID
FROM table2
GROUP BY ID
)
SELECT *
FROM temp2 JOIN
temp1
ON temp1.ID = temp2.ID;
Your query has multiple errors. I would suggest you start by understanding why this version works -- or at least does something other than report on syntax errors. Then, go back and study SQL some more.
I'm trying to create two temporary tables
Just for clarity... you used a CTE which is not quite the same thing as a temporary table. You've also tagged 'temp tables', so you want a temp table? You can store query results in a declared table variable or an actual temp table.
An example of declared table variables:
DECLARE #table1 TABLE(id int, count_sales int)
INSERT INTO #table1 (id, count_sales)
SELECT ID, COUNT(*)
FROM table1
GROUP BY ID
--or however you populate temp table1
DECLARE #table2 TABLE(id int, count_somethingelse int)
INSERT INTO #table2 (id, count_somethingelse)
SELECT ID, COUNT(*)
FROM table2
GROUP BY ID
--or however you populate temp table2
SELECT T3.id
--,T2.(some column)
--,T1.(some column)
--,etc...
FROM table3 T3 INNER JOIN #table2 T2 ON T3.id = T2.id
INNER JOIN #table1 T1 ON T3.id = T1.id

Query for earliest datetime and corresponding number field

I'm attempting to update a table with a dollar amount based on the earliest datetime field from another table. For example:
Table 1
ID|INITIAL_ANNUAL_RATE_AMT|
1 | NULL (I want to update this to 25.02)
Table 2
ID|ANNUAL_RATE_AMT|STARTING_DATE|
1 |25.01 |1/1/2014
1 |25.02 |1/1/2013
I've got a query like this that retreives the earliest date from table 2 and the corresponding objects ID:
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.ID
But how can I leverage this into an update statement that sets the INITIAL_ANNUAL_RATE_AMT in table 1 to the earliest corresponding value in table 2?
Something like this (which currently fails):
update t1
set t1.Initial_Annual_Rate__c = t3.ANNUAL_RATE_AMT
from t1, t2
left join
(select t2.ID
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from t2
group by t2.DEAL_ID)
as t3 ON (t3.DEAL_ID = t1.DEAL_ID)
One way is to use a CTE
;WITH C AS(
SELECT t.ID, EARLIEST_START_DATE, ANNUAL_RATE_AMT FROM(
select ID,
MIN(t2.STARTING_DATE) as EARLIEST_START_DATE
from #Table2 AS t2
group by t2.ID) t
INNER JOIN #Table2 AS t2 ON t2.ID = t.ID AND t.EARLIEST_START_DATE = t2.STARTING_DATE
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT = C.ANNUAL_RATE_AMT
FROM #Table1 AS t1
INNER JOIN C ON C.ID = t1.ID
SQLFIDDLE
Another method, using a window function to get the first row in each ID partitioned set:
-- Setup test data
declare #table1 table (ID int, INITIAL_ANNUAL_RATE_AMT decimal(9,2))
declare #table2 table (ID int, ANNUAL_RATE_AMT decimal(9,2), STARTING_DATE date)
INSERT INTO #table1 (ID, INITIAL_ANNUAL_RATE_AMT)
SELECT 1, NULL
INSERT INTO #table2 (ID, ANNUAL_RATE_AMT, STARTING_DATE)
SELECT 1,25.01,'1/1/2014'
UNION SELECT 1,25.02,'1/1/2013'
-- Do the update
;with table2WithIDRowNumbers as (
select ID, ANNUAL_RATE_AMT, STARTING_DATE, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY STARTING_DATE) as rowNumber
FROM #table2
)
UPDATE t1
SET INITIAL_ANNUAL_RATE_AMT=t2.ANNUAL_RATE_AMT
FROM table2WithIDRowNumbers t2
INNER JOIN #table1 t1 ON t1.ID=t2.ID
where t2.rowNumber=1
-- Show the result
SELECT * from #table1

INSERT SELECT TOP (n) ORDER BY column not included in the destination table

Is that possible to insert from select when the select statement has more columns that the table to insert to ?
consider scenario:
INSERT INTO table_1 --table_1 consist only of one column
SELECT TOP 10 col1, col2 --col_2 is only selected because is used in ORDER BY
FROM table_2
ORDER BY col2 DESC
The above statement will result with error. One way I would accomplish that is to use sub-query like that
INSERT INTO table_1
SELECT TOP 10 col1
FROM (
SELECT col1, col2
FROM table_2
ORDER BY col2 DESC
) AS t
But I'm wondering if there is a straight forward way for example using equal operator like in UPDATE statement.
UPDATE
I apologize for submitting oversimplified example. That was because I've took it for granted this will apply to my scenario without actually testing it.
This is the reproduced context of my query (tested on sqlfiddle as have no SQL Server installed on my home PC)
CREATE TABLE table_1 (id INT)
CREATE TABLE table_2 (id INT, col2 INT)
CREATE TABLE table_3 (id INT, col2 INT)
INSERT INTO table_2 VALUES (1,3),(2,2),(3,1)
INSERT INTO table_3 VALUES (1,3),(1,2),(3,1)
INSERT INTO table_1
SELECT TOP 1 t.id, t.Qty
FROM table_2
INNER JOIN
(
SELECT table_2.id, COUNT(table_3.id) AS Qty
FROM table_2
INNER JOIN table_3 on table_3.id = table_2.id
GROUP BY table_2.id
) AS t ON (t.id = table_2.id)
ORDER BY t.Qty
The original query is much more complex, therefore I would like to avoid another sub-query if this is possible.
This query results with the error saying:
Column name or number of supplied values does not match table definition.: INSERT INTO table_1 SELECT TOP 1 table_1.id FROM table_1 INNER JOIN ( SELECT table_2.id, COUNT(table_3.id) AS Qty FROM table_2 INNER JOIN table_3 on table_3.id = table_2.id GROUP BY table_2.id ) AS t ON (t.id = table_1.id) ORDER BY t.Qty
Up don't need to include the order by col in the select list.
CREATE TABLE table_1 (id INT)
GO
CREATE TABLE table_2 (col1 INT, col2 INT)
GO
INSERT INTO table_2 VALUES (1,3),(2,2),(3,1)
GO
INSERT INTO table_1 --table_1 consist only of one column
SELECT TOP 2 col1
FROM table_2
ORDER BY col2 DESC
Have you tried specifying the column that you are trying to insert into?
INSERT INTO table_1 (id)
SELECT TOP 1 table_1.id
FROM table_1
INNER JOIN
(
SELECT table_2.id, COUNT(table_3.id) AS Qty
FROM table_2
INNER JOIN table_3 on table_3.id = table_2.id
GROUP BY table_2.id
) AS t ON (t.id = table_1.id)
ORDER BY t.Qty