How to rewrite a LEFT JOIN - sql

Please, consider the following query:
create table lt (id1 int, val1 string);
insert into lt VALUES (1, "one"), (2, "two"), (3, "three");
create table rt (id2 int, val2 string);
insert into rt VALUES (2, "two"), (3, "three"), (4, "four");
select * from lt left join rt on id1=id2;
+-----+-------+------+-------+
| id1 | val1 | id2 | val2 |
+-----+-------+------+-------+
| 1 | one | NULL | NULL |
| 2 | two | 2 | two |
| 3 | three | 3 | three |
+-----+-------+------+-------+
For this specific example I can rewrite the LEFT JOIN as INNER JOIN + query that gets all IDs that are not in the "rt" table:
select lt.*, NULL as id2, NULL as val2 from lt where id1 not in (select id2 from rt)
union all
select * from lt join rt on id1=id2;
+-----+-------+------+-------+
| id1 | val1 | id2 | val2 |
+-----+-------+------+-------+
| 1 | one | NULL | NULL |
| 2 | two | 2 | two |
| 3 | three | 3 | three |
+-----+-------+------+-------+
Both querires give same result for this example. But is this generally true? Can I rewrite any LEFT JOIN in this fashion (or may be there is a shorter way)?

You can try below -
DEMO
select val1, NULL as id2, NULL as val2 from lt where id1 not in (select id2 from
rt)
union
select val1,id1, val1 from lt where 1=1 and id1 in (select id2 from rt)
OUTPUT:
val1 id2 val2
one
two 2 two
three 3 three

Related

How to join and union at the same time

I'm sure this has a simple solution but I'm struggling to find it.
I have two tables
CREATE TABLE t1 (
"name" VARCHAR(1),
"id" INTEGER,
"data1" VARCHAR(2)
);
INSERT INTO t1
("name", "id", "data1")
VALUES
('a', '1', 'a1'),
('d', '1', 'd1');
CREATE TABLE t2 (
"name" VARCHAR(1),
"id" INTEGER,
"data2" VARCHAR(2)
);
INSERT INTO t2
("name", "id", "data2")
VALUES
('d', '1', 'd2'),
('k', '1', 'k2');
I want this final combined table:
| name | id | data1 | data2 |
| ---- | --- | ----- | ----- |
| a | 1 | a1 | |
| d | 1 | d1 | d2 |
| k | 1 | | k2 |
Things I've tried:
Do a union
select
t1.name,
t1.id,
t1.data1,
NULL as data2
from t1
union
select
t2.name,
t2.id,
NULL as data1,
t2.data2
from t2
| name | id | data1 | data2 |
| ---- | --- | ----- | ----- |
| a | 1 | a1 | |
| d | 1 | d1 | |
| d | 1 | | d2 |
| k | 1 | | k2 |
Do a full join
select * from t1
full join t2 on t2.id = t1.id
and t2.name = t1.name;
| name | id | data1 | name | id | data2 |
| ---- | --- | ----- | ---- | --- | ----- |
| a | 1 | a1 | | | |
| d | 1 | d1 | d | 1 | d2 |
| | | | k | 1 | k2 |
The answer is somewhere in between 😅
You're looking for a FULL OUTER JOIN:
SELECT COALESCE(t1.name, t2.name) AS name,
COALESCE(t1.id, t2.id) AS id,
t1.data1,
t2.data2
FROM t1
FULL OUTER JOIN t2 ON t2.name = t1.name
Output:
name id data1 data2
a 1 a1 null
d 1 d1 d2
k 1 null k2
Demo on db-fiddle
I think you just want a full join:
select *
from t1 full join
t2
using (name, id);
Thanks, Gordon and Nick for your answers.
I checked the documentation and found an even shorter variation:
select *
from t1
natural full join t2;
From the documentation
Furthermore, the output of JOIN USING suppresses redundant columns: there is no need to print both of the matched columns, since they must have equal values.
While JOIN ON produces all columns from T1 followed by all columns from T2, JOIN USING produces one output column for each of the listed column pairs (in the listed order), followed by any remaining columns from T1, followed by any remaining columns from T2.

Select records within a group

Not sure how to frame this question so asking with an example.
From the below table, I want to find out all those records which are not defined with type as 'A'.
So from this table I want to find out the record with ID as 2.
TableA
+-----+------+
| ID1 | Type |
+-----+------+
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 2 | C |
| 3 | A |
| 3 | B |
| 3 | C |
+-----+------+
There is also a TableB, if we want to use.
+-----+
| ID2 |
+-----+
| 1 |
| 2 |
| 3 |
+-----+
Thanks a lot for helping.
One method is to use a HAVING clause with a conditional COUNT:
SELECT ID1
FROM dbo.YourTable
GROUP BY ID1
HAVING COUNT(CASE WHEN [Type] = 'A' THEN 1 END) = 0;
You can use a CTE to select all the IDs that are associated to the value you want to exclude.
Then you can use a subquery to filter out those IDs:
declare #TableA table (ID1 int, Type char(1))
insert into #TableA
values
(1, 'A')
,(1, 'B')
,(1, 'C')
,(2, 'B')
,(2, 'C')
,(3, 'A')
,(3, 'B')
,(3, 'C')
;with filteredIds
as
(
select
distinct ID1
from #TableA
where Type ='A'
)
select
*
from #TableA
where
ID1 not in (select id1 from filteredIds)
The result contains only the records that do not have value 'A' (in your example records with ID1=2):
You can use not exists:
select t2.id2
from TableB t2
where not exists (select 1
from TableA t1
where t2.id2 = t1.id1 and t1.Type = 'A'
);
With an index on TableA(ID1, Type), this is probably the fastest method under most circumstances.
Note that this also finds ids that are not in TableA at all.

Problem with creating a Procedure which should filter data

I need to create a procedure which filters data and insert it to a table.
I have 4 tables: (the keys are the id's)
table1:
id1
parameter1
parameter2
parameter3
table2:
id1
id2
table3:
id2
id3
value1
value2
value3
table4:
id2
id3
value1
value2
value3
I need to create a procedure which inserts to table 4 the values from table 3: value1, value2, value3, only if they are smaller than or equal to the parameters from table 1 (parameter1, parameter2, parameter3)
I've tried the next thing:
insert into table4 (
,Id2
,Id3
,value1
,value2
,value3
)
select
,Id2
,Id3
,value1
,value2
,value3
FROM table1
LEFT JOIN table2 ON table1.id1 = table2.id1
LEFT JOIN table3 ON table2.id2 = table3.id3
WHERE table3.value1 <= table1.parameter1
and table3.value2 <= table1.parameter2
and table3.value3 <= table1.parameter3
But each run, 3 new rows are inserted to table4 with different values and wrong values (they are bigger than the parameters).
What am I doing wrong? Should I create the procedure with IF statement instead? How to make it right?
I've added an example.
As you can see, the new table: table 4, has only 2 rows, because the value of 'value 2' in table3 (value 2= 12) is greater than 'parameter2' (parameter2= 11) (from table 1)
table1:
Id1 | Parameter1 | Parameter2 | Parameter3 |
=====+============+=============+============+
1 | 1 | 2 | 3 |
2 | 10 | ** 11 ** | 12 |
3 | 10 | 15 | 16 |
table2:
Id1 | Id2 |
=====+=====+
1 | 4 |
2 | 5 |
3 | 6 |
table3:
Id2 | Id3 | Value1| Value2| Value3|
=====+=====+========+=======+=======+
4 | 7 | 1 | 2 | 3 |
5 | 8 | 10 |** 12**| 12 |
6 | 9 | 1 | 2 | 3 |
The new table (table 4) should be:
table4:
Id2 | Id3 | Value1| Value2| Value3|
=====+=====+========+=======+=======+
4 | 7 | 1 | 2 | 3 |
6 | 9 | 1 | 2 | 3 |
If you use table3 as the table you select from, you can use joins with the other tables as filters when inserting. Try this (for SQL Server):
INSERT INTO table4 (
id2
,id3
,value1
,value2
,value3
)
SELECT
t3.id2
,t3.id3
,t3.value1
,t3.value2
,t3.value3
FROM table3 t3
JOIN table2 t2 on t3.id2 = t2.id2
JOIN table1 t1 on t2.id1 = t1.id
and t3.value1 <= t1.parameter1
and t3.value2 <= t1.parameter2
and t3.value3 <= t1.parameter3

MS SQL Server multiple column sort independently

So to get things out of the way, due to structure i'm left in a situation of trying to sort some columns of data in SQL in a way that is evading me.
The problem is that i need multiple sets of 2 columns sorted independently, For example i have something like this:
Name | Val1 | Name | Val2 | Name | Val3
A | 2 | A | 1 | A | 3
B | 1 | B | 3 | B | 2
C | 3 | C | 2 | C | 1
and i need the table to be sorted by the highest of each value:
Name | Val1 | Name | Val2 | Name | Val3
C | 3 | B | 3 | A | 3
A | 2 | C | 2 | B | 2
B | 1 | A | 1 | C | 1
I don't seem to know how to organise using ROW_NUMBER() and various other things i have done through long searches being able to separate out individual columns for ordering but i don't know how i can keep two linked while the others sort independently, Can anyone help?
EDIT:
The Data is Extrapolated from one table after calculations had been done for their values.
So say i have my table of:
Name | Val1 | Val2 | Val3 |
A | 2 | 1 | 3 |
B | 1 | 3 | 2 |
C | 3 | 2 | 1 |
The values are names specifically are just used for example, but are wildly Differing values.
So from that table of final results i need to get the results in the format that the name with the highest value will be on top for each individual value
SELECT Name AS N1,
Val1,
Name As N2,
Val2
etc
EDIT: Example:
Name1|Units|Name2|Units| Name3|Units
AF |218 |AF |0.83 | AF |1.04
AD |172 |AD |0.49 | AD |1.05
AF |116 |AF |0.87 | AF |1.06
AF |324 |AF |0.84 | AF |1.10
If I understand your question correctly, consider the following approach:
CREATE TABLE #NameValue (
Name varchar(10),
Val1 int,
Val2 int,
Val3 int
)
INSERT INTO #NameValue
VALUES
('A', 102, 201, 303),
('B', 101, 203, 302),
('C', 103, 202, 301);
WITH nv1 AS (
SELECT Name, Val1, ROW_NUMBER() OVER (ORDER BY Val1 DESC) AS RN1
FROM #NameValue
),
nv2 AS (
SELECT Name, Val2, ROW_NUMBER() OVER (ORDER BY Val2 DESC) AS RN2
FROM #NameValue
),
nv3 AS (
SELECT Name, Val3, ROW_NUMBER() OVER (ORDER BY Val3 DESC) AS RN3
FROM #NameValue
)
SELECT
nv1.Name AS Name1, nv1.Val1,
nv2.Name AS Name2, nv2.Val2,
nv3.Name AS Name3, nv3.Val3
FROM nv1
LEFT JOIN nv2 ON (nv1.RN1 = nv2.RN2)
LEFT JOIN nv3 ON (nv1.RN1 = nv3.RN3)
Output:
Name1 Val1 Name2 Val2 Name3 Val3
C 103 B 203 A 303
A 102 C 202 B 302
B 101 A 201 C 301

Get one row of grouped objects

I have a table that contains a Husband to Wife realtion.
The table contains two rows for each realtion(BTW,gender has no meaning.it could be Husband-Husband and Wife-Wife. just saying). meaning, the table might show result of two rows for a "connection":
Wife--Husband and\or Husband--Wife
The table looks like this:
Id1 | Id2 | ConnectiondID | RelatedConnectionId
-----------------------------------------------------
123 | 333 | FF45 | F421
333 | 123 | F421 | FF45
456 | 987 | F333 | F321
987 | 456 | F321 | F333
My expected result is to have only one relation per group:
Id1 | Id2
----------
123 | 333
456 | 987
This is actually very simple assuming you only want couples and your ID values are all unique and numeric, and does not require any self joins, functions or grouping:
declare #t table(Id1 int,Id2 int,ConnectiondID nvarchar(5),RelatedConnectionId nvarchar(5));
insert into #t values(123,333,'FF45','F421'),(333,123,'F421','FF45'),(456,444,'FF46','F422'),(444,456,'F422','FF46'),(789,555,'FF47','F423'),(555,789,'F423','FF47');
select *
from #t
where Id1 < Id2
order by Id1
Output:
+-----+-----+---------------+---------------------+
| Id1 | Id2 | ConnectiondID | RelatedConnectionId |
+-----+-----+---------------+---------------------+
| 123 | 333 | FF45 | F421 |
| 444 | 456 | F422 | FF46 |
| 555 | 789 | F423 | FF47 |
+-----+-----+---------------+---------------------+
If I am understanding your question correctly, you need to perform a self-join on the table e.g. ON t1.id1 = t2.id2 or ON t1.ConnectionId = t2.RelatedConnectionID and obviously this is joining both ways.
To limit this to just one way add a condition on the join predicate such that one of the values is less than or greater than the other; e.g.
DECLARE #tbl table( Id1 smallint PRIMARY KEY, Id2 smallint,ConnectiondID char(5),RelatedConnectionId char(5));
INSERT #tbl(Id1,Id2,ConnectiondID,RelatedConnectionId)
VALUES(123,333,'FF45','F421'),
(333,123,'F421','FF45'),
(456,222,'FF45','F421'),
(222,456,'F421','FF45'),
(789,111,'FF45','F421'),
(111,789,'F421','FF45');
SELECT *
FROM #tbl t1
JOIN #tbl t2 ON t2.Id1 = t1.Id2 AND t2.Id1 > t1.Id1;
For example
DECLARE #T TABLE (id1 int, id2 int,ConnectiondID varchar(5),RelatedConnectionId varchar(5) )
INSERT INTO #T (Id1,Id2,ConnectiondID,RelatedConnectionId)
VALUES
(123 , 333 ,'FF45','F421'),
(333 , 123 , 'F421','FF45'),
(2123 , 2333 ,'2FF45','2F421'),
(2333 , 2123 , '2F421','2FF45'),
(3 , 2 , 'AAAA','BBB'),
(2 , 3 , 'BBB','AAAA')
SELECT
a.*
FROM
#t a
WHERE
CASE
WHEN ConnectiondID > RelatedConnectionId
THEN RelatedConnectionId
ELSE NULL
END IS NULL