Lets say I have a table1:
id name
-------------
1 "one"
2 "two"
3 "three"
And a table2 with a foreign key to the first:
id tbl1_fk option value
-------------------------------
1 1 1 1
2 2 1 1
3 1 2 1
4 3 2 1
Now I want to have as a query result:
table1.id | table1.name | option | value
-------------------------------------
1 "one" 1 1
2 "two" 1 1
3 "three"
1 "one" 2 1
2 "two"
3 "three" 2 1
How do I achieve that?
I already tried:
SELECT
table1.id,
table1.name,
table2.option,
table2.value
FROM table1 AS table1
LEFT outer JOIN table2 AS table2 ON table1.id = table2.tbl1fk
but the result seems to omit the null vales:
1 "one" 1 1
2 "two" 1 1
1 "one" 2 1
3 "three" 2 1
SOLVED: thanks to Mahmoud Gamal: (plus the GROUP BY)
Solved with this query
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1fk
AND t1.option = t2.option
group by t1.id, t1.name, t2.option, t2.value
ORDER BY t1.id, t1.name
You have to use CROSS JOIN to get every possible combination of name from the first table with the option from the second table. Then LEFT JOIN these combination with the second table. Something like:
SELECT
t1.id,
t1.name,
t2.option,
t2.value
FROM
(
SELECT t1.id, t1.name, t2.option
FROM table1 AS t1
CROSS JOIN table2 AS t2
) AS t1
LEFT JOIN table2 AS t2 ON t1.id = t2.tbl1_fk
AND t1.option = t2.option
SQL Fiddle Demo
Simple version: option = group
It's not specified in the Q, but it seems like option is supposed to define a group somehow. In this case, the query can simply be:
SELECT t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(option)) AS option FROM table2) o
CROSS JOIN table1 t1
LEFT JOIN table2 t2 ON t2.option = o.option AND t2.tbl1_fk = t1.id
ORDER BY o.option, t1.id;
Or, if options are not numbered in sequence, starting with 1:
...
FROM (SELECT DISTINCT option FROM table2) o
...
Returns:
id | name | option | value
----+-------+--------+-------
1 | one | 1 | 1
2 | two | 1 | 1
3 | three | |
1 | one | 2 | 1
2 | two | |
3 | three | 2 | 1
Faster and cleaner, avoiding the big CROSS JOIN and the big GROUP BY.
You get distinct rows with a group number (grp) per set.
Requires Postgres 8.4+.
More complex: group indicated by sequence of rows
WITH t2 AS (
SELECT *, count(step OR NULL) OVER (ORDER BY id) AS grp
FROM (
SELECT *, lag(tbl1_fk, 1, 2147483647) OVER (ORDER BY id) >= tbl1_fk AS step
FROM table2
) x
)
SELECT g.grp, t1.id, t1.name, t2.option, t2.value
FROM (SELECT generate_series(1, max(grp)) AS grp FROM t2) g
CROSS JOIN table1 t1
LEFT JOIN t2 ON t2.grp = g.grp AND t2.tbl1_fk = t1.id
ORDER BY g.grp, t1.id;
Result:
grp | id | name | option | value
-----+----+-------+--------+-------
1 | 1 | one | 1 | 1
1 | 2 | two | 1 | 1
1 | 3 | three | |
2 | 1 | one | 2 | 1
2 | 2 | two | |
2 | 3 | three | 2 | 1
-> SQLfiddle for both.
How?
Explaining the complex version ...
Every set is started with a tbl1_fk <= the last one. I check for this with the window function lag(). To cover the corner case of the first row (no preceding row) I provide the biggest possible integer 2147483647 the default for lag().
With count() as aggregate window function I add the running count to each row, effectively forming the group number grp.
I could get a single instance for every group with:
(SELECT DISTINCT grp FROM t2) g
But it's faster to just get the maximum and employ the nifty generate_series() for the reduced CROSS JOIN.
This CROSS JOIN produces exactly the rows we need without any surplus. Avoids the need for a later GROUP BY.
LEFT JOIN t2 to that, using grp in addition to tbl1_fk to make it distinct.
Sort any way you like - which is possible now with a group number.
try this
SELECT
table1.id, table1.name, table2.option, table2.value FROM table1 AS table11
JOIN table2 AS table2 ON table1.id = table2.tbl1_fk
This is enough:
select * from table1 left join table2 on table1.id=table2.tbl1_fk ;
Related
If I have two different tables in a SQL Server 2019 database as follows:
Table1
|id | name |
+-----+--------+
| 1 | rose |
| 2 | peter |
| 3 | ann |
| 4 | rose |
| 5 | ann |
Table2
| name2 |
+--------+
|rose |
|ann |
I would like to retrieve only the last tow ids from table1 (which in this case 4 and 5) that match name2 in table2. In other words, match happens only once on the last added names in table1, furthermore, the ids (4, 5) to be inserted in table2.
How to do that using SQL?
Thank you
You can use row_number()
select name,id from
(
select *, row_number() over(partition by t.name order by id desc) as rn
from table1 t join table2 t1 on t.name=t1.name2
)A where rn=1
Your question is vague, so there could be many answers here. My first thought is that you simply want an inner join. This will fetch ONLY the data that both tables share.
SELECT Table1.*
FROM Table1
INNER JOIN Table2 on Table1.name = Table2.name2
You seem to be describing:
select . . . -- whatever columns you want
from (select top (2) t1.*
from table1 t1
order by t1.id desc
) t1 join
table2 t2
on t2.name2 = t1.name;
This doesn't seem particularly useful for the data you have provided, but it does what you describe.
EDIT:
If you want only the most recent rows that match, use row_number():
select . . . -- whatever columns you want
from (select t1.*,
row_number() over (partition by name order by id desc) as seqnum
from table1 t1
) t1 join
table2 t2
on t2.name2 = t1.name and t1.seqnum = 1;
I have to select every name from table1 where there's tuples that match every type from table2 without grouping or aggregate functions.
table1 table2
name|type type|info
a | 1 1 | .
a | 2 2 | ..
a | 3 3 | ...
b | 1
b | 2
b | 3
c | 2
From here, it should output
name|
a |
b |
edit:
ended up doing something like
SELECT distinct outside.name
FROM table1 outside
WHERE '' NOT IN
[ (SELECT *
FROM table1 t
WHERE t.name=outside.name)
RIGHT OUTER JOIN
table2 ]
Second select makes a table with empty values for names that don't have a type in table2. So if '' isn't in the second select that means it has a tuple for every type in table2. I think
Here is one method:
select t1.name
from table1 t1
where exists (select 1 from table2 t2 where t2.type = t1.type)
group by t1.name
having count(distinct t1.type) = (select count(distinct t2.type) from table2);
This filters t1 down to the matches in t2. It then counts the number that match.
This uses count(distinct), which allows duplicates in the respective tables. If there are no duplicates, then just use count().
If I have tables like this:
t1:
id | name
----+------
1 | a
2 | b
3 | c
4 | d
t2:
id | value
----+-------
10 | xxx
20 | yyy
30 | zzz
t_join:
t1_id | t2_id
-------+-------
1 | 10
2 | 20
3 | 30
A SELECT query for t1.id=1 looks like:
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=1;
And of course returns:
id | name | value
----+------+-------
1 | a | xxx
If I do the same thing with id=4, I get nothing.
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=4;
id | name | value
----+------+-------
(0 rows)
And, if I do the same thing with a nonsense id=1234132, I also get nothing.
SELECT t1.id, t1.name, t2.value FROM t1, t2,t_join WHERE t1.id=t_join.t1_id AND t2.id=t_join.t2_id AND t1.id=1234132;
id | name | value
----+------+-------
(0 rows)
Is there a way I can differentiate between having an empty result (id=4) vs something that's null (id=1234132)? I guess I want verification that the id I'm checking exists without a separate query. Is this possible?
How about a left join:
SELECT t1.id, t1.name, t2.value
FROM t1 LEFT JOIN
t_join
ON t1.id = t_join.t1_id LEFT JOIN
t2
ON t2.id = t_join.t2_id
WHERE t1.id = 1;
If t.id is not found, you'll get no rows. If there are no matches in t2, then you'll get a NULL value.
Also, a simple rule: Never use commas in the FROM clause. Always, always use proper, explicit JOIN syntax.
------------------
| **table 1** |
------------------
| 1 | 400 |
| 2 | 220 |
| 3 | 123 |
------------------
| **table 2** |
------------------
| 1 | 100 |
formula : table1 - table2 where table1.id=table2.id
------------------
| **Result** |
------------------
| 1 | 300 |
| 2 | 220 |
| 3 | 123 |
You want an outer join to get all rows from table_1 and the matching ones from table2
select t1.id, t1.val - coalesce(t2.val, 0) as result
from table_1 t1
left join table_2 t2 on t1.id = t2.id;
The coalesce(t2.val, 0) is necessary because the outer join will return null for those rows where no id exists in table_2 but t1.val - null would yield null
select t1.id,
nvl2(t2.val,t1.val-t2.val,t1.val) val
from t1,t2
where t1.id=t2.id(+)
order by t1.id;
Try this
select t1.col1, t1.col2-t2.col1 as balance from
table1 t1 left join table2 t2 on t1.col1=t2.col1
I don't the syntax in Oracle sql, but I can give the solution in mysql.
Consider the table with 2 columns:
id , value
SELECT table1.id, table1.value - table2.value
FROM table1, table2
WHERE table1.id=table2.id
OR
SELECT table1.id, table1.value
FROM table1, table2
WHERE NOT (table1.id =table2.id)
In some cases using scalar subquery caching could give better performance. It is on developer to compare execution plans and decide which query is the most appropriate.
with t1 (id, num) as
(
select 1, 400 from dual union all
select 2, 220 from dual union all
select 3, 123 from dual
),
t2(id, num) as
(
select 1, 100 from dual
)
select id,
num - nvl((select num from t2 where t2.id = t1.id), 0) result
from t1;
This is just to show you a different technique for solving problems in which you try to get data from several tables, but some may not have matching rows.
Using outer join in this case is in my opinion more logical.
I am trying to join two tables one is a unique feature the seconds is readings taken on several dates that relate to the unique features. I want all of the records in the first table plus the most recent reading. I was able to get the results I was looking for before adding the shape field. By using the code
SELECT
Table1.Name, Table1.ID, Table1.Shape,
Max(Table2.DATE) as Date
FROM
Table1
LEFT OUTER JOIN
Table2 ON Table1.ID = table2.ID
GROUP BY
Table1.Name, Table1.ID, Table1.Shape
The shape field is a geometry type and I get the error
'The type "Geometry" is not comparable. It can not be use in the Group By Clause'
So I need to go about it a different way, but not sure how.
Below is a sample of the two tables and the desired results.
Table1
Name| ID |Shape
AA1 | 1 | X
BA2 | 2 | Y
CA1 | 3 | Z
CA2 | 4 | Q
Table2
ID | Date
1 | 5/27/2013
1 | 6/27/2014
2 | 5/27/2013
2 | 6/27/2014
3 | 5/27/2013
3 | 6/27/2014
My Desired Result is
Name| ID |Shape |Date
AA1 | 1 | X | 6/27/2014
BA2 | 2 | Y | 6/27/2014
CA1 | 3 | Z | 6/27/2014
CA2 | 4 | Q | Null
You can do the aggregation on Table2 in a CTE, finding the MAX(DATE) for each ID, and then join that result to Table1:
WITH AggregatedTable2(ID, MaxDate) AS
(
SELECT
ID, MAX(DATE)
FROM
Table2
GROUP BY
ID
)
SELECT
t1.ID, t1.Name, t1.Shape, t2.MaxDate
FROM
Table1 t1
LEFT JOIN
AggregatedTable2 t2 ON t1.ID = t2.ID
Try casting geometry as a varchar.
Select Table1.Name, Table1.ID, cast(Table1.Shape as varchar(1)) AS Shape, Max(Table2.DATE) as Date
FROM Table1 LEFT OUTER JOIN
Table2 ON Table1.ID = table2.ID
Group By Table1.Name, Table1.ID, cast(Table1.Shape as varchar(1))
Try this:
SELECT t1.Name
, t1.ID
, t1.Shape
, MAX(t2.Date) As Date
FROM Table1 AS t1
LEFT JOIN Table2 AS t2
ON t2.ID = t1.ID
GROUP
BY t1.Name
, t1.ID
, t1.Shape