I have been trying to achieve this in SQL (Oracle 11g) for a while but could not find a proper way to do it.
My table names has the following rows:
NAME REPEAT
---- ------
KAUSHIK 2
KARTHIK 3
NIDHI 1
ASHWINI 5
JAGADEESH 6
What I need is an output like this:
NAME
----
KAUSHIK -- 2 rows
KAUSHIK
KARTHIK
KARTHIK -- 3 rows
KARTHIK
NIDHI -- 1 row
ASHWINI
ASHWINI -- 5 rows
...
and so on.
One of the queries which I have tried so far, which of course is not working. I tried to use unpivot as well but could not seem to find a proper way to accomplish this.
SELECT m.name
FROM names m
INNER JOIN
( SELECT name, repeat FROM names
) n
ON m.name = n.name
CONNECT BY LEVEL <= n.repeat;
Try this:
select * from names
cross join (select rownum n from dual
connect by level <= (select max(repeat) from names))
where n <= repeat
order by name
You may use some temp table containing list of numbers 1 to N, where N is the highest number in your table names. Let call it num(o int) Then the query will be
SELECT *
FROM names, num
WHERE num.o <= names.repeat
If we presume that your all_objects system object has more objects than the max repeat...
SELECT n.name
FROM names n
LEFT JOIN (Select rownum from all_objects) z
on z.rowNum < n.repeat
I just found an alternative with connect by and correlated sub-query.
select name
from names o
connect by level <= ( select repeat from names
i where i.name = o.name )
AND
prior name = name and
prior sys_guid() is not null
order by name;
This question already has answers here:
Retrieving last record in each group from database - SQL Server 2005/2008
(2 answers)
Closed 7 years ago.
This has probably been asked before, but I have no idea on how to find it in the first place.
In the following query (with respective returned values):
select * from tbChapter where idLesson in(12, 13)
-- Result --
id idLesson name sequence
52 12 Intro 1
53 12 Chapter One 2
54 12 Chapter Two 3
55 13 Intro 1
56 13 Chapter One 2
57 13 Chapter Two 3
58 13 Chapter Three 4
I want to get only the last entry for each idLesson, for example:
-- Expected result --
id idLesson name sequence
54 12 Chapter Two 3
58 13 Chapter Three 4
How can I proceed?
Ps: I'l actually replace where idLesson in(12, 13) with subquery that will return dozens of idLesson values.
Try this:
select * from tbChapter where id in
(select MAX(id) from tbChapter group by idLesson)
Try this:
select *
from tbChapter as a
where sequence = (select max(sequence)
from tbChapter as b
where a.id_lesson = b.id_lesson)
The canonical way is to use window functions. Here is an example:
select c.*
from (select c.*, max(sequence) over (partition by idLesson) as maxs
from tblChapter c
) c
where sequence = maxs;
A more creative way that might perform better under some circumstances is to use cross apply:
select c.*
from (select distinct idLesson from tblChapter) l cross apply
(select top 1 c*
from tblChapter c
where c.idLesson = l.idLesson
order by sequence desc
) c;
Note that the first subquery can be replaced by a table that has all the lessons, with one per row.
SELECT
Max(id),
idlesson,
name,
Max(sequence)
FROM
tbChapter
WHERE
idLesson in(12, 13)
GROUP BY
idlesson,
name
All of the recursive CTE examples I can find use essentially the same scenario. They all traverse up or down an org chart. My CTE works great for parent/child relationships that are one-to-one, like where one employee has one manager, but I have a table where a child can have multiple parents. What I want is a disctinct list of parent IDs stemming from one child.
If you're interested in specifics, I'm recursing through the predecessors of a particular task in an MS Project file. I want to start with a particular milestone and trace all the way up to the top of the file in order to find any predecessors that may be affecting the milestone. As you may know, a task can have multiple predecessors.
My relationships look like this:
tblTasks
child parent
3 1
16 1
25 1
25 3
25 16
26 1
26 3
27 25
27 26
Here is my CTE:
;WITH ProjectTrace(Task)
AS
(
-- Anchor member definition (This is task 27)
SELECT t.parent AS Task
FROM #tblTasks t
WHERE t.child = 27
UNION ALL
-- Recursive member definition (This is everything that hooks into 27 via predecessors)
SELECT t.parent AS Task
FROM #tblTasks t
INNER JOIN ProjectTrace trace
ON t.child = trace.Task
)
SELECT * FROM ProjectTrace ORDER BY Task
I want to supply task #27 to the query and get only 1,3,16,25,26 in my resultset. However, because of the way the recursion works, my resultset looks like this:
Task
1
1
1
1
1
3
3
16
25
26
If you look at the relationships, I guess that makes sense. I can always change the select a the end to a select distinct, but when I get really deep in the project, say task number 500, it returns millions of records.
What might I be doing wrong?
I think distinct is good way to do this. You can also check iterative insert solution:
declare #Temp table(Task int primary key)
insert into #Temp
select distinct parent from Table1 where child = 27
while 1 = 1
begin
insert into #Temp
select distinct T.parent
from Table1 as T
where
exists (select * from #Temp as TT where TT.Task = T.child) and
not exists (select * from #Temp as TT where TT.Task = T.parent)
if ##rowcount = 0 break
end
select * from #Temp
don't know if it would be faster, check it by yourself.
I don't think there's a better performing option than DISTINCT.
You can't use a LEFT JOIN to limit to where the trace.Task IS NULL, you can't GROUP BY. I think DISTINCT is the way to go.
I have a table that lists the duration of different activities. It looks like
id duration
1 15
2 30
3 30
4 45
5 30
...etc
I want to sum these activities like
for (lastActivity=1 to 5)
SELECT id, SUM(duration) FROM durations
WHERE id<=lastActivity
to produce an output like
id endtime
1 15
2 45
3 75
4 120
5 150
where each row sums the duration of the activities up to its position in the list.
It seems an easy task (and possibly is), but I can't figure out how the sql should look like to produce such an output. I have tried using GROUP BY together with the OVER clause but perhaps there's a simpler way of doing this.
SELECT t.id,
t.duration,
rt.runningTotal
FROM mytable t
CROSS apply (SELECT Sum(duration) AS runningTotal
FROM emp
WHERE id <= t.id) AS rt
ORDER BY t.id
The APPLY operator allows you to invoke a table-valued function for each row returned by an outer table expression of a query. The table-valued function acts as the right input and the outer table expression acts as the left input. The right input is evaluated for each row from the left input and the rows produced are combined for the final output. The list of columns produced by the APPLY operator is the set of columns in the left input followed by the list of columns returned by the right input.
Note : To use APPLY, the database compatibility level must be at least 90. This was introduced in sql server 2005.
you can use running total
check this post
Running total in sqlserver stackoverflow
This will degrade depending on how large your actual table is, but this should do the trick:
Some interesting reading around this can be found here
SELECT 1 as id, 15 as num into #test
UNION ALL SELECT 2, 30
UNION ALL SELECT 3, 30
UNION ALL SELECT 4, 45
UNION ALL SELECT 5, 30
select
t1.id
,MAX(t1.num) as id_num
,SUM(t2.num) as running_total
from #test t1
LEFT OUTER JOIN #test t2 on t2.id <= t1.id
GROUP BY
t1.id
Try this :
select d2.ID,sum(d1.duration)
from durations d1,durations d2
where d1.id<=d2.id
group by d2.id
I need to sum points on each level earned by a tree of users. Level 1 is the sum of users' points of the users 1 level below the user. Level 2 is the Level 1 points of the users 2 levels below the user, etc...
The calculation happens once a month on a non production server, no worries about performance.
What would the SQL look like to do it?
If you're confused, don't worry, I am as well!
User table:
ID ParentID Points
1 0 230
2 1 150
3 0 80
4 1 110
5 4 54
6 4 342
Tree:
0
|---\
1 3
| \
2 4---
\ \
5 6
Output should be:
ID Points Level1 Level2
1 230 150+110 150+110+54+342
2 150
3 80
4 110 54+342
5 54
6 342
SQL Server Syntax and functions preferably...
If you were using Oracle DBMS that would be pretty straightforward since Oracle supports tree queries with the CONNECT BY/STARTS WITH syntax. For SQL Server I think you might find Common Table Expressions useful
Trees don't work well with SQL. If you have very (very very) few write accesses, you could change the tree implementation to use nested sets, that would make this query incredibly easy.
Example (if I'm not mistaken):
SELECT SUM(points)
FROM users
where left > x and right < y
However, any changes on the tree require touching a massive amount of rows. It's probably better to just do the recursion in you client.
I would say: create a stored procedure, probably has the best performance.
Or if you have a maximum number of levels, you could create subqueries, but they will have a very poort performance.
(Or you could get MS SQL Server 2008 and get the new hierarchy functions... ;) )
SQL in general, like others said, does not handle well such relations. Typically, a surrogate 'relations' table is needed (id, parent_id, unique key on (id, parent_id)), where:
every time you add a record in 'table', you:
INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_id]);
INSERT INTO relations (id, parent_id) VALUES ([current_id], [current_parent_id]);
INSERT INTO relations (id, parent_id)
SELECT [current_id], parent_id
FROM relations
WHERE id = [current_parent_id];
have logic to avoid cycles
make sure that updates, deletions on 'relations' are handled with stored procedures
Given that table, you want:
SELECT rel.parent_id, SUM(tbl.points)
FROM table tbl INNER JOIN relations rel ON tbl.id=rel.id
WHERE rel.parent_id <> 0
GROUP BY rel.parent_id;
Ok, this gives you the results you are looking for, but there are no guarantees that I didn't miss something. Consider it a starting point. I used SQL 2005 to do this, SQL 2000 does not support CTE's
WITH Parent (id, GrandParentId, parentId, Points, Level1Points, Level2Points)
AS
(
-- Find root
SELECT id,
0 AS GrandParentId,
ParentId,
Points,
0 AS Level1Points,
0 AS Level2Points
FROM tblPoints ptr
WHERE ptr.ParentId = 0
UNION ALL (
-- Level2 Points
SELECT pa.GrandParentId AS Id,
NULL AS GrandParentId,
NULL AS ParentId,
0 AS Points,
0 AS Level1Points,
pa.Points AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.GrandParentId = pt.Id
UNION ALL
-- Level1 Points
SELECT pt.ParentId AS Id,
NULL AS GrandParentId,
NULL AS ParentId,
0 AS Points,
pt.Points AS Level1Points,
0 AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL
UNION ALL
-- Points
SELECT pt.id,
pa.ParentId AS GrandParentId,
pt.ParentId,
pt.Points,
0 AS Level1Points,
0 AS Level2Points
FROM tblPoints pt
JOIN Parent pa ON pa.Id = pt.ParentId AND pa.ParentId IS NOT NULL )
)
SELECT id,
SUM(Points) AS Points,
SUM(Level1Points) AS Level1Points,
CASE WHEN SUM(Level2Points) > 0 THEN SUM(Level1Points) + SUM(Level2Points) ELSE 0 END AS Level2Points
FROM Parent
GROUP BY id
ORDER by id
If you are working with trees stored in a relational database, I'd suggest looking at "nested set" or "modified preorder tree traversal". The SQL will be as simple as that:
SELECT id,
SUM(value) AS value
FROM table
WHERE left>left\_value\_of\_your\_node
AND right<$right\_value\_of\_your\_node;
... and do this for every node you are interested in.
Maybe this will help you:
http://www.dbazine.com/oracle/or-articles/tropashko4 or use google.
You have a couple of options:
Use a cursor and a recursive user-defined function call (it's quite slow)
Create a cache table, update it on INSERT using a trigger (it's the fastest solution but could be problematic if you have lots of updates to the main table)
Do a client-side recursive calculation (preferable if you don't have too many records)
You can write a simple recursive function to do the job. My MSSQL is a little bit rusty, but it would look like this:
CREATE FUNCTION CALC
(
#node integer,
)
returns
(
#total integer
)
as
begin
select #total = (select node_value from yourtable where node_id = #node);
declare #children table (value integer);
insert into #children
select calc(node_id) from yourtable where parent_id = #node;
#current = #current + select sum(value) from #children;
return
end
The following table:
Id ParentId
1 NULL
11 1
12 1
110 11
111 11
112 11
120 12
121 12
122 12
123 12
124 12
And the following Amount table:
Id Val
110 500
111 50
112 5
120 3000
121 30000
122 300000
Only the leaves (last level) Id's have a value defined.
The SQL query to get the data looks like:
;WITH Data (Id, Val) AS
(
select t.Id, SUM(v.val) as Val from dbo.TestTable t
join dbo.Amount v on t.Id = v.Id
group by t.Id
)
select cd.Id, ISNULL(SUM(cd.Val), 0) as Amount FROM
(
-- level 3
select t.Id, d.val from TestTable t
left join Data d on d.id = t.Id
UNION
-- level 2
select t.parentId as Id, sum(y.Val) from TestTable t
left join Data y on y.id = t.Id
where t.parentId is not null
group by t.parentId
UNION
-- level 1
select t.parentId as Id, sum(y.Val) from TestTable t
join TestTable c on c.parentId = t.Id
left join Data y on y.id = c.Id
where t.parentId is not null
group by t.parentId
) AS cd
group by id
this results in the output:
Id Amount
1 333555
11 555
12 333000
110 500
111 50
112 5
120 3000
121 30000
122 300000
123 0
124 0
I hope this helps.