Access SQL: Select from table if ID is in another - sql

Say I have the table T1:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 92 | True | 3 | A | X |
2 | 1 | True | 3 | B | Y |
2 | 13 | False| 2 | A | Y |
3 | 13 | True | 3 | B | W |
4 | 1 | True | 3 | B | Z |
And T2:
ID | PNo | MM | CP |
---|-----|------|----|
1 | 13 | True | 4 |
2 | 92 | True | 3 |
3 | 1 | True | 3 |
4 | 13 | False| 2 |
5 | 13 | True | 3 |
1 | 1 | False| 3 |
What I want to do is to do a INSERT INTO where I take values of T1 and T2 but only if the ID of T2 is in T1 and if T1 has the flag with the value A.
I have tried two things:
1) INNER JOIN: Something like
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T1.ID, T2.PNo, T2.MM, T2.CP, 'A', T1.Name
FROM T2 INNER JOIN T1 ON T1.ID = T2.ID
WHERE (T1.FLAG = 'A')
The problem here is that it literally takes every combination of all relevant rows from T1 and T2. What I want is, I only want to take those rows of T2 whose IDs are also in T1.
2) IN?
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T1.ID, T2.PNo, T2.MM, T2.CP, 'A', T1.Name
FROM T2, T1
WHERE T2.ID IN
(SELECT ID FROM T1 WHERE Flag = 'X')
Problem here is, this takes foreeeeeeeeeeever!
Is there not a more sophisticated method for this?
edit:// Changed a value in T2 so that the example is more meaningful.
So what I want in the new table T3 is:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
2 | 1 | True | 3 | A | Y |
What I get instead is:
ID | PNo | MM | CP | Flag | Name |
---|-----|------|----|------|------|
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
1 | 13 | True | 4 | A | X |
1 | 1 | False| 3 | A | X |
2 | 1 | True | 3 | A | Y |
So basically for all T1 values that I select (ID, Name) and for all corresponding rows that I can match by ID in T2, I get every combination.

Your second solution is almost right, however, you perform cartesian product unnecessarily (I'm not surprised that it takes forever). Try this one and if it is slow then create an index on Flag.
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT *
FROM T2
WHERE T2.ID IN
(SELECT ID FROM T1 WHERE Flag = 'X')

This should do what you want:
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T2.ID, T2.PNo, T2.MM, T2.CP, ?, "A"
FROM T2
WHERE T2.ID IN (SELECT ID FROM T1 WHERE Flag = "A");
You are selecting T1.ID in your first query. You can take either T1.ID or T2.ID, because you are requiring that they be equal.
You can do this with a JOIN:
INSERT INTO T3 (ID, PNo, MM, CP, Flag, Name)
SELECT T2.ID, T2.PNo, T2.MM, T2.CP, T1.Name, "A"
FROM T2 INNER JOIN
T1
ON T2.ID = T1.ID;
If this is generating duplicates, then you have multiple rows in T1 for a given name.

Related

Take the row after the specific row

I have the table, where I need to take the next row after the row which has course 'TA' and flag = 1. For this I created the column rnum (OVER DATE) which may help for finding it
| student | date | course | flag | rnum |
| ------- | ----- | ----------- | ---- | ---- |
| 1 | 17:00 | Math | null | 1 |
| 1 | 17:10 | Python | null | 2 |
| 1 | 17:15 | TA | 1 | 3 |
| 1 | 17:20 | English | null | 4 |
| 1 | 17:35 | Geography | null | 5 |
| 2 | 16:10 | English | null | 1 |
| 2 | 16:20 | TA | 1 | 2 |
| 2 | 16:30 | SQL | null | 3 |
| 2 | 16:40 | Python | null | 4 |
| 3 | 19:05 | English | null | 1 |
| 3 | 19:20 | Literachure | null | 2 |
| 3 | 19:30 | TA | null | 3 |
| 3 | 19:40 | Python | null | 4 |
| 3 | 19:50 | Python | null | 5 |
As a result I should have:
| student | date | course | flag | rnum |
| ------- | ----- | ------- | ---- | ---- |
| 1 | 17:20 | English | null | 4 |
| 2 | 16:30 | SQL | null | 3 |
There are many ways to get your desired result, let's see some of them.
1) EXISTS
You can use the EXISTS clause, specifying a subquery to match for the condition.
SELECT T2.*
FROM #MyTable T2
WHERE EXISTS (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
)
2) LAG
You ca use window function LAG to access previous row for a given order and then filter your resultset with your conditions.
SELECT w.student, w.date, w.course, w.flag, w.rnum
FROM (
SELECT T1.*
, LAG(course, 1) OVER (PARTITION BY student ORDER BY rnum) prevCourse
, LAG(flag, 1) OVER (PARTITION BY student ORDER BY rnum) prevFlag
FROM #MyTable T1
) w
WHERE prevCourse = 'TA' AND prevFlag = 1
3) JOIN
You can self-JOIN your table on the next rnum and keep only the rows who match the right condition.
SELECT T2.*
FROM MyTable T1
JOIN MyTable T2 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
WHERE T1.course = 'TA' AND T1.flag = 1
4) CROSS APPLY
You can use CROSS APPLY to specify a subquery with the matching condition. It is pretty similar to EXISTS clause, but you will also get in your resultset the columns from the subquery.
SELECT T2.*
FROM #MyTable T2
CROSS APPLY (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
) x
5) CTE
You can use common table expression (CTE) to extract matching rows and then use it to filter your table with a JOIN.
;WITH
T1 AS (
SELECT student, rnum
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
)
SELECT T2.*
FROM #MyTable T2
JOIN T1 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
Adding the rownumber was a good start, you can use it to join the table with itself:
WITH matches AS (
SELECT
student,
rnum
FROM table
WHERE flag = 1
AND course = 'TA'
)
SELECT t.*
FROM table t
JOIN matches m
on t.student = m.student
and t.rnum = m.rnum + 1

SQL query join- unrelated tables

Can someone help me to join the two tables without any primary or secondary keys. Sample table is
TABLE 1
| ID | NAME |
| 1 | x |
| 2 | Y |
| 3 | z |
TABLE 2
| Num | NAME | DATE |
| 52 | X | 12-aug-17 |
| 53 | X | 11-apr-17 |
| 62 | X | 10-aug-11 |
| 12 | y | 2-jan-16 |
| 23 | Y | 3-apr-18 |
I want retrieve data from X
select *
from table2
where name = 'x';
| Num | NAME | DATE |
| 52 | X | 12-aug-17 |
| 53 | X | 11-apr-17 |
| 62 | X | 10-aug-11 |
Now I will get three data from table2. I'm little stuck after this step. I want to get top of data the from table 2 and combine with table one.
I want final output should be
| ID | NAME | Num | DATE |
| 1 | x | 52 | 12-aug-17 |
Can someone suggest me how can I join this table? Its easy to join when we have any primary key but here not the case
Thanks
You can use this:
SELECT TOP(1) table1.ID, table2.Num, table2.Name, table2.DATE
FROM table2 INNER JOIN table1 ON table1.NAME = table2.NAME
WHERE table2.NAME = 'x'
ORDER BY table2.DATE ASC
OR
SELECT table1.ID, table2.Num, table2.Name, table2.DATE
FROM table1 INNER JOIN
(SELECT TOP(1) * FROM table2 WHERE NAME = 'x' ORDER BY DATE ASC) table2
ON table1.NAME = table2.NAME
You need to get the maximum DATE using a subquery, as in:
select t1.id, t2.*
from table1 t1
join table2 t2 on t2.name = t1.name
where t2.date = (
select max(date) from table2 where name = 'x'
);

How to group results of a postgres table if any of the fields match?

I have a postgresql table of records Where every every record has a record in that table that matches it on at least one of three possible fields.
id | name | email | phone | product
----------------------------------------------------
1 | Rob A | foo#bar.com | 123 | 102
2 | Rob B | foo#bar.com | 323 | 102
3 | Rob C | foo#bcr.com | 123 | 102
4 | Rob A | foo#bdr.com | 523 | 102
5 | Rob A | foo#bar.com | 123 | 104
6 | Cat A | liz#bar.com | 999 | 102
7 | Cat B | lid#bar.com | 999 | 102
8 | Cat A | lib#bar.com | 991 | 102
I want to group tables any tables where the "product" matches and any of these other three fields, (name, email, phone). So the groups would end up looking like
id | name | email | phone | product
----------------------------------------------------
1 | Rob A | foo#bar.com | 123 | 102
2 | Rob B | foo#bar.com | 323 | 102
3 | Rob C | foo#bcr.com | 123 | 102
4 | Rob A | foo#bdr.com | 523 | 102
5 | Rob A | foo#bar.com | 123 | 104
6 | Cat A | liz#bar.com | 999 | 102
7 | Cat B | lid#bar.com | 999 | 102
8 | Cat A | lib#bar.com | 991 | 102
Is there any way to do this?
If we INNER JOIN the table with itself like
SELECT t1.id id1,
t2.id id2
FROM elbat t1
INNER JOIN elbat t2
ON t2.product = t1.product
AND (t2.name = t1.name
OR t2.email = t1.email
OR t2.phone = t1.phone)
AND t2.id > t1.id;
we'll have the lowest ID of a "group" with more than one row in id1. For each id1 the other members of the "group" are in id2.
That is, we can join the result from the query above, so that for each row the lowest ID of the "group" is joined. As rows, which build a "group" on their own or rows, which already have the lowest ID of a "group" won't find a partner row, we have to LEFT JOIN. We can now use the joined lowest ID, or the ID of a row itself, if there wasn't joined any partner row, as the "group" ID using coalesce().
SELECT coalesce(x.id1, t.id) groupid,
t.*
FROM elbat t
LEFT JOIN (SELECT t1.id id1,
t2.id id2
FROM elbat t1
INNER JOIN elbat t2
ON t2.product = t1.product
AND (t2.name = t1.name
OR t2.email = t1.email
OR t2.phone = t1.phone)
AND t2.id > t1.id) x
ON x.id2 = t.id
ORDER BY coalesce(x.id1, t.id);
As we also ordered by the "group" ID, we can sequentially traverse the result in any application and know, if the "group" ID changes, we're reading the first row of a new "group".
db<>fiddle

How to mass-check for duplicates in MS-Access and log changes?

I want to mass-compare a few hundred fields in MS Access between 2 tables with identical column structures. If there are any differences between the column values, replaces the row in table1 with the new row in table2. If table2 does no longer holds a row that exists in table1, that row should be dropped from table1. All changes to table1 should be logged in tableLOGS.
Take for example:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 0 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 0 | 0 |
After running the desired SQL query, the result should be:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 1 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 2 | 0 | 1 | edit |
| 4 | 3 | 0 | 0 | delete |
I expect this would have to be broken down into 2 separate queries?
Mass-compare rows and update changes Log the changes to tableLOGS This seems like a fairly common task so perhaps MS Access has an easy way of accomplishing this? Thanks for all the help! :)
P.S. I am also open to simply deleting rows from table1 that do not match table2, and INSERT INTO table1 from table2.
This is from memory I didn't actually run this so it may need some fixing. My apologies
Start with the logging for updates
INSERT INTO TableLogs
SELECT A, B
FROM (
SELECT A, B
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL)
Then update table1 with the updated values
UPDATE table1
INNER JOIN(
SELECT *
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL) t2
ON table1.pid = t2.pid
Log missing records in table 1
INSERT INTO tablelogs
SELECT A, B
FROM table2
INNER JOIN table1 t1 ON t1.pid = t2.pid AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL
Delete the missing rows from table1
DELETE table1
WHERE pid NOT IN
(SELECT pid FROM Table2)
You could also put a trigger on table1 to update table logs but its not really best practice.
Hope that helps, like I said I didn't run it yet.

How can I write a select statement for this use case?

Please help me compose a SELECT statement. I have these two tables:
Table1 Table2
---------------- ------------------------------------------------
ID | PName | | ID | NameID | DateActive | HoursActive |
---------------- ------------------------------------------------
1 | Neil | | 1 | 1 | 8/2/2013 | 3 |
2 | Mark | | 2 | 1 | 8/3/2013 | 4 |
3 | Onin | | 3 | 2 | 8/2/2013 | 2 |
---------------- | 4 | 2 | 8/6/2013 | 5 |
| 5 | 3 | 8/7/2013 | 1 |
| 6 | 3 | 8/8/2013 | 10 |
------------------------------------------------
And I just want to retrieve the earliest DateActive but no duplicate PName. Like this:
PName | DateActive | HoursActive |
----------------------------------------
Neil | 8/2/2013 | 3 |
Mark | 8/2/2013 | 2 |
Onin | 8/7/2013 | 1 |
----------------------------------------
Something like this might do it. You need to find the min date for each NameID first, then join back to the table to get the hours.
SELECT
PName, MaxDate as DataActive, HoursActive
From
Table1 t1
inner Join Table2 t2 on t1.ID = t2.NameID
Inner Join (Select min(DateActive) as mindate, NameID from Table2 Group by NameID) as t3 on t3.mindate = t2.ActiveDate and t3.NameID = t2.NameId
This should be a pretty standard solution:
select t.pname,
t2.dateactive,
t2.hoursac
from table1 t
join table2 t2 on t.id = t2.nameid
join (
select nameid, min(dateactive) mindateactive
from table2
group by nameid
) t3 on t2.nameid = t3.name
and t3.mindateactive = t2.dateactive
If you are using an RDBMS that supports partition by statements, then this would be more efficient:
select pname, dateactive, HoursActive
from (
select t.pname,
t2.dateactive,
t2.hoursactive,
rank() over (partition by t.id order by t2.dateactive) rownum
from table1 t
join table2 t2 on t.id = t2.nameid
) t
where rownum = 1