Oracle SQL - Generate aggregate rows for certain rows using select - sql

I have a table like below.
|FILE| ID |PARENTID|SHOWCHILD|CAT1|CAT2|CAT3|TOTAL|
|F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
|F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
|F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
|F4 | LG1| | Y | 6 | 3 | 7 | 16 |
|F5 | LG2| | Y | 4 | 7 | 3 | 14 |
Now, Is it possible if I want to find the total (ie) aggregate of cat1, cat2, cat3 & total only for rows which has showChild as 'Y' and add that to the resultset.
|Tot| Res | Res | N | 10 | 10 | 10 | 30 |
Expected final output:
|FILE| ID |PARENTID|SHOWCHILD|CAT1|CAT2|CAT3|TOTAL|
|F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
|F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
|F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
|F4 | LG1| | Y | 6 | 3 | 7 | 16 |
|F5 | LG2| | Y | 4 | 7 | 3 | 14 |
|Tot | Res| Res | N | 10 | 10 | 10 | 30 |
Here I have added the Tot row(last row) after considering only the rows which has showchild as 'Y' and added that to the resultset.
I am trying for a solution without using UNION
Any help on achieving the above results is highly appreciated.
Thank you.

One approach would be to use a union:
WITH cte AS (
SELECT "FILE", ID, PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL, 1 AS position
FROM yourTable
UNION ALL
SELECT 'Tot', 'Res', 'Res', 'N', SUM(CAT1), SUM(CAT2), SUM(CAT3), SUM(TOTAL), 2
FROM yourTable
WHERE SHOWCHILD = 'Y'
)
SELECT "FILE", ID, PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL
FROM cte
ORDER BY
position,
"FILE";
Demo

You can try using UNION
select FILE,ID ,PARENTID,SHOWCHILD,CAT1,CAT2,CAT3,TOTAL from table1
union
select 'Tot','Res','Res','N',sum(cat1), sum(cat2),sum(cat3), sum(total)
from table1 where SHOWCHILD='Y'

I see you already accepted an answer, but you did ask for a solution that did not involve UNION. One such solution would be to use GROUPING SETS.
GROUPING SETS allow you to specify different grouping levels in your query and generate aggregates at each of those levels. You can use it to generate an output row for each record plus a single "total" row, as per your requirements. The function GROUPING can be used in expressions to identify whether each output row is in one group or the other.
Example, with test data:
with input_data ("FILE", "ID", PARENTID, SHOWCHILD, CAT1, CAT2, CAT3, TOTAL ) AS (
SELECT 'F1','A1','P1','N',3,2,6,11 FROM DUAL UNION ALL
SELECT 'F2','A2','P2','N',4,7,3,14 FROM DUAL UNION ALL
SELECT 'F3','A3','P1','N',3,1,1,5 FROM DUAL UNION ALL
SELECT 'F4','LG1','','Y',6,3,7,16 FROM DUAL UNION ALL
SELECT 'F5','LG2','','Y',4,7,3,14 FROM DUAL )
SELECT decode(grouping("FILE"),1,'Tot',"FILE") "FILE",
decode(grouping("ID"),1,'Res',"ID") "ID",
decode(grouping(parentid),1, 'Res',parentid) parentid,
decode(grouping(showchild),1, 'N',showchild) showchild,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat1,0)),sum(cat1)) cat1,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat2,0)),sum(cat2)) cat2,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',cat3,0)),sum(cat3)) cat3,
decode(grouping("FILE"),1,sum(decode(showchild,'Y',total,0)),sum(total)) total
from input_data
group by grouping sets (("FILE", "ID", parentid, showchild), ())
+------+-----+-----+----------+-----------+------+------+------+-------+
| FILE | F2 | ID | PARENTID | SHOWCHILD | CAT1 | CAT2 | CAT3 | TOTAL |
+------+-----+-----+----------+-----------+------+------+------+-------+
| F1 | F1 | A1 | P1 | N | 3 | 2 | 6 | 11 |
| F2 | F2 | A2 | P2 | N | 4 | 7 | 3 | 14 |
| F3 | F3 | A3 | P1 | N | 3 | 1 | 1 | 5 |
| F4 | F4 | LG1 | - | Y | 6 | 3 | 7 | 16 |
| F5 | F5 | LG2 | - | Y | 4 | 7 | 3 | 14 |
| Tot | Tot | Res | Res | N | 10 | 10 | 10 | 30 |
+------+-----+-----+----------+-----------+------+------+------+-------+

Related

Join two tables on consecutive segments

I want to join two tables, where the first table has more entries than the second, such that rows from each are joined in order. Maybe a little example would be helpful:
Table T:
| tid | sid | ron | val | seqno |
| --- | --- | --- | --- | --- |
| 1 | a | x1 | 15 | 1 |
| 2 | b | x2 | 10 | 3 |
| 2 | b | x3 | 20 | 4 |
| 3 | a | x5 | 10 | 5 |
| 4 | c | x9 | 15 | 7 |
| 4 | c | x9 | 15 | 8 |
| 4 | c | x9 | 20 | 10 |
| 4 | c | x9 | 15 | 11 |
| 6 | b | x11 | 22 | 12 |
| 7 | b | x12 | 10 | 14 |
| 7 | b | x13 | 10 | 16 |
| 7 | b | x13 | 10 | 17 |
| 7 | b | x14 | 10 | 19 |
The second table (Table C) is as follows (in reality, more columns):
| tid | sid | ron | val | fid |
| --- | --- | --- | --- | --- |
| 2 | b | x3 | 20 | 54 |
| 4 | c | x9 | 15 | 12 |
| 4 | c | x9 | 15 | 14 |
| 4 | c | x9 | 20 | 15 |
| 4 | c | x9 | 15 | 20 |
| 7 | b | x13 | 10 | 112 |
| 7 | b | x13 | 10 | 113 |
seqNo and fid are there in each table to provide ordering within the groups formed by (tid, sid, ron), and that is the ordering I'd like to maintain.
How can I get from these two tables to something like the following table?
| tid | sid | ron | val | fid | seqno |
| --- | --- | --- | --- | --- | --- |
| 2 | b | x3 | 20 | 54 | 4 |
| 4 | c | x9 | 15 | 12 | 7 |
| 4 | c | x9 | 15 | 14 | 8 |
| 4 | c | x9 | 20 | 15 | 10 |
| 4 | c | x9 | 15 | 20 | 11 |
| 7 | b | x13 | 10 | 112 | 16 |
| 7 | b | x13 | 10 | 113 | 17 |
I can't assign a rank to each element in the group and use that for matching inside of a LEFT JOIN, since there are cases where matching doesn't begin at the end of the group (for example tid=7). Also, because val in the same group may have repeated values, I can't blindly match on it either, as that may blow up the number of rows.
This is what I managed to get late last night, seems to be working correctly:
WITH
table_t AS (
SELECT *
FROM (VALUES
(1,'a','x1',15,1),
(2,'b','x2',10,3),
(2,'b','x3',20,4),
(3,'a','x5',10,5),
(4,'c','x9',15,7),
(4,'c','x9',15,8),
(4,'c','x9',20,10),
(4,'c','x9',15,11),
(6,'b','x11',22,12),
(7,'b','x12',10,14),
(7,'b','x13',10,16),
(7,'b','x13',10,17),
(7,'b','x14',10,19)
) AS c(tid, sid, ron, val, seq)
),
table_t_ranked AS (
SELECT *
, DENSE_RANK() OVER (PARTITION BY tid, sid, ron ORDER BY seq ASC) AS ranking
FROM table_t
),
table_c AS (
SELECT *
FROM (VALUES
(2,'b','x3',20,54),
(4,'c','x9',15,12),
(4,'c','x9',15,14),
(4,'c','x9',20,15),
(4,'c','x9',15,20),
(7,'b','x13',10,112),
(7,'b','x13',10,113)
) AS c(tid, sid, ron, val, fid)
),
table_c_ranked AS (
SELECT *
, DENSE_RANK() OVER (PARTITION BY tid, sid, ron ORDER BY fid ASC) AS ranking
FROM table_c
),
foo AS (
SELECT c.*
, t.seq
, t.ranking as ranking_t
FROM table_c_ranked c
LEFT JOIN table_t_ranked t
ON c.tid = t.tid
AND c.sid = t.sid
AND c.ron = t.ron
AND c.val = t.val
)
SELECT tid, sid, ron, val, fid, seq
FROM foo
WHERE ranking = ranking_t
ORDER BY tid, seq

Merge groups if they contain the same value

I have the following table:
+-----+----+---------+
| grp | id | sub_grp |
+-----+----+---------+
| 10 | A2 | 1 |
| 10 | B4 | 2 |
| 10 | F1 | 2 |
| 10 | B3 | 3 |
| 10 | C2 | 4 |
| 10 | A2 | 4 |
| 10 | H4 | 5 |
| 10 | K0 | 5 |
| 10 | Z3 | 5 |
| 10 | F1 | 5 |
| 10 | A1 | 5 |
| 10 | A | 6 |
| 10 | B | 6 |
| 10 | B | 7 |
| 10 | C | 7 |
| 10 | C | 8 |
| 10 | D | 8 |
| 20 | A | 1 |
| 20 | B | 1 |
| 20 | B | 2 |
| 20 | C | 2 |
| 20 | C | 3 |
| 20 | D | 3 |
+-----+----+---------+
Within every grp, my goal is to merge all the sub_grp sharing at least one id.
More than 2 sub_grp can be merged together.
The expected result should be:
+-----+----+---------+
| grp | id | sub_grp |
+-----+----+---------+
| 10 | A2 | 1 |
| 10 | B4 | 2 |
| 10 | F1 | 2 |
| 10 | B3 | 3 |
| 10 | C2 | 1 |
| 10 | A2 | 1 |
| 10 | H4 | 2 |
| 10 | K0 | 2 |
| 10 | Z3 | 2 |
| 10 | F1 | 2 |
| 10 | A1 | 2 |
| 10 | A | 6 |
| 10 | B | 6 |
| 10 | B | 6 |
| 10 | C | 6 |
| 10 | C | 6 |
| 10 | D | 6 |
| 20 | A | 1 |
| 20 | B | 1 |
| 20 | B | 1 |
| 20 | C | 1 |
| 20 | C | 1 |
| 20 | D | 1 |
+-----+----+---------+
Here is a SQL Fiddle with the test values: http://sqlfiddle.com/#!9/13666c/2
I am trying to solve this either with a stored procedure or queries.
This is an evolution from my previous problem: Merge rows containing same values
My understanding of the problem
Merge sub_grp (for a given grp) if any one of the IDs in one sub_grp match any one of the IDs in another sub_grp. A given sub_grp can be merged with only one other (the earliest in ascending order) sub_grp.
Disclaimer
This code may work. Not tested as OP did not provide DDLs and data scripts.
Solution
UPDATE final
SET sub_grp = new_sub_grp
FROM
-- For each grp, sub_grp combination return a matching new_sub_grp
( SELECT a.grp, a.sub_grp, MatchGrp.sub_grp AS new_sub_grp
FROM tbl AS a
-- Inner join will exclude cases where there are no matching sub_grp and thus nothing to update.
INNER JOIN
-- Find the earliest (if more than one sub-group is a match) matching sub-group where one of the IDs matches
( SELECT TOP 1 grp, sub_grp
FROM tbl AS b
-- b.sub_grp > a.sub_grp - this will only look at the earlier sub-groups avoiding the "double linking"
WHERE b.grp = a.grp AND b.sub_grp > a.sub_grp AND b.ID = a.ID
ORDER BY grp, sub_grp ) AS MatchGrp ON 1 = 1
-- Only return one record per grp, sub_grp combo
GROUP BY grp, sub_grp, MatchGrp.sub_grp ) AS final
You can re-number sub groups afterwards as a separate update statement with the help of DENSE_RANK window function.

How to get values of rows and columns

I have two tables.
Student Table
Property Table
Result Table
How can I get the value of Student Table and the property ID of the column fron the Property table and merge that into the Result table?
Any advice would be helpful.
Update #1:
I tried using Christian Moen 's suggestion, this is what i get.
You need to UNPIVOT the Student's columns first, to get the columns (properties names) in one column as rows. Then join with the Property table based on the property name like this:
WITH UnPivoted
AS
(
SELECT ID, value,col
FROM
(
SELECT ID,
CAST(Name AS NVARCHAR(50)) AS Name,
CAST(Class AS NVARCHAR(50)) AS Class,
CAST(ENG AS NVARCHAR(50)) AS ENG,
CAST(TAM AS NVARCHAR(50)) AS TAM,
CAST(HIN AS NVARCHAR(50)) AS HIN,
CAST(MAT AS NVARCHAR(50)) AS MAT,
CAST(PHY AS NVARCHAR(50)) AS PHY
FROM Student
) AS s
UNPIVOT
(value FOR col IN
([Name], [class], [ENG], [TAM], [HIN], [MAT], [PHY])
)AS unpvt
)
SELECT
ROW_NUMBER() OVER(ORDER BY u.ID,PropertyID) AS ID,
p.PropertyID,
u.Value,
u.ID AS StudID
FROM Property AS p
INNER JOIN UnPivoted AS u ON p.PropertyName = u.col;
For the first ID, I used the ranking function ROW_NUMBER() to generate this sequence id.
This will give the exact results that you are looking for.
Results:
| ID | PropertyID | Value | StudID |
|----|------------|--------|--------|
| 1 | 1 | Jack | 1 |
| 2 | 2 | 10 | 1 |
| 3 | 3 | 89 | 1 |
| 4 | 4 | 88 | 1 |
| 5 | 5 | 45 | 1 |
| 6 | 6 | 100 | 1 |
| 7 | 7 | 98 | 1 |
| 8 | 1 | Jill | 2 |
| 9 | 2 | 10 | 2 |
| 10 | 3 | 89 | 2 |
| 11 | 4 | 99 | 2 |
| 12 | 5 | 100 | 2 |
| 13 | 6 | 78 | 2 |
| 14 | 7 | 91 | 2 |
| 15 | 1 | Trevor | 3 |
| 16 | 2 | 12 | 3 |
| 17 | 3 | 100 | 3 |
| 18 | 4 | 50 | 3 |
| 19 | 5 | 49 | 3 |
| 20 | 6 | 94 | 3 |
| 21 | 7 | 100 | 3 |
| 22 | 1 | Jim | 4 |
| 23 | 2 | 8 | 4 |
| 24 | 3 | 100 | 4 |
| 25 | 4 | 91 | 4 |
| 26 | 5 | 92 | 4 |
| 27 | 6 | 100 | 4 |
| 28 | 7 | 100 | 4 |
Other option is to use of apply if you don't want to go unpivot way
select row_number() over (order by (select 1)) ID, p.PropertyID [PropID], a.Value, a.StuID
from Student s
cross apply
(
values (s.ID, 'Name', s.Name),
(s.ID, 'Class', cast(s.Class as varchar)),
(s.ID, 'ENG', cast(s.ENG as varchar)),
(s.ID, 'TAM', cast(s.TAM as varchar)),
(s.ID, 'HIN', cast(s.HIN as varchar)),
(s.ID, 'MAT', cast(s.MAT as varchar)),
(s.ID, 'PHY', cast(s.PHY as varchar))
) as a(StuID, Property, Value)
join Property p on p.PropertyName = a.Property

MS-SQL Grouping and counting query

So I have a table and the columns are Product and ProductGroupID.
Every Product can be in 1 or more groups as shown in this example:
+-------+--------------+
|product|ProductGroupId|
+-------+--------------+
| 10 | 2 |
| 10 | 9 |
| 10 | 4 |
| 10 | 7 |
| 20 | 7 |
| 30 | 4 |
| 40 | 1 |
| 50 | 11 |
| 50 | 12 |
| 60 | 2 |
| 70 | 9 |
| 80 | 11 |
| 90 | 12 |
| 100 | 13 |
+-------+--------------+
For every product I need to get it's group or groups and to bring the number of product which are in those groups.
For example product 10 is in groups 2,4,9,7 so I need to count all the products that are in those groups, in this case 5 (for count of products 10,60,70,30,20).
I attach the full desired outcome for this example.
https://i.stack.imgur.com/XTa4R.png
Any suggestion how to do this in ms-sql?
Thnaks!
If I understood it correctly this will work for you.
Schema from your image:
CREATE TABLE #PRODUCTS (PRODUCT INT, PRODUCTGROUP_ID INT)
INSERT INTO #PRODUCTS
SELECT 10,2
UNION ALL
SELECT 10,9
UNION ALL
SELECT 10,4
UNION ALL
SELECT 10,7
UNION ALL
SELECT 20,7
UNION ALL
SELECT 30,4
UNION ALL
SELECT 40,1
UNION ALL
SELECT 50,11
UNION ALL
SELECT 50,12
UNION ALL
SELECT 60,2
UNION ALL
SELECT 70,9
UNION ALL
SELECT 80,11
UNION ALL
SELECT 90,12
UNION ALL
SELECT 100,13
Now do a Self Join with GroupId on condition
SELECT P.PRODUCT, COUNT(DISTINCT G.PRODUCT) Linked_GroupProducts
FROM #PRODUCTS P
INNER JOIN #PRODUCTS G ON P.PRODUCTGROUP_ID = G.PRODUCTGROUP_ID
GROUP BY P.PRODUCT
And the result will be
+---------+----------------------+
| PRODUCT | Linked_GroupProducts |
+---------+----------------------+
| 10 | 5 |
| 20 | 2 |
| 30 | 2 |
| 40 | 1 |
| 50 | 3 |
| 60 | 2 |
| 70 | 2 |
| 80 | 2 |
| 90 | 2 |
| 100 | 1 |
+---------+----------------------+

find other columns value based on maximum of one column using groupby particular column

I have data like below
+-------+---------+--------+
| Count | Mindif | Device |
+-------+---------+--------+
| 45 | 3 | A |
| 78 | 4 | A |
| 52 | 5 | A |
| 24 | 6 | A |
| 22 | 1 | B |
| 22 | 2 | B |
| 34 | 3 | B |
| 37 | 4 | B |
| 52 | 5 | B |
| 34 | 6 | B |
| 13 | 1 | C |
| 30 | 2 | C |
| 57 | 3 | C |
| 111 | 4 | C |
| 35 | 5 | C |
+-------+---------+--------+
Want to find Mindif and device based on max value of count.
Output be like
+-------+---------+--------+
| Count | Mindif | Device |
+-------+---------+--------+
| 78 | 4 | A |
| 52 | 5 | B |
| 111 | 4 | C |
+-------+---------+--------+
You can use a query like this:
SELECT t1.Count, t1.Mindif, t1.Device
FROM mytable AS t1
JOIN (
SELECT Device, MAX(Count) AS Count
FROM mytable
GROUP BY Device
) AS t2 ON t1.Device = t2.Device AND t1.Count = t2.Count
The query uses a derived table that returns the max Count value per Device. Joining back to the original table we can get the desired result.
using Window Function
SELECT Count, Mindif, Device
FROM
(SELECT Count, Mindif, Device,
rank() over (order by Count desc) as r
FROM table) S
WHERE S.r = 1;
OR
Simple Join with MAX
SELECT a.* FROM table a
LEFT SEMI JOIN
(SELECT MAX(Count)Cnt
FROM table)b on (a.Count = b.Cnt)