Subqueries oracle - sql

I would like to select house_id, house_name, image_name, and size for the lowest image size for each house.
create table house( id int, name varchar(50));
create table image( id int, name varchar(50), house_id int, siz int);
insert into house (id, name) values (1,'house1');
insert into house (id, name) values (2,'house2');
insert into image values (31,'img1',1, 10);
insert into image values (32,'img2',2, 20);
insert into image values (33,'img3',2, 15);
insert into image values (34,'img4',2, 19);
http://sqlfiddle.com/#!4/f86fe/35
Can anybody help me? I tried to prepare some query, but in the last column I always get the null values.
select h.id, h.name, m.mid, i.name
from house h left join (select min(siz) as mid,house_id from image
group by house_id) m on h.id = m.house_id
left join image i on i.id=m.house_id
Results:
| ID | NAME | MID | NAME |
|----|--------|-----|--------|
| 1 | house1 | 10 | (null) |
| 2 | house2 | 15 | (null) |

Two options - one using ROW_NUMBER() analytic function and the other using aggregation functions:
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table house( id int, name varchar(50));
create table image( id int, name varchar(50), house_id int, siz int);
insert into house (id, name) values (1,'house1');
insert into house (id, name) values (2,'house2');
insert into image values (31,'img1',1, 10);
insert into image values (32,'img2',2, 20);
insert into image values (33,'img3',2, 15);
insert into image values (34,'img4',2, 19);
Query 1:
SELECT h.id, h.name, i.siz, i.name AS image_name
FROM house h
LEFT OUTER JOIN (
SELECT i.*,
ROW_NUMBER() OVER (
PARTITION BY house_id
ORDER BY siz
) AS rn
FROM image i
) i
ON ( h.id = i.house_id AND i.rn = 1 )
Results:
| ID | NAME | SIZ | IMAGE_NAME |
|----|--------|-----|------------|
| 1 | house1 | 10 | img1 |
| 2 | house2 | 15 | img3 |
Query 2:
SELECT h.id,
MIN( h.name ) AS name,
MIN( i.siz ) AS siz,
MIN( i.name ) KEEP ( DENSE_RANK FIRST ORDER BY siz ) AS image_name
FROM house h
LEFT OUTER JOIN image i
ON ( h.id = i.house_id )
GROUP BY h.id
Results:
| ID | NAME | SIZ | IMAGE_NAME |
|----|--------|-----|------------|
| 1 | house1 | 10 | img1 |
| 2 | house2 | 15 | img3 |

In Oracle 12c, you can use OUTER APPLY, which is simpler (in my view) than ROW_NUMBER()
SELECT h.id, h.name, i.siz, i.name as image_name
FROM house h
OUTER APPLY ( SELECT i.*
FROM image i
WHERE i.house_id = h.id
ORDER BY i.siz ASC
FETCH FIRST 1 ROW ONLY ) i;
+----+--------+-----+------------+
| ID | NAME | SIZ | IMAGE_NAME |
+----+--------+-----+------------+
| 1 | house1 | 10 | img1 |
| 2 | house2 | 15 | img3 |
+----+--------+-----+------------+

Related

Insert on a child table and update FK on parent

I have a parent table with the following structure and data:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | NULL | Image1.jpg |
| 2 | NULL | Image7.jpg |
| 3 | NULL | Picture_Test.png |
---------------------------------------------
And the empty child table which holds the translated images:
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| | | | |
-------------------------------------------------------------------------
Now I'm looking for a single query statement or at least few queries which I can run without any further programming. Doing this job with scripting or programming would be easy but I have often situations where I need this kind of insert / update. And developing each time a small console app is not feasible.
At the end the two tables should look like this:
---------------------------------------------
| Id | TranslationId | Name |
---------------------------------------------
| 1 | 28 | NULL |
| 2 | 29 | NULL |
| 3 | 30 | NULL |
---------------------------------------------
-------------------------------------------------------------------------
| Id | De | Fr | En |
-------------------------------------------------------------------------
| 28 | Image1.jpg | NULL | NULL |
| 29 | Image7.jpg | NULL | NULL |
| 30 | Picture_Test.png | NULL | NULL |
-------------------------------------------------------------------------
Thank you for any advice.
You could do it something like the below :
INSERT INTO Child
(
Id
,De
,Fr
,En
)
OUTPUT Inserted.Id INTO #Temp
SELECT Id
,De
,Fr
,En
FROM #Values --If you are using a table type to insert into the Child table as a set based approach
;WITH CTE
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,Id
FROM #Temp
)
,CTE1 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY Id) AS Rnk
,*
FROM Parent
)
UPDATE cte1
SET TranslationId = cte.Id
FROM CTE1 cte1
JOIN CTE cte ON cte.Rnk = cte1.Rnk
Demo, assuming Name is unique in the first table
create table tab1 (
id int identity
,TranslationId int null
,Name nvarchar(max) null
);
insert tab1 (Name)
values
('Image1.jpg')
,('Image7.jpg')
,('Picture_Test.png')
,(null)
create table tab2 (
id int identity (100,1)
,De nvarchar(max) null
,Fr nvarchar(max) null
,En nvarchar(max) null
);
-- Update them
declare #map table(
name nvarchar(max)
,ref int
);
insert tab2 (de)
output inserted.De, inserted.id
into #map(Name, ref)
select Name
from tab1 src
where Name is not null and not exists (select 1 from tab2 t2 where t2.De = src.Name);
update t1 set TranslationId = ref, Name = null
from tab1 t1
join #map m on t1.Name = m.Name;
select * from tab1;
select * from tab2;
I figured out in the meantime how to do it. Applied on the database, the query looks like this:
DECLARE #Temp TABLE (ImageId INT, Id INT)
MERGE INTO Translation USING
(
SELECT Image.Name AS Name, Image.Id AS ImageId
FROM Candidate
INNER JOIN Candidacy ON Candidate.Id = Candidacy.CandidateId
INNER JOIN Election ON Candidacy.ElectionId = Election.Id
INNER JOIN SmartVoteCandidate ON Candidate.Id = SmartVoteCandidate.CandidateId
INNER JOIN Image ON SmartVoteCandidate.SpiderImageId = Image.Id
WHERE Election.Id = 1575) AS temp ON 1 = 0
WHEN NOT MATCHED THEN
INSERT (De)
VALUES (temp.Name)
OUTPUT temp.ImageId, INSERTED.Id
INTO #Temp (ImageId, Id);
UPDATE Image
SET Image.TranslationId = t.Id, Name = NULL
FROM #Temp t
WHERE Image.Id = t.ImageId
The solution is heavily inspired by
Is it possible to for SQL Output clause to return a column not being inserted?
Using a join in a merge statement

Recursive Iteration in Oracle

I have a table like that:
+----+-----+------+
| id | ord | test |
+----+-----+------+
| 1 | 1 | A |
| 1 | 2 | B |
| 1 | 3 | C |
| 2 | 1 | B |
| 2 | 2 | C |
+----+-----+------+
(Here is some code for creating the data)
drop table temp_test;
create table temp_test (id varchar(20), ord varchar(20), test varchar(20));
insert into temp_test (id,ord,test) values ('1','1','A');
insert into temp_test (id,ord,test) values ('1','2','B');
insert into temp_test (id,ord,test) values ('1','3','C');
insert into temp_test (id,ord,test) values ('2','1','B');
insert into temp_test (id,ord,test) values ('2','2','C');
commit;
How could I get the following result?
+----+-----+-------+
| id | ord | test |
+----+-----+-------+
| 1 | 1 | A |
| 1 | 2 | A_B |
| 1 | 3 | A_B_C |
| 2 | 1 | B |
| 2 | 2 | B_C |
+----+-----+-------+
I have tried using LAG(), something like:
select CONCAT(lag(TEST) over (partition by ID order by ord),TEST) AS TEST from temp_test;
but it does not work recursively.
This code works:
SELECT
R1.*,
( SELECT LISTAGG(test, ';') WITHIN GROUP (ORDER BY ord)
FROM temp_test R2
WHERE R1.ord >= R2.ord
AND R1.ID = R2.ID
GROUP BY ID
) AS WTR_KEYWORD_1
FROM temp_test R1
ORDER BY id, ord;
but it is not performant enough for a larger data set.
Some say the Hierarchical queries are outdated, but they generally perform far better than recursive CTE
SELECT id,
ord,
LTRIM(sys_connect_by_path(test,'_'),'_') as test
FROM temp_test r2 START WITH ord = 1 -- use MIN() to get this if it's not always 1
CONNECT BY PRIOR id = id AND ord = PRIOR ord + 1;
Demo
you can make use of recursive cte to achieve this
with cte(id,ord,test,concat_val)
as (select id,ord,test,test as concat_val
from temp_test
where ord=1
union all
select a.id,a.ord,a.test,b.concat_val||'_'||a.test
from temp_test a
join cte b
on a.id=b.id
and a.ord=b.ord+1
)
select * from cte order by id,ord
Demo here
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=78baa20f7f364e653899caf63ce7ada2

Get nth level on self-referencing table

I have a self-referencing table which has at max 5 levels
groupid | parentid | detail
--------- | --------- | ---------
Group A | Highest | nope
Group B | Group A | i need this
Highest | NULL | nope
Group C | Group B | nope
Group D | Group C | nope
I have a transaction table which lookups to the groupid on the table above to retrieve the detail value where groupid = Group B. The values of the groupid on the transaction table is only between Group B to D and will never go any higher.
txnid | groupid | desired | desired
--------- | --------- | --------- | ---------
1 | Group D | Group B | i need this
2 | Group B | Group B | i need this
3 | Group C | Group B | i need this
4 | Group B | Group B | i need this
How should my T-SQL script be like to attain the desired column? I can left join to the self referencing table multiple times to get until group B it's not consistent on how many time I need to join back.
Greatly appreciate any thoughts!
Still not clear to me how do you know which is the GROUP B, I suppose it's the record where the parent of it parent is null.
create table org(groupid char(1), parentid char(1), details varchar(20));
insert into org values
('a', null, 'nope'),('b', 'a', 'I need this'),('c', 'b', 'nope'),('d', 'c', 'nope'),('e', 'd', 'nope');
create table trans(id int, groupid char(1));
insert into trans values
(1, 'b'),(2, 'c'),(3, 'c'),(4, 'd'),(5, 'e');
GO
10 rows affected
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
) select * from all_levels;
GO
groupid_b | groupid_c | groupid_d | groupid_e | details
:-------- | :-------- | :-------- | :-------- | :----------
b | c | d | e | I need this
--= build a 4 levels row
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
)
--= no matter what groupid returns b group details
, only_b as
(
select groupid_b as groupid, groupid_b, details from all_levels
union all
select groupid_c as groupid, groupid_b, details from all_levels
union all
select groupid_d as groupid, groupid_b, details from all_levels
union all
select groupid_e as groupid, groupid_b, details from all_levels
)
--= join with transactions table
select id, t.groupid, groupid_b, ob.details
from trans t
inner join only_b ob
on ob.groupid = t.groupid;
GO
id | groupid | groupid_b | details
-: | :------ | :-------- | :----------
1 | b | b | I need this
2 | c | b | I need this
3 | c | b | I need this
4 | d | b | I need this
5 | e | b | I need this
dbfiddle here
You can deal with a recursive function too, but I don't believe it can be better on terms of performance.
create function findDetails(#groupid char(1))
returns varchar(100)
as
begin
declare #parentid char(1) = '1';
declare #next_parentid char(1) = '1';
declare #details varchar(100) = '';
while #next_parentid is not null
begin
select #details = org.details, #parentid = org.parentid, #next_parentid = op.parentid
from org
inner join org op
on op.groupid = org.parentid
where org.groupid = #groupid
set #groupid = #parentid;
end
return #details;
end
GO
✓
select id, groupid, dbo.findDetails(groupid) as details_b
from trans;
GO
id | groupid | details_b
-: | :------ | :----------
1 | b | I need this
2 | c | I need this
3 | c | I need this
4 | d | I need this
5 | e | I need this
dbfiddle here

How to merge two tables data in one using SQL Server 2008

I have two tables
Table #1: tbl_test1
id | product1 | price1|
---+----------+-------+
1 | A | 200 |
2 | B | 250 |
3 | C | 300 |
Table #2 : tbl_test2
id | product2 | price2|
---+----------+-------+
40 | P | 200 |
20 | Q | 250 |
and I want to result in my given format
id | product1 | price1|id | product2 | price2|
---+----------+-------+---+----------+-------+
1 | A | 200 |50 | P | 200 |
2 | B | 250 |40 | Q | 250 |
3 | C | 300 | | | |
Please help...
I don't know what's your end game but if you just want to display your data side by side you need to use FULL JOIN. Additionally, you have to add a ROW_NUMBER for each of your tables:
WITH CteTest1 AS(
SELECT *,
rn = ROW_NUMBER() OVER(ORDER BY id)
FROM #tbl_test1
),
CteTest2 AS(
SELECT *,
rn = ROW_NUMBER() OVER(ORDER BY id)
FROM #tbl_test2
)
SELECT
t1.id, t1.product1, t1.price1,
t2.id, t2.product2, t2.price2
FROM CteTest1 t1
FULL JOIN CteTest2 t2
ON t2.rn = t1.rn
ONLINE DEMO
declare #FirstTable table (StudentId int, SubjectId int)
declare #SecondTable table (MarksId int, RankId int, LastRank int)
insert into #FirstTable values (1, 1)
insert into #FirstTable values (1, 2)
insert into #FirstTable values (1, 3)
insert into #SecondTable values (1, 4, 10)
insert into #SecondTable values (1, 5, 11)
;with XmlTable (RowNumber, StudentId, SubjectId) as
(
select row_number() over(order by StudentId) as RowNumber, * from #FirstTable
)
,TechnicalIdsTable (RowNumber, MarksId, RankId, LastRank) as
(
select row_number() over(order by MarksId) as RowNumber, * from #SecondTable
)
select x.StudentId, x.SubjectId, t.MarksId, t.RankId, t.LastRank from XmlTable x
left join TechnicalIdsTable t
on x.RowNumber = t.RowNumber

Create New Table From Other Table After Grouping

How can I insert to a table a value from "grouping" other table?
That means I have 2 table with different structure.
The table ORDRE with existed DATA
Table ORDRE:
ORDRE ID | CODE_DEST |
-------------------------
1 | a |
2 | b |
3 | c |
4 | a |
5 | a |
6 | b |
7 | g |
I want to INSERT the value FROM Table ORDRE INTO TABLE VOIT:
ID_VOIT | ORDRE ID | CODE_DEST |
---------------------------------------
1 | 1 | a |
1 | 4 | a |
1 | 5 | a |
2 | 2 | b |
2 | 6 | b |
3 | 3 | c |
4 | 7 | g |
This is my best guess on what you need using only the info available.
declare #Ordre table
(
ordre_id int,
code_dest char(1)
)
declare #Voit table
(
id_voit int,
ordre_id int,
code_dest char(1)
)
insert into #Ordre values
(1,'a'),
(2,'b'),
(3,'c'),
(4,'a'),
(5,'a'),
(6,'b'),
(7,'g')
insert into #Voit
select id_voit, ordre_id, rsOrdre.code_dest
from #Ordre rsOrdre
inner join
(
select code_dest, ROW_NUMBER() over (order by code_dest) as id_voit
from #Ordre
group by code_dest
) rsVoit on rsVoit.code_dest = rsOrdre.code_dest
order by id_voit, ordre_id
select * from #Voit
Working Example.
For the specific data you give as an example, this works:
insert into VOIT
select
case code_dest
when 'a' then 1
when 'b' then 2
when 'c' then 3
when 'g' then 4
else 0
end, orderId, code_dest from ORDRE order by code_dest, orderId
But it kind of sucks because it requires hard-coding in a huge case statement.
Test is here - https://data.stackexchange.com/stackoverflow/q/119442/
What I like more is moving the VOIT ID / Code_Dest associations to a new table, so then you could do an inner join instead.
insert into VOIT
select voit_id, orderId, t.code_dest
from ORDRE t
join Voit_CodeDest t2 on t.code_dest = t2.code_dest
order by code_dest, orderId
Working example of that here - https://data.stackexchange.com/stackoverflow/q/119443/