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

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

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

Update the column of table1 and get it on table2

How can I create a query where I can update the table1 column date that I get it on table2?
Here is some example of the tables
Table1:
| stud_id | start_date | birt_date | name | exam_date |
| s001 | 11/19/2018 | 05/20/1999 | john | 10/20/2018 |
| s003 | 01/01/2018 | 05/25/1995 | mike | 10/20/2018 |
| s005 | 12/23/2018 | 02/20/1999 | ed | 10/20/2018 |
| s005 | 12/23/2018 | 02/20/1999 | ed | 10/05/2017 |
Table2:
| stud_id | start_date | exam_date |
| s005 | 01/01/2017 | 10/20/2018 |
| s001 | 01/01/2017 | 10/20/2018 |
| s003 | 01/01/2017 | 10/20/2018 |
Basically I want to change just the start_date of the 3 so s006 will not change.
How can I accomplish that using query? I was thinking using in then select the table but I think its not gonna work.
I need to based on two or more column for the condition of my update so I want to update the table 1 where table1.stud_id = table2.stud_id and table1.exam_date = table2.exam_date
do join and update
update t1
set t1.stardate=t2.startdate,
t1.exam_date=t2.exam_date
from table1 t1 join table2 t2
on t1.stud_id=t2.stud_id
where t2.stud_id='s003' -- if you just need s003 update
You can also use CTE as well to update:
;With cte as
(
select t1.start_date as t1date,t2.start_date as t2date from table1 t1
join table2 t2
on t1.stud_id=t2.stud_id
and t1.exam_date=t2.exam_date
)
update cte set t1date=t2date
You can join the 2 tables:
UPDATE T1
SET Start_Date = T2.Start_Date
FROM Table1 AS T1
INNER JOIN Table2 AS T2
ON T1.stud_id = T2.stud_id
AND T1.exam_date = T2.exam_date

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'
);

Access SQL: Select from table if ID is in another

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.

How to do a proper view?

I have two tables here:
Table1
Version | Position | PIN
1 | 1 | 1111
1 | 2 | 2222
2 | 1 | 3333
2 | 2 | 4444
I have another table
Table2
Name | Version | Position1 | Position2
'Phone1' | 1 | A | B
'Phone2' | 2 | C | D
Actually Table2.Position1 = (Table1.Position=1) and Table2.Position2 = (Table1.Position=2). Also Table1.Version = Table2.Version.
I would like to make the view that looks like this
Table3
Name | P_Name | PIN
'Phone1' | A | 1111
'Phone1' | B | 2222
'Phone2' | C | 3333
'Phone2' | D | 4444
Edited:
WITH CTE(Name, Version, Pos, Position)
AS (
SELECT Name, Version, Position1, 1 FROM Table2
UNION ALL
SELECT Name, Version, Position2, 2 FROM Table2
)
SELECT T2.Name, T2.Pos, T1.PIN
FROM Table1 t1
JOIN CTE t2
ON t1.Version = t2.Version
AND t1.Position = t2.Position;
Output:
| NAME | POS | PIN |
|--------|-----|------|
| Phone1 | A | 1111 |
| Phone1 | B | 2222 |
| Phone2 | C | 3333 |
| Phone2 | D | 4444 |
See this SQLFiddle
Earlier
You can do this using JOIN like this:
SELECT t2."Name", t1."Position", t1."PIN"
FROM Table1 AS t1
JOIN Table2 AS t2
ON t1."Version" = t2."Version"
AND ( (t1."Position" = 1 AND t2."Position1" = t1."PIN")
OR (t1."Position" = 2 AND t2."Position2" = t1."PIN")
)
Output:
| NAME | POSITION | PIN |
|--------|----------|------|
| Phone1 | 1 | 1111 |
| Phone1 | 2 | 2222 |
| Phone2 | 1 | 3333 |
| Phone2 | 2 | 4444 |
See this SQLFIddle
I think that most elegant and scalable way is to do this:
with cte(Name, Version, P_Name, Position) as (
select Name, Version, Position1, 1 from Table2
union all
select Name, Version, Position2, 2 from Table2
)
select
T2.Name, T2.P_Name, T1.PIN
from Table1 as T1
left outer join cte as T2 on
T2.Position = T1.Position and T2.Version = T1.Version
order by Name, P_Name
=> sql fiddle demo
You can easily add position = 3, 4 and then just add lines into cte