I have a hierarchical data structure stored using materialized paths.
Table:Files
node parentNode name path
100 NULL f1 /f1/
101 100 f2 /f1/f2/
102 101 f3 /f1/f2/f3/
I have the node column as primary key(clustered)
Now if I want to find the ancestors of f3, given the path, I do something like this:
SELECT * FROM Files WHERE '/f1/f2/f3/' LIKE [path] + '%'
The problem with this is, the execution plan uses a clustered index scan( which I think SQL server defaults to for table scans)
Is there anyway I can find the ancestors of a node, given the path in a more efficient manner, preferably not using a CTE? I also have a depth column at my disposal if required.
If you have slow moving hierarchies, consider adding Range Keys. They facilitate navigation, filtration, and/or aggregration without the need of recursion.
The Range keys indicate ownership between X and Y. The range keys are especially helpful when dealing with large hierarchies (180K nodes).
The following is a simplified example, but may help.
Sample Hier Build
--Drop Table #MyHier
Declare #YourTable table (id int,ParentId int,Name varchar(50))
Insert into #YourTable values
(11, NULL,'A')
,(12, 11 ,'B')
,(13, 12 ,'F')
,(14, 13 ,'C')
,(15, 13 ,'D')
,(16, 11 ,'E')
,(17, 12 ,'G')
,(18, NULL ,'M')
,(19, 18 ,'N')
,(20, 18 ,'O')
,(21, 20 ,'P')
Declare #Top int = null --<< Sets top of Hier Try 3
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Name) as varchar(500))
,ID
,ParentId
,Lvl=1
,Name
,Path = cast('/'+Name+'/' as varchar(500))
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentId ,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Name)) as varchar(500))
,r.ID
,r.ParentId
,p.Lvl+1
,r.Name
,cast(p.path + '/'+r.Name+'/' as varchar(500))
From #YourTable r
Join cteP p on r.ParentId = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select A.R1
,B.R2
,A.ID
,A.ParentId
,A.Lvl
,Name = Replicate(#Nest,A.Lvl-1) + A.Name
,Path
Into #MyHier
From cteR1 A
Join cteR2 B on A.ID=B.ID
Select Full Hier
-- Get The Full Hier
Select *
From #MyHier
Order By R1
Returns
Get Ancestors
-- Get Ancestors of a Node
Declare #GetAncestors int = 15
Select A.*
From #MyHier A
Join (Select R1 From #MyHier Where ID=#GetAncestors) B
on B.R1 between A.R1 and A.R2
Order By A.R1
Returns
Select Descendants
-- Get Descendants of a Node
Declare #GetDesendants int = 12
Select A.*
From #MyHier A
Join (Select R1,R2 From #MyHier Where ID=#GetDesendants) B
on A.R1 between B.R1 and B.R2
Order By A.R1
Returns
Related
CREATE TABLE #A (UpperLimit NUMERIC(4))
CREATE TABLE #B (Id NUMERIC(4), Amount NUMERIC(4))
INSERT INTO #A VALUES
(1000), (2000), (3000)
INSERT INTO #B VALUES
(1, 3100),
(2, 1900),
(3, 1800),
(4, 1700),
(5, 900),
(6, 800)
Given these 2 tables, I want to join Table A to B ON B.Amount < A.UpperLimit but each record from Table B can only be used once, so the desired output would be:
I could easily do this by plopping Table B's records into a temp table, cursor over table A taking top record < UpperLimit and Deleting that record from the temp table or some other programmatic solution, but I'd like to avoid that and I'm pretty sure this could be done with a "normal" (recursive CTE? Partition?) query.
You could achieve your desired output using below recursive CTE
WITH
DATA AS
(
SELECT * FROM #A A1 INNER JOIN #B B1 ON A1.UpperLimit >= B1.Amount
),
MA AS
(
SELECT MIN(UpperLimit) AS MinLimit, MAX(UpperLimit) AS MaxLimit FROM #A
),
RESULT AS
(
-- Get the first record corresponding with maximum upper limit
SELECT *
FROM DATA D1
WHERE NOT EXISTS
(SELECT 1
FROM DATA D2
WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount)
AND D1.UpperLimit = (SELECT MaxLimit FROM MA)
-- Recursive get remain record corresponding with other upper limit
UNION ALL
SELECT D1.*
FROM RESULT R1 INNER JOIN DATA D1
ON (R1.UpperLimit > D1.UpperLimit AND R1.Id != D1.Id)
WHERE D1.UpperLimit >= (SELECT MinLimit FROM MA)
AND NOT EXISTS
(SELECT 1
FROM DATA D2
WHERE D2.UpperLimit = D1.UpperLimit AND D2.Amount > D1.Amount AND D2.Id != R1.Id)
)
SELECT DISTINCT * FROM RESULT ORDER BY UpperLimit DESC;
Demo: https://dbfiddle.uk/Y-m0K6Mk
Might be a bit lengthy but hopefully clear enough.
with a as
(select -- order and number rows in table A in some way
row_number() over (order by UpperLimit) as RnA,
*
from #a),
b as
(select -- order and number rows in table B in the same way
row_number() over (order by Amount) as RnB,
*
from #b),
m as
(select -- get and number all possible pairs of values from both tables considering the restriction
row_number() over (order by a.UpperLimit desc, b.Amount desc) as RnM,
*
from a
join b on
b.Amount < a.UpperLimit),
r as
(select -- use recursion to get all possible combinations of the value pairs with metrics of interest for comparison
convert(varchar(max), RnA) as ListA,
convert(varchar(max), RnB) as ListB,
RnA,
RnB,
1 as CountB,
convert(int, Amount) as SumB
from m
where RnM = 1
union all
select
r.ListA + ' ' + convert(varchar(max), m.RnA),
r.ListB + ' ' + convert(varchar(max), m.RnB),
m.RnA,
m.RnB,
r.CountB + 1,
r.SumB + convert(int, m.Amount)
from m
join r on
m.RnA < r.RnA and
m.RnB < r.RnB),
e as
(select top(1) -- select combinations of interest using metrics
ListA,
ListB
from r
order by CountB desc, SumB desc),
ea as
(select -- turn id list into table for table A
ea.Rn,
ea.Value
from e
cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListA, ' ')) as ea),
eb as
(select -- turn id list into table for table B
eb.Rn,
eb.Value
from e
cross apply(select row_number() over (order by (select null)) as Rn, Value from string_split(e.ListB, ' ')) as eb)
select -- get output table with actual values from the original tables
a.UpperLimit,
b.Amount,
b.Id
from ea
join eb on
ea.Rn = eb.Rn
join a on
ea.Value = a.RnA
join b on
eb.Value = b.RnB;
You can use an APPLY with a TOP 1 for this. Each row in the outer table gets only one row from the APPLY.
SELECT
*
FROM #A a
OUTER APPLY (
SELECT TOP (1) *
FROM #B b
WHERE b.Amount < a.UpperLimit
) b;
To simulate an inner-join (rather than a left-join) use CROSS APPLY.
This query returns very close to desired outcome.
WITH CTE AS (SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY B.Value ORDER BY B.Value DESC) AS RowNum
FROM #B B),
cc as (SELECT A.Limit, CTE.*
FROM #A A
LEFT JOIN CTE ON CTE.Value < A.Limit AND CTE.RowNum = 1),
cc2 as (select *, MAX(Value) OVER ( PARTITION BY cc.Limit) as l1 from cc)
select Limit, ID, Value
from cc2
where Value = l1
This query use 3 Common Table Expressions. First sort Table B with ROW_NUMBER() function and PARTITION BY clause, second one JOIN Table A with Table B with the condition given and the third one filters the record that is in Limit on Table A and use the Limit only once.
I have 2 tables:
contains full tree data
contains only specific childs
I need to get full hierarchy by child values. I can do it by one specific child node by following way:
;with tree as
(
select id, parent_id, name, level from f_all where id = #specefic_id
union all
select f.id, f.parent_id, f.name, f.level from f_all f
inner join tree t on f.id = t.parent_id and f.id <> f.parent_id
)
select *
from tree
OPTION (Maxrecursion 0)
I have an idea but I think it is not good. My idea is create function with above code. And call it by select my second table. I even didn't try it. Can you give me a right direction.
This is 2012+ ( Using concat() ... easily converted ).
Declare #f_all table (id int,parent_id int,name varchar(50))
Insert into #f_all values
(1,null,'1'),(2,1,'2'),(3,1,'3'),(4,2,'4'),(5,2,'5'),(6,3,'6'),(7,null,'7'),(8,7,'8')
Declare #Top int = null --<< Sets top of Hier Try 9
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
Declare #Filter varchar(25) = '4,6' --<< Empty for All or try 4,6
;with cteP as (
Select Seq = cast(1000+Row_Number() over (Order by name) as varchar(500))
,ID
,parent_id
,Lvl=1
,name
From #f_all
Where IsNull(#Top,-1) = case when #Top is null then isnull(parent_id,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.name)) as varchar(500))
,r.ID
,r.parent_id
,p.Lvl+1
,r.name
From #f_all r
Join cteP p on r.parent_id = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select Distinct
A.R1
,B.R2
,A.ID
,A.parent_id
,A.Lvl
,name = Replicate(#Nest,A.Lvl-1) + A.name
From cteR1 A
Join cteR2 B on A.ID=B.ID
Join (Select R1 From cteR1 where IIF(#Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',#Filter+','))>0) F on F.R1 between A.R1 and B.R2
Order By A.R1
Returns (#Top=null and #Filter='4,6')
Return Full Hier (#Top=null and #Filter='')
Returns Just a portion (#Top=2 and #Filter='')
The problem for me was that i didn't know how cte recursion works. Now i know how it works line by line: Recursive Queries Using Common Table Expressions.
Code below returns all hierarchy by children nodes:
;with tree as(
select id, parent_id, name, level from f_all fa
inner join #2nd_table_cildren_id c on c.id = fa.id
union all
select f.id, f.parent_id, f.name, f.level from f_all f
inner join tree t on f.id = t.parent_id and f.id <> f.parent_id
)
select distinct *
from tree
OPTION (Maxrecursion 0)
Let's take an example. These are the rows of the table I want get the data:
The column I'm talking about is the reference one. The user can set this value on the web form, but the system I'm developing must suggest the lowest reference value still not used.
As you can see, the smallest value of this column is 35. I could just take the smaller reference and sum 1, but, in that case, the value 36 is already used. So, the value I want is 37.
Is there a way to do this without a loop verification? This table will grow so much.
This is for 2012+
DECLARE #Tbl TABLE (id int, reference int)
INSERT INTO #Tbl
( id, reference )
VALUES
(1, 49),
(2, 125),
(3, 35),
(4, 1345),
(5, 36),
(6, 37)
SELECT
MIN(A.reference) + 1 Result
FROM
(
SELECT
*,
LEAD(reference) OVER (ORDER BY reference) Tmp
FROM
#Tbl
) A
WHERE
A.reference - A.Tmp != -1
Result: 37
Here is yet another place where the tally table is going to prove invaluable. In fact it is so useful I keep a view on my system that looks like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a cross join E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Next of course we need some sample data and table to hold it.
create table #Something
(
id int identity
, reference int
, description varchar(10)
)
insert #Something (reference, description)
values (49, 'data1')
, (125, 'data2')
, (35, 'data3')
, (1345, 'data4')
, (36, 'data5')
, (7784, 'data6')
Now comes the magic of the tally table.
select top 1 t.N
from cteTally t
left join #Something s on t.N = s.reference
where t.N >= (select MIN(reference) from #Something)
and s.id is null
order by t.N
This is ugly, but should get the job done:
select
top 1 reference+1
from
[table]
where
reference+1 not in (select reference from [table])
order by reference
I used a table valued express to get the next value. I first left outer joined the table to itself (shifting the key in the join by +1). I then looked only at rows that had no corresponding match (b.ID is null). The minimum a.ReferenceID + 1 gives us the answer we are looking for.
create table MyTable
(
ID int identity,
Reference int,
Description varchar(20)
)
insert into MyTable values (10,'Data')
insert into MyTable values (11,'Data')
insert into MyTable values (12,'Data')
insert into MyTable values (15,'Data')
-- Find gap
;with Gaps as
(
select a.Reference+1 as 'GapID'
from MyTable a
left join MyTable b on a.Reference = b.Reference-1
where b.ID is null
)
select min(GapID) as 'NewReference'
from Gaps
NewReference
------------
13
I hope the code was clearer than my description.
CREATE TABLE #T(ID INT , REFERENCE INT, [DESCRIPTION] VARCHAR(50))
INSERT INTO #T
SELECT 1,49 , 'data1' UNION ALL
SELECT 2,125 , 'data2' UNION ALL
SELECT 3,35 , 'data3' UNION ALL
SELECT 4,1345, 'data4' UNION ALL
SELECT 5,36 , 'data5' UNION ALL
SELECT 6,7784, 'data6'
SELECT TOP 1 REFERENCE + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
ORDER BY T1.REFERENCE
--- OR
SELECT MIN(REFERENCE) + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
How about using a Tally table. The following illustrates the concept. It would be better to use a persisted numbers table as opposed to the cte however the code below illustrates the concept.
For further reading as to why you should use a persisted table, check out the following link: sql-auxiliary-table-of-numbers
DECLARE #START int = 1, #END int = 1000
CREATE TABLE #TEST(UsedValues INT)
INSERT INTO #TEST(UsedValues) VALUES
(1),(3),(5),(7),(9),(11),(13),(15),(17)
;With NumberSequence( Number ) as
(
Select #start as Number
union all
Select Number + 1
from NumberSequence
where Number < #end
)
SELECT MIN(Number)
FROM NumberSequence n
LEFT JOIN #TEST t
ON n.Number = t.UsedValues
WHERE UsedValues IS NULL
OPTION ( MAXRECURSION 1000 )
You could try using a descending order:
SELECT DISTINCT reference
FROM `Resultsados`
ORDER BY `reference` ASC;
As far as I know, there is no way to do this without a loop. To prevent multiple values from returning be sure to use DISTINCT.
I have two sets of data, as shown below.
Table 1:
enter code here
ID Value_1
1 233.67
2 83.28
3 84.49
4 1234.83
Table 2:
NewID Value_3 Value_4
5 NULL 83
6 NULL 85
7 NULL 235
I want to join the two tables in such a way that the resulting data set would look like below.
ID NewID Value_1 Value_2
1 7 233.67 235
2 5 83.28 83
3 6 84.49 85
4 NULL 1234.83 NULL
I know that using the ROUND command would cause future problems. Do any of you know how I could create the above resulting set?
Something like this:
DECLARE #Table1 TABLE
(
Id int,
Value1 float
)
INSERT INTO #Table1
VALUES
(1, 233.67),
(2, 83.28),
(3, 84.49),
(4, 1234.83)
DECLARE #Table2 TABLE
(
NewId int,
Value2 float
)
INSERT INTO #Table2
VALUES
(5, 83),
(6, 85),
(7, 235)
SELECT DISTINCT
t1.Id,
FIRST_VALUE(t2.NewId) OVER (PARTITION BY t1.Id ORDER BY ABS(t1.Value1 - t2.Value2) ASC) AS NewId,
t1.Value1,
FIRST_VALUE(t2.Value2) OVER (PARTITION BY t1.Id ORDER BY ABS(t1.Value1 - t2.Value2) ASC) AS Value2
FROM #Table1 t1
CROSS JOIN #Table2 t2
ORDER BY t1.Id
But you will get a result for ID=4 too.
This approach avoids a cross join, which requires every combination to be tested. It also works with SQL Server 2005 and up. It works by calculating a lower and upper bound between the midpoints of each record in Table2 and then joining on Table1 where Value_1 is between the midpoints. If Value_1 lands right on the boundary this code will round up (choose the higher of Value_4 to match).
--Load Table1
select 1 ID, convert(float,233.67) Value_1 into #Table1
insert into #Table1 select 2, 83.28
insert into #Table1 select 3, 84.49
insert into #Table1 select 4, 1234.83
--Load Table2
select 5 NewID, null Value_3, convert(float,83) Value_4 into #Table2
insert into #Table2 select 6, NULL, 85
insert into #Table2 select 7, NULL, 235
;with cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from #Table2
)
Select #Table1.ID,
NewTable2.NewID,
#Table1.Value_1,
NewTable2.Value_4 Value_2
from #Table1
full join
(
select Table2.NewID,
Table2.Value_3,
Table2.Value_4,
Table2Prev.Value_4 + (Table2.Value_4 - Table2Prev.Value_4) / 2.0 LowerBound,
Table2.Value_4 + (Table2Next.Value_4 - Table2.Value_4) / 2.0 UpperBound
from cte_Table2 Table2
left join cte_Table2 Table2Prev
on Table2.OrderNum = Table2Prev.OrderNum + 1
left join cte_Table2 Table2Next
on Table2.OrderNum = Table2Next.OrderNum - 1
) NewTable2
on (#Table1.Value_1 < UpperBound or UpperBound is null)
and (#Table1.Value_1 >= LowerBound or LowerBound is null)
order by 1
I noticed that you did not show a match for ID of 4 in your expected output. If you need some kind of range to exclude matches then you would have to add that restriction to the ON conditions of the join. For example you could exclude values that are outside a threshold of 10 by making sure you use a FULL JOIN and add this to the ON conditions:
and abs(#Table1.Value_1-NewTable2.Value_4) < 10.0
It occurs to me though that maybe what you are really trying to do is a type of join where not only is the value in Table1 matched with it's closest value in Table2 but the value in Table2 is also the closest value in Table1. In this case, you would have to build a sub-query for Table1 with the boundary conditions and then check for it's closest match as well. Like this:
;with cte_Table1 as
(
select *, ROW_NUMBER() over (order by Value_1) OrderNum
from #Table1
),
cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from #Table2
)
Select NewTable1.ID,
NewTable2.NewID,
NewTable1.Value_1,
NewTable2.Value_4 Value_2
from
(
select Table1.ID,
Table1.Value_1,
Table1Prev.Value_1 + (Table1.Value_1 - Table1Prev.Value_1) / 2.0 LowerBound,
Table1.Value_1 + (Table1Next.Value_1 - Table1.Value_1) / 2.0 UpperBound
from cte_Table1 Table1
left join cte_Table1 Table1Prev
on Table1.OrderNum = Table1Prev.OrderNum + 1
left join cte_Table1 Table1Next
on Table1.OrderNum = Table1Next.OrderNum - 1
) NewTable1
full join
(
select Table2.NewID,
Table2.Value_3,
Table2.Value_4,
Table2Prev.Value_4 + (Table2.Value_4 - Table2Prev.Value_4) / 2.0 LowerBound,
Table2.Value_4 + (Table2Next.Value_4 - Table2.Value_4) / 2.0 UpperBound
from cte_Table2 Table2
left join cte_Table2 Table2Prev
on Table2.OrderNum = Table2Prev.OrderNum + 1
left join cte_Table2 Table2Next
on Table2.OrderNum = Table2Next.OrderNum - 1
) NewTable2
on (NewTable1.Value_1 < NewTable2.UpperBound or NewTable2.UpperBound is null)
and (NewTable1.Value_1 >= NewTable2.LowerBound or NewTable2.LowerBound is null)
and (NewTable2.Value_4 < NewTable1.UpperBound or NewTable1.UpperBound is null)
and (NewTable2.Value_4 >= NewTable1.LowerBound or NewTable1.LowerBound is null)
order by 1
Now the records in the tables will only match if the values are the closest match both ways. This will ensure that each record in Table1 will be matched to at most 1 value in Table2. Because it's a full join you can get null values on either side... depending on which table is smaller.
One more thing to mention... this code might not handle duplicate values the way you need. If you have more than one of the same value in either Value_1 or Value_4, it will find a match to one of them but not both. If you want that... then you would have to change your common table expressions to:
;with cte_Table1 as
(
select *, ROW_NUMBER() over (order by Value_1) OrderNum
from (select distinct Value_1 from #Table1) tbl1
),
cte_Table2 as
(
select *, ROW_NUMBER() over (order by Value_4) OrderNum
from (select distinct Value_4 from #Table2) tbl2
)
Then update your sub-queries to only output the values with the boundaries. This would give you best matches with between the unique values. Then you could join to Table1 and Table2 ON Table1.Value_1 = NewTable1.Value1 and Table2.Value_4 = NewTable2.Value_4 to get the rest of the fields.
Certainly some optimizations can be made if you have very large tables... like breaking out some of these sub-queries into indexed temporary tables.
I have tow tables with the same number of rows
Example:
table a:
1,A
2,B
3,C
table b:
AA,BB
AAA,BBB,
AAAA,BBBB
I want a new table made like that in SQL SErver:
1,A,AA,BB
2,B,AAA,BBB
3,C,AAAA,BBBB
How do I do that?
In SQL Server 2005 (or newer), you can use something like this:
-- test data setup
DECLARE #tablea TABLE (ID INT, Val CHAR(1))
INSERT INTO #tablea VALUES(1, 'A'), (2, 'B'), (3, 'C')
DECLARE #tableb TABLE (Val1 VARCHAR(10), Val2 VARCHAR(10))
INSERT INTO #tableb VALUES('AA', 'BB'),('AAA', 'BBB'), ('AAAA', 'BBBB')
-- define CTE for table A - sort by "ID" (I just assumed this - adapt if needed)
;WITH DataFromTableA AS
(
SELECT ID, Val, ROW_NUMBER() OVER(ORDER BY ID) AS RN
FROM #tablea
),
-- define CTE for table B - sort by "Val1" (I just assumed this - adapt if needed)
DataFromTableB AS
(
SELECT Val1, Val2, ROW_NUMBER() OVER(ORDER BY Val1) AS RN
FROM #tableb
)
-- create an INNER JOIN between the two CTE which just basically selected the data
-- from both tables and added a new column "RN" which gets a consecutive number for each row
SELECT
a.ID, a.Val, b.Val1, b.Val2
FROM
DataFromTableA a
INNER JOIN
DataFromTableB b ON a.RN = b.RN
This gives you the requested output:
You could do a rank over the primary keys, then join on that rank:
SELECT RANK() OVER (table1.primaryKey),
T1.*,
T2.*
FROM
SELECT T1.*, T2.*
FROM
(
SELECT RANK() OVER (table1.primaryKey) [rank], table1.* FROM table1
) AS T1
JOIN
(
SELECT RANK() OVER (table2.primaryKey) [rank], table2.* FROM table2
) AS T2 ON T1.[rank] = T2.[rank]
Your query is strange, but in Oracle you can do this:
select a.*, tb.*
from a
, ( select rownum rn, b.* from b ) tb -- temporary b - added rn column
where a.c1 = tb.rn -- assuming first column in a is called c1
if there is not column with numbers in a you can do same trick twice
select ta.*, tb.*
from ( select rownum rn, a.* from a ) ta
, ( select rownum rn, b.* from b ) tb
where ta.rn = tb.rn
Note: be aware that this can generate random combination, for example
1 A AA BB
2 C A B
3 B AAA BBB
because there is no order by in ta and tb