sql select only first 3 rows from second table - sql

I only need 3 records from child table.
CREATE TABLE project
(prj_code int , prj_name varchar(255) );
CREATE TABLE task
(id int , prj_code int, task_name varchar(255) );
INSERT INTO project VALUES (1,'myproj1');
INSERT INTO project VALUES (2,'myproj2');
INSERT INTO task VALUES (1,1,'testing');
INSERT INTO task VALUES (2,1,'prod');
INSERT INTO task VALUES (3,1,'prod2');
INSERT INTO task VALUES (4,1,'testing2');
INSERT INTO task VALUES (5,2,'mytask1');
INSERT INTO task VALUES (6,2,'mytask2');
Please see the fiddle
EDIT:
Want to pivot table to retrieve the 3 smallests tasks Ids in columns for each project

This code does the trick. You need to use RANK to select the 3 smallests IDs and then pivot table:
WITH tasks AS (
select
A.prj_code,
prj_name,
RANK() OVER (PARTITION BY A.PRJ_CODE ORDER BY ID ) RANK,
ID,
TASK_NAME
from task A
inner join project B
on A.PRJ_CODE = B.PRJ_CODE
order by A.prj_code)
select PRJ_CODE, prj_name, sum(T1) T1, sum(T2) T2, sum(T3) T3
from tasks A
PIVOT (max(ID) for RANK IN ('1' as T1 ,'2' as T2 ,'3' as T3 )) B
GROUP BY PRJ_CODE, prj_name
OR
use the code bellow to have the tasks names
select PRJ_CODE, prj_name, max(T1) T1, max(T2) T2, max(T3) T3
from tasks A
PIVOT (max(TASK_NAME) for RANK IN ('1' as T1 ,'2' as T2 ,'3' as T3 )) B
GROUP BY PRJ_CODE, prj_name
SQLFiddle

This is a hideous solution, but I think it illustrates a way to accomplish this. I don't assume any pivoting capabilies, not knowing what version of Oracle you're running.
with tasks as (
select
a.prj_code, a.prj_name,
b.id, b.task_name,
row_number() over (partition by a.prj_code order by b.id) as rn
from
project a
join task b on
a.prj_code = b.prj_code
), pivotdata as (
select
prj_code, prj_name,
case when rn = 1 then task_name end as task_1,
case when rn = 2 then task_name end as task_2,
case when rn = 3 then task_name end as task_3
from tasks
)
select
prj_code, prj_name,
max (task_1) as task_1, max (task_2) as task_2,
max (task_3) as task_3
from pivotdata
group by
prj_code, prj_name
Output:
Prj Code Prj Name Task 1 Task 2 Task 3
1 myproj1 testing prod prod2
2 myproj2 mytask1 mytask2

Related

Insert/join table on multiple conditions

I’ve a table that looks like this:
Table A
Version,id
5060586,22285
5074515,22701
5074515,22285
7242751,22701
7242751,22285
I want to generate a new key called groupId that is inserted as my example below:
Table A
Version,id,groupId
5060586,22285,1
5074515,22701,2
5074515,22285,2
7242751,22701,2
7242751,22285,2
I want the groupId to be the same as long as the id's are the same in the different versions. So for example version 5074515 and 7242751 has the same id's so therefor the groupId will be the same. If all the id's aren't the same a new groupId should be added as it has in version 5060586.
How can i solve this specific problem in SQL oracle?
One approach is to create a unique value representing the set of ids in each version, then assign a groupid to the unique values of that, then join back to the original data.
INSERT ALL
INTO t (version,id) VALUES (5060586,22285)
INTO t (version,id) VALUES (5074515,22701)
INTO t (version,id) VALUES (5074515,22285)
INTO t (version,id) VALUES (7242751,22701)
INTO t (version,id) VALUES (7242751,22285)
SELECT 1 FROM dual;
WITH groups
AS
(
SELECT version
, LISTAGG(id,',') WITHIN GROUP (ORDER BY id) AS group_text
FROM t
GROUP BY version
),
groupids
AS
(
SELECT group_text, ROW_NUMBER() OVER (ORDER BY group_text) AS groupid
FROM groups
GROUP BY group_text
)
SELECT t.*, groupids.groupid
FROM t
INNER JOIN groups ON t.version = groups.version
INNER JOIN groupids ON groups.group_text = groupids.group_text;
dbfiddle.uk
You can use:
UPDATE tableA t
SET group_id = ( SELECT COUNT(DISTINCT id)
FROM TableA x
WHERE x.Version <= t.version );
Which, for the sample data:
CREATE TABLE TableA (
Version NUMBER,
id NUMBER,
group_id NUMBER
);
INSERT INTO TableA (Version, id)
SELECT 5060586,22285 FROM DUAL UNION ALL
SELECT 5074515,22701 FROM DUAL UNION ALL
SELECT 5074515,22285 FROM DUAL UNION ALL
SELECT 7242751,22701 FROM DUAL UNION ALL
SELECT 7242751,22285 FROM DUAL;
Then, after the update:
SELECT * FROM tablea;
Outputs:
VERSION
ID
GROUP_ID
5060586
22285
1
5074515
22701
2
5074515
22285
2
7242751
22701
2
7242751
22285
2
db<>fiddle here

How to fetch records from two tables without common column using CTE

Users table details
userid values (abc,xyz,abc,sdf)
master table details
(mid,priority)values(101,1),(102,2),(101,1),(103,1)
i need to count of mid based on userid (userid is names of users) group by priority(priority is int ) grouping like case priority =1 then 'Open', priority =2 then 'closed' etc using CTE(common table expression)
Select * from users
userid
abc
xyz
abc
sdf
Select * from master
mid Priority
101 1
102 2
101 1
103 1
(Priority 1= Open 2=Closed)
OUTPUT expected:
Userid count(mid) Priority
abc 2 Open
xyz 1 Closed
sdf 1 Open
Try this:
use db_test;
go
drop table dbo.users;
create table dbo.users
(
userid varchar(max) not null
)
;
insert into dbo.users
values
('abc'),
('xyz'),
('sdf')
create table dbo.master
(
mid int not null,
Priority int not null
)
;
insert into dbo.master
values
(101, 1),
(102, 2),
(101, 1),
(103, 1)
;
with cte1 as (
select userid, row_number() over(order by userid asc) as rn
from dbo.users
), cte2 as (
select mid, priority, dense_rank() over(order by mid asc) as rn
from dbo.master
)
select a.userid, count(*) as [count(mid)], b.priority
from cte1 a join cte2 b on a.rn = b.rn
group by a.userid, b.priority

SQL group by if values are close

Class| Value
-------------
A | 1
A | 2
A | 3
A | 10
B | 1
I am not sure whether it is practical to achieve this using SQL.
If the difference of values are less than 5 (or x), then group the rows (of course with the same Class)
Expected result
Class| ValueMin | ValueMax
---------------------------
A | 1 | 3
A | 10 | 10
B | 1 | 1
For fixed intervals, we can easily use "GROUP BY". But now the grouping is based on nearby row's value. So if the values are consecutive or very close, they will be "chained together".
Thank you very much
Assuming MSSQL
You are trying to group things by gaps between values. The easiest way to do this is to use the lag() function to find the gaps:
select class, min(value) as minvalue, max(value) as maxvalue
from (select class, value,
sum(IsNewGroup) over (partition by class order by value) as GroupId
from (select class, value,
(case when lag(value) over (partition by class order by value) > value - 5
then 0 else 1
end) as IsNewGroup
from t
) t
) t
group by class, groupid;
Note that this assumes SQL Server 2012 for the use of lag() and cumulative sum.
Update:
*This answer is incorrect*
Assuming the table you gave is called sd_test, the following query will give you the output you are expecting
In short, we need a way to find what was the value on the previous row. This is determined using a join on row ids. Then create a group to see if the difference is less than 5. and then it is just regular 'Group By'.
If your version of SQL Server supports windowing functions with partitioning the code would be much more readable.
SELECT
A.CLASS
,MIN(A.VALUE) AS MIN_VALUE
,MAX(A.VALUE) AS MAX_VALUE
FROM
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS A
LEFT JOIN
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS B
ON A.CLASS = B.CLASS AND A.ROW_ID=B.ROW_ID+1
GROUP BY A.CLASS,CASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END
ORDER BY A.CLASS,cASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END DESC
ps: I think the above is ANSI compliant. So should run in most SQL variants. Someone can correct me if it is not.
These give the correct result, using the fact that you must have the same number of group starts as ends and that they will both be in ascending order.
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (class char(1),Value int);
insert into #temp values ('A',1);
insert into #temp values ('A',2);
insert into #temp values ('A',3);
insert into #temp values ('A',10);
insert into #temp values ('A',13);
insert into #temp values ('A',14);
insert into #temp values ('b',7);
insert into #temp values ('b',8);
insert into #temp values ('b',9);
insert into #temp values ('b',12);
insert into #temp values ('b',22);
insert into #temp values ('b',26);
insert into #temp values ('b',67);
Method 1 Using CTE and row offsets
with cte as
(select distinct class,value,ROW_NUMBER() over ( partition by class order by value ) as R from #temp),
cte2 as
(
select
c1.class
,c1.value
,c2.R as PreviousRec
,c3.r as NextRec
from
cte c1
left join cte c2 on (c1.class = c2.class and c1.R= c2.R+1 and c1.Value < c2.value + 5)
left join cte c3 on (c1.class = c3.class and c1.R= c3.R-1 and c1.Value > c3.value - 5)
)
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where PreviousRec is null) as Starts join
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where NextRec is null) as Ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
** Method 2 Inline views using not exists **
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value < t.Value and Value > t.Value -5 )
) Starts join
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value > t.Value and Value < t.Value +5 )
) ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
In both methods I use a select distinct to begin because if you have a dulpicate entry at a group start or end things go awry without it.
Here is one way of getting the information you are after:
SELECT Under5.Class,
(
SELECT MIN(m2.Value)
FROM MyTable AS m2
WHERE m2.Value < 5
AND m2.Class = Under5.Class
) AS ValueMin,
(
SELECT MAX(m3.Value)
FROM MyTable AS m3
WHERE m3.Value < 5
AND m3.Class = Under5.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m1.Class
FROM MyTable AS m1
WHERE m1.Value < 5
) AS Under5
UNION
SELECT Over4.Class,
(
SELECT MIN(m4.Value)
FROM MyTable AS m4
WHERE m4.Value >= 5
AND m4.Class = Over4.Class
) AS ValueMin,
(
SELECT Max(m5.Value)
FROM MyTable AS m5
WHERE m5.Value >= 5
AND m5.Class = Over4.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m6.Class
FROM MyTable AS m6
WHERE m6.Value >= 5
) AS Over4

how to insert many records excluding some

I want to create a table with a subset of records from a master table.
for example, i have:
id name code
1 peter 73
2 carl 84
3 jack 73
I want to store peter and carl but not jack because has same peter's code.
I need hight performance because i have 20M records.
I try this:
SELECT id, name, DISTINCT(code) INTO new_tab
FROM old_tab
WHERE (conditions)
but don't work.
Assuming you want to pick the row with the maximum id per code, then this should do it:
insert into new_tab (id, name, code)
(SELECT id, name, code
FROM
(
SELECT id, name, code, rank() as rnk OVER (PARTITION BY code ORDER BY id DESC)
FROM old_tab WHERE rnk = 1
)
)
and for the minimum id per code, just change the sort order in the rank from DESC to ASC:
insert into new_tab (id, name, code)
(SELECT id, name, code
FROM
(
SELECT id, name, code, rank() as rnk OVER (PARTITION BY code ORDER BY id ASC)
FROM old_tab WHERE rnk = 1
)
)
Using a derived table, you can find the minID for each code, then join back to that in the outer to get the rest of the columns for that ID from oldTab.
select id,name,code
insert into newTabFROM
from old_tab t inner join
(SELECT min(id) as minId, code
from old_tab group by code) x
on t.id = x.minId
WHERE (conditions)
Try this:
CREATE TABLE #Temp
(
ID INT,
Name VARCHAR(50),
Code INT
)
INSERT #Temp VALUES (1, 'Peter', 73)
INSERT #Temp VALUES (2, 'Carl', 84)
INSERT #Temp VALUES (3, 'Jack', 73)
SELECT t2.ID, t2.Name, t2.Code
FROM #Temp t2
JOIN (
SELECT t.Code, MIN(t.ID) ID
FROM #temp t
JOIN (
SELECT DISTINCT Code
FROM #Temp
) d
ON t.Code = d.Code
GROUP BY t.Code
) b
ON t2.ID = b.ID

How to select distinct elements when they appear repeated in a table? (T-SQL)

Consider the following table:
create table temp
(
name int,
a int,
b int
)
insert into temp (name, a, b)
values (1, 2, 3)
insert into temp (name, a, b)
values (1, 4, 5)
insert into temp (name, a, b)
values (2, 6, 7)
I want to select *(all fields) with distinct [name]. In the case of two or more rows having the same [name], to choose whether to display the first (1, 2, 3) or the second row(1, 4, 5) the rule can be to choose the one with greater [b].
Can you point how must I write this stored procedure?
SELECT t.*
FROM temp t
INNER JOIN (
SELECT name, max(b) as b
FROM temp
GROUP BY name
) m
ON t.name = m.name
AND t.b = m.b
Not exactly fast on big tables, unless you have an index on name, b.
In MSSQL 2005 and above:
SELECT name, a, b
FROM (
SELECT temp.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY b DESC) AS rn
FROM temp
) t
WHERE rn = 1
Here is one way to do this
sql 2000 and up version
select t1.* from(
select name,max(B) as MaxB
from temp
group by name) t2
join temp t1 on t1.a = t2.MaxB
and t1.name = t2.name
SQL 2005 and up version
select name, a, b
from (
select m.*,
row_number() over (
partition by name
order by B desc) as rn
from temp m
) m2
where m2.rn = 1;
CREATE VIEW Max_B_From_Temp AS
SELECT name,Max(b) as b
FROM temp
GROUP BY name
SELECT temp.*
FROM temp INNER JOIN Max_B_From_Temp
ON
temp.name=Max_B_From_Temp.name and
temp.b=Max_B_From_Temp.b
Its not a stored procedure, but you can call the query from one.