Order by in subquery and alias - sql

I have a problem with a order by in oracle query.
select KEY, B, C, (select D from TABLE1 a where a.KEY = b.KEY and a.DATE<
b.DATE order BY a.DATE and rownum =1 )
FROMSTATUS from TABLE2 b
I known the "order by" is not working in subquery. I modify my query as:
select KEY, B, C, (select * from (select D from TABLE1 a where a.KEY =
b.KEY and a.DATE< b.DATE order by DATE) where rownum = 1)
FROMSTATUS from TABLE2 b
But in this way the B.KEY and B.DATE has not resolved by oracle
I need select only a 1 value from TABLE2 and the value is the first previous a.DATE
Example:
TABLE1
KEY DATE A B C
1 01/31/2000 1 2 3
2 02/25/2000 X Y Z
TABLE2
KEY DATE D
1 01/30/2000 1
1 01/27/2000 2
1 01/25/2000 2
2 02/20/2000 4
2 02/13/2000 1
I need this result:
TABLE1.KEY TABLE1.DATE TABLE1.A TABLE1.B TABLE1.C TABLE2.DATE TABLE2.D
1 01/31/2000 1 2 3 01/30/2000 1
2 02/25/2000 X Y Z 02/20/2000 4
Can you help me?
(i am sorry for my bad english)

row_number() after union will get your output.
select tFinal.DATE, tFinal.KEY
from (select row_number() over (partition by KEY order by t1.T, t1.DATE desc) as rn, t1.DATE, t1.KEY
from
(select DATE, KEY, 't1' as T from TABLE1
union all
select DATE, KEY, 't2' as T from TABLE2) t1) tFinal
Where rn = 2

You can use window functions for this:
WITH cte AS (
SELECT TABLE2.KEY, TABLE2.B, TABLE2.C, TABLE1.D
, ROW_NUMBER() OVER (PARTITION BY TABLE2.KEY, TABLE2.DATE ORDER BY TABLE1.DATE DESC) AS rn
FROM TABLE2
LEFT JOIN TABLE1 ON TABLE2.KEY = TABLE1.KEY AND TABLE2.DATE > TABLE1.DATE
)
SELECT *
FROM cte
WHERE rn = 1

Here's an answer that uses aggregation:
WITH t1 AS (SELECT 1 KEY, to_date('31/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('25/02/2000', 'dd/mm/yyyy') dt FROM dual),
t2 AS (SELECT 1 KEY, to_date('30/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 1 KEY, to_date('27/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 1 KEY, to_date('25/01/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('20/02/2000', 'dd/mm/yyyy') dt FROM dual UNION ALL
SELECT 2 KEY, to_date('13/02/2000', 'dd/mm/yyyy') dt FROM dual)
SELECT t1.KEY,
t1.dt t1_date,
MAX(t2.dt) t2_date
FROM t1
LEFT OUTER JOIN t2 ON t1.key = t2.key AND t2.dt < t1.dt
GROUP BY t1.key, t1.dt
ORDER BY t1.key;
KEY T1_DATE T2_DATE
---------- ----------- -----------
1 31/01/2000 30/01/2000
2 25/02/2000 20/02/2000
I'm assuming here that t1.key is a unique column. Whether this is more performant than any of the other answers for your data is up to you to test *{:-)

In Oracle you can use KEEP LAST for this:
select
key,
b,
c,
(
select max(d) keep (dense_rank last order by t2.date)
from table2 t2
where t2.key = t1.key and t2.date < t1.date
) as fromstatus
from table1 t1;
As of Oracle 12c you can also use FETCH FIRST ROW:
select
key,
b,
c,
(
select d
from table2 t2
where t2.key = t1.key and t2.date < t1.date
order by t2.date desc
fetch first row only
) as fromstatus
from table1 t1;
or, moving the subquery to the FROM clause:
select
t1.key,
t1.b,
t1.c,
first_t2.d as fromstatus
from table1 t1
outer apply
(
select d
from table2 t2
where t2.key = t1.key and t2.date < t1.date
order by t2.date desc
fetch first row only
) first_t2;
This last query has the advantage that you could easily select more values from the table2 row than just one.

Related

How multiple join to the same table with other table?

I am not sure of the question title!, but i have this problem
table1
id | from | to
1 A B
2 C A
3 B A
table2
id | table1_id
1 1
2 1
3 1
4 3
5 3
6 2
7 2
8 2
I need to get data from table1 considering rows with ids (2,3) as one row and joined with table2
fetch the last id between them which is 5
the result
id | from | to | table2_id
3 B | A | 5
2 C | A | 8
As you have not mentioned database, I have written SQL in SQL Server
WITH TAB_FLAT AS
(
SELECT TAB_1.table1_id , COALESCE ( TAB_2.table2_id , TAB_1.table2_id ) max_id , TAB_1.table2_id FROM
( select table1_id , max(id) table2_id from table2 group by table1_id ) TAB_1
LEFT JOIN
( select table1_id , max(id) table2_id from table2 group by table1_id ) TAB_2
ON TAB_1.table2_id = TAB_2.table1_id
)
select TAB_FLAT.table1_id AS ID, COALESCE(C.frm ,COALESCE(B.frm,A.frm) ) AS 'FROM' ,
COALESCE(C.to2 ,COALESCE(B.to2,A.to2) ) AS 'TO' ,
TAB_FLAT.max_id AS table2_id
FROM TAB_FLAT
LEFT JOIN table1 A ON A.id = TAB_FLAT.table1_id
LEFT JOIN table1 B ON B.id = TAB_FLAT.table2_id
LEFT JOIN table1 C ON C.id = TAB_FLAT.max_id
WHERE TAB_FLAT.table1_id IN (1,2)
Demo --> https://rextester.com/DCHUN74655
Explanation:
SELECT TAB_1.table1_id , COALESCE ( TAB_2.table2_id , TAB_1.table2_id ) max_id , TAB_1.table2_id FROM
( select table1_id , max(id) table2_id from table2 group by table1_id ) TAB_1
LEFT JOIN
( select table1_id , max(id) table2_id from table2 group by table1_id ) TAB_2
ON TAB_1.table2_id = TAB_2.table1_id
We are doing a self left join to get the largest table 2 ID for each table 1 ID
AND corresponding intermediate level.
select TAB_FLAT.table1_id AS ID, COALESCE(C.frm ,COALESCE(B.frm,A.frm) ) AS 'FROM' ,
COALESCE(C.to2 ,COALESCE(B.to2,A.to2) ) AS 'TO' ,
TAB_FLAT.max_id AS table2_id
FROM TAB_FLAT
LEFT JOIN table1 A ON A.id = TAB_FLAT.table1_id
LEFT JOIN table1 B ON B.id = TAB_FLAT.table2_id
LEFT JOIN table1 C ON C.id = TAB_FLAT.max_id
We are joining to table 1 based on table1 id , intermediate id and largest id
WHERE TAB_FLAT.table1_id IN (1,2)
Filtering out records with ID = 1 or 2
First, write a query to get each row's max ID in table 2.
select t1.*, max(t2.id) as table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
Then use that as a CTE for two queries. One to get all the rows except 1 and 3. And one to get 1 or 3, whichever has a greater table2_id. union them together.
with max_table2 as (
select t1.*, max(t2.id) as table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
)
select *
from max_table2
where id not in (1,3)
union
(
select *
from max_table2
where id in (1,3)
order by table2_id desc
limit 1
)
If the combined 1/3 row must have the ID of 1, despite 3 having the larger table2_id, this can be done by hard coding the ID in the select query.
with max_table2 as (
select t1.*, max(t2.id) as table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
)
select *
from max_table2
where id not in (1,3)
union
(
select 1 as id, "from", "to", table2_id
from max_table2
where id in (1,3)
order by table2_id desc
limit 1
)
Try it.
I suspect rather than hard coding rows 1 and 3, you're actually counting them as equivalent because they have the same path, just reversed. We can make this query more generic.
First, normalize the from/to so they're in the same order. While we're at it, also get their max table2 id.
select
t1.id,
case when "from" < "to" then "from" else "to" end as "from",
case when "from" < "to" then "to" else "from" end as "to",
max(t2.id) as max_table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
id from to max_table2_id
2 A C 8
3 A B 5
1 A B 3
Then rank the paths with the same from/to.
with normalized_max_table2 as (
select
t1.id,
case when "from" < "to" then "from" else "to" end as "from",
case when "from" < "to" then "to" else "from" end as "to",
max(t2.id) as table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
)
select *,
rank() over (partition by "from", "to" order by table2_id desc) as "rank"
from normalized_max_table2
id from to table2_id rank
3 A B 5 1
1 A B 3 2
2 A C 8 1
And, finally, select only the first ranks.
with normalized_max_table2 as (
select
t1.id,
case when "from" < "to" then "from" else "to" end as "from",
case when "from" < "to" then "to" else "from" end as "to",
max(t2.id) as table2_id
from table2 t2
join table1 t1 on t2.table1_id = t1.id
group by t1.id
),
ranked_max_table2 as (
select *,
rank() over (partition by "from", "to" order by table2_id desc) as "rank"
from normalized_max_table2
)
select id, "from", "to", table2_id
from ranked_max_table2
where "rank" = 1
id from to table2_id
3 A B 5
2 A C 8
That's a long-hand way of doing it. There may be a more compact way.
Try it.

Get previous record where date is not null - SQL SERVER

I have two tables as below :
table 1:
ID B C D E Date
1 b c D E 2018/10/10
1 c d A B 2018/10/14
Table 2 :
ID B C Date
1 b c 2018/10/10
1 x y 2018/10/11
1 y x 2018/10/12
1 p q 2018/10/13
1 c d 2018/10/14
Table A has 6 columns, where as table 2 has 4 columns.
Using left join the result is :
Select * from Table2 t2 left join table1 t1
on t2.id=t1.id and t2.Date = t1.Date
Left join result is :
ID B C D E Date1 ID B C Date2
1 b c D E 2018/10/10 1 b c 2018/10/10
- - - - - - 1 x y 2018/10/11
- - - - - - 1 y x 2018/10/12
- - - - - 1 p q 2018/10/13
1 c d A B 2018/10/14 1 c d 2018/10/14
Note :
'-' represnt NULL.
Dates are sorted in left join result -- order by table2.date ,
table1.date asc
I have taken result of join in cte. not able to
generate final result.
Expected Result :
ID B C D E Date
1 b c D E 2018/10/10
1 x y D E 2018/10/11
1 y x D E 2018/10/12
1 p q D E 2018/10/13
1 c d A B 2018/10/14
Where Date from table1 is null in left join result, the search for previous non null date of table 1 , which would be just less than current date of table 2.
And fetch values of column D and E from there and keep the values of column B and C from current record where Date1 is null.
Being a newbie in sql, I am stuck in this. Please help.
Thanks in advance.
use outer apply with top 1. It will give your result, is fast and very short:
-- create data from example:
-- ------------------------
select * into #Table1 from
(select 1 as ID, 'b' as B, 'c' as C, 'D' as D, 'E' as E, cast('2018-10-10' as date) as [Date]
union all select 1, 'c', 'd', 'A', 'B', '2018-10-14')t
select * into #Table2 from
(select 1 as ID, 'b' as B, 'c' as C , cast('2018-10-10' as date) as [Date]
union all select 1, 'x', 'y', '2018-10-11'
union all select 1, 'y', 'x', '2018-10-12'
union all select 1, 'p', 'q', '2018-10-13'
union all select 1, 'c', 'd', '2018-10-14')t
-- SOLUTION
-- --------
select
T2.ID,
T2.B,
T2.C,
T1.D,
T1.E,
T2.[Date]
from
#Table2 T2
outer apply
(
select top 1 * from #Table1 T1
where T1.ID=T2.ID and T1.[Date] <= T2.[Date]
order by T1.[Date] desc
) T1
-- clean everything
-- ----------------
drop table #Table1
drop table #Table2
If you are using SQL Server 2012 or later, the following query returns the value you expected. I have used CTE and first_value() function and the query is optimized.
with
cte
as
(
select
t2.ID ,
t2.B ,
t2.C ,
t1.D ,
t1.E ,
t2.[Date] ,
sum(case when t1.D is null then 0 else 1 end) over (order by t2.[Date]) as D_partition,
sum(case when t1.E is null then 0 else 1 end) over (order by t2.[Date]) as E_partition
from
Table2 t2
left join
table1 t1
on
t2.id = t1.id
and
t2.[Date] = t1.[Date]
)
select
cte.ID ,
cte.B ,
cte.C ,
first_value(D) over(partition by D_partition order by D desc) as D ,
first_value(E) over(partition by E_partition order by E desc) as E ,
cte.Date
from
cte;
It seems you are left joining table2 to table 1, and you want the values of table1 if they exist, else from table2. This "if" in the level of data is typically implemented with the CASE function. However, in your case, we can use a more specific function, ISNULL(a,b), which returns a when a has a value and b when a is null:
select
t2.ID,
isnull(t1.B,t2.B) as B,
isnull(t1.C,t2.C) as C,
isnull(t1.D,t2.D) as D,
isnull(t1.E,t2.E) as E,
isnull(t1.[Date],t2.[Date]) as [Date]
from Table2 t2
left join table1 t1
on t2.id=t1.id and t2.Date = t1.Date
However, are you sure that t2.Date = t1.Date is needed? Ususally, the column named ID is unique/primary key, so that would make the additional join condition on date redundunt. You should remove it if this is the case.
Could you check to see if below is something you are looking for?
Select t2.ID,t2.B,t2.C,t1.D,t1.E, t2.Date from Table2 t2 left join table1 t1
on t2.id=t1.id and (t2.Date >= t1.Date)
where not exists (select 1 from table1 t12 where t2.Date > t1.Date and t2.Date >= t12.Date and t12.Date > t1.Date)
Here we tried to open table 1 two times (t1 and t12) to make sure the date in table 2 must >= a date in table1 and < an other date.

Assign values from another table based on time range

Let's say I have T1 :
id date attribute
1 2017-04-20 t
1 2017-04-19 t
1 2017-04-18 t
2 2017-04-20 t
2 2017-04-19 f
And I also have T2 :
id date date_end attribute
1 2017-04-19 2017-04-25 f
What I want is to get a new table, which would contain data from T1 but in case there is matching ids in both table, replace the attribute value in T1 with the attribute value in T2 for the date range defined in T2.
The result would look like this:
id date attribute
1 2017-04-20 f
1 2017-04-19 f
1 2017-04-18 t
2 2017-04-20 t
2 2017-04-19 f
What I have come up with is the following:
SELECT t1.id, t1.date,
CASE WHEN max(T2.id) IS NULL THEN T1.attribute ELSE T2.attribute END
FROM T1
LEFT JOIN T2 using (id)
Can't figure out how to change the attributes for the particular range.
Any help is appreciated!
I think this is just LEFT JOIN with COALESCE():
SELECT t1.id, t1.date,
COALESCE(t2.attribute, t1.attribute) as attribute
FROM T1 LEFT JOIN
T2
ON t1.id = t2.id AND
t1.date >= t2.date and t1.date <= t2.date_end;
For BigQuery Standard SQL
#standardSQL
SELECT
a.id,
a.date,
CASE
WHEN a.date BETWEEN b.date AND b.date_end THEN b.attribute
ELSE a.attribute
END AS attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id
You can play/test it with sample data from your question
#standardSQL
WITH T1 AS (
SELECT 1 AS id, '2017-04-20' AS date, 't' AS attribute UNION ALL
SELECT 1, '2017-04-19', 't' UNION ALL
SELECT 1, '2017-04-18', 't' UNION ALL
SELECT 2, '2017-04-20', 't' UNION ALL
SELECT 2, '2017-04-19', 'f'
),
T2 AS (
SELECT 1 AS id, '2017-04-19' AS date, '2017-04-25' AS date_end, 'f' AS attribute
)
SELECT
a.id,
a.date,
CASE
WHEN a.date BETWEEN b.date AND b.date_end THEN b.attribute
ELSE a.attribute
END AS attribute
FROM T1 AS a
LEFT JOIN T2 AS b
ON a.id = b.id
-- ORDER BY 1,2 DESC

First row from each group based on condition

Id Date
1 5/11/2015
1 5/11/2015
1 5/12/2015
1 5/13/2015
2 5/11/2015
2 5/11/2015
2 5/12/2015
2 5/13/2015
3 5/14/2015
3 5/15/2015
3 5/16/2015
3 5/17/2015
4 5/13/2015
4 5/13/2015
4 5/14/2015
4 5/15/2015
ID Name
1 Roy
2 Jame
3 Jani
4 Romi
I am not able to get the first row matching with second table
I want to get only one row from each table group by ID where date is greater than today's date (i.e. 5/11/2015), like as shown below.
Id Name Date
1 Roy 5/12/2015
2 Jane 5/12/2015
3 Jani 5/14/2015
4 Romi 5/13/2015
Use cross apply or correlated subquery to do this
select * from Table2 t2
cross apply
(select top 1 [date] from table1 t1
where t2.id = t1.id
AND t1.[date] > convert(date,getdate())
ORDER BY [date] ASC) CS
SQLFIDDLE DEMO
One option would be to use row_number():
with cte as (
select t2.id, t2.name, t1.date,
row_number() over (partition by t2.id order by t1.date) rn
from table1 t1
join table2 t2 on t1.id = t2.id
where t1.date > getdate())
select id, name, date
from cte
where rn = 1
SQL Fiddle Demo
You can use a CTE + ROW_NUMBER:
WITH CTE AS
(
SELECT t1.ID,
t1.[Date],
t2.Name,
RN = ROW_NUMBER() OVER (PARTITION BY t1.ID ORDER BY t1.[Date] ASC)
FROM dbo.Table1 t1
INNER JOIN dbo.Table2 t2
ON t1.ID = t2.ID
WHERE t1.[Date] > GetDate()
)
SELECT ID, Name, Date
FROM CTE
WHERE RN = 1
Demo
The ORDER BY t1.[Date] ASC specifies which row you want to keep, in this case the row with the oldest date, use DESC if you want to keep the newest.
Use a derived table with GROUP BY to get each id's lowest date (after today). Then JOIN with t2:
select t1.id, t2.name, t1.date
from (select Id, min(Date)
from table1
where Date > getdate()
group by id) t1 (id, date) join table 2 t2 on t1.id = t2.id

sql - how to select multiple columns with only one distinct column from joining multiple tables

I am using SQL Server. I want to select multiple columns with only one distinct column.
For example,
TABLE 1:
ID NAME ...(other columns)
1 A
2 B
3 C
Table 2 (ID and number together is the unique key):
ID Number Year...(other columns)
1 111 2011
2 12345678 2011
2 22222222 2012
3 333 2013
Table 3:
Name Company ...(other columns)
A Amazon
B Google
C Amazon
Each table above has many columns (more than 2). How can get the result so that there are only 5 columns as result without other "useless" columns and the ID column is the distinct column.
More specifically, for example,
The normal sql statement I had is the following:
select distinct ID, NAME, NUMBER, COMPANY, Year
from table1
left join table2 on table1.ID = table2.ID
left join table3 on table1.name = table3.name
group by ID, NAME, NUMBER, COMPANY, year
order by ID desc, Year desc
This will output the following:
ID NAME NUMBER COMPANY YEAR
1 A 111 Amazon 2011
2 B 12345678 google 2011
2 B 22222222 google 2012
3 c 333 Amazon 2013
What I want to have is actually the following:
ID NAME NUMBER COMPANY YEAR
1 A 111 Amazon 2011
2 B 22222222 google 2012
3 c 333 Amazon 2013
I want to have the results without duplicated ID. If there are duplicate ID's, I want to show only the latest one. In above example, ID 2 has 2 rows in table2. I want to show the one with the latest date which is 2012.
How can I achieve this. Thanks in advance.
You can use not exists to only select the latest rows per id (where another row with the same id and a greater year does not exist).
select * from table1 t1
where not exists (
select 1 from table1 t2
where t2.id = t1.id
and t2.year > t1.year
)
using analytic functions (this should be faster than the query above)
select * from
(select *,
row_number() over(partition by id order by year desc) rn
from table1) t1 where rn = 1
edit: applied to your tables
select t2.id, t3.name, t2.number, t3.company, t2.year from
(
select * from
(select *,
row_number() over(partition by id order by year desc) rn
from table2
) t1 where rn = 1
) t2 join table1 t1 on t2.id = t1.id
join table3 t3 on t3.name = t1.name
WITH CTE AS
(
SELECT t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY, t2.Year,
Row_number() OVER(partition BY t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY ORDER BY t2.Year DESC) AS rn
FROM table1 t1
LEFT JOIN table2 t2 ON t1.ID = t2.ID
LEFT JOIN table3 t3 ON t1.name = t3.name
)
SELECT ID, NAME, NUMBER, COMPANY, Year
FROM CTE
WHERE rownum = 1
ORDER BY ID desc, Year desc
I used a subquery, note subqueries are inefficient.
select distinct t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY, t2.Year
from table1 t1
left join table2 t2 on t1.ID = t2.ID
inner join table3 t3 on t1.name = t3.name --inner join to select the latest record only
and t2.Year = (Select MAX(year) from table2 t22
where t22.ID = t2.Id group by ID)
group by t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY, t2.year
order by t1.ID, t2.Year desc
EDIT: using a more efficient CTE
WITH CTE as
(
Select Id, MAX(year) as [yr] from table2 t2 group by ID
)
select distinct t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY, t2.Year
from table1 t1
left join table2 t2 on t1.ID = t2.ID
left join table3 t3 on t1.name = t3.name
inner join CTE on cte.yr = t2.Year
and t2.Id = CTE.Id
group by t1.ID, t1.NAME, t2.NUMBER, t3.COMPANY, t2.year
order by t1.ID, t2.Year desc