Related
Table 1:
ID
Name
Class
Date
Intime
Outtime
INAM
OUTPM
1
Smith
1st
07-12-2022
8:30 AM
Null
P
Null
1
Smith
1st
07-12-2022
Null
4:30 PM
Null
P
How to join these two rows into a single row?
Required output:
ID
Name
Class
Date
Intime
Outtime
INAM
OUTPM
1
Smith
1st
07-12-2022
8:30 AM
4:30 PM
P
P
Can someone please help me to join into a single row? Thank you...
You may aggregate by the first 4 columns and take the max of the final 4 columns:
SELECT ID, Name, Class, Date,
MAX(Intime) AS Intime, MAX(Outtime) AS Outtime, MAX(INAM) AS INAM,
MAX(OUTPM) AS OUTPM
FROM yourTable
GROUP BY ID, Name, Class, Date;
SELECT
FK,
MAX(Field1) AS Field1,
MAX(Field2) AS Field2
FROM
table1
GROUP BY
FK;
I used MAX, but any aggregate which picks one value from among the GROUP BY rows should work.
Test data:
CREATE TABLE table1 (FK int, Field1 varchar(10), Field2 varchar(10));
INSERT INTO table1 VALUES (3, 'ABC', NULL);
INSERT INTO table1 VALUES (3, NULL, 'DEF');
INSERT INTO table1 VALUES (4, 'GHI', NULL);
INSERT INTO table1 VALUES (4, 'JKL', 'MNO');
INSERT INTO table1 VALUES (4, NULL, 'PQR');
Results:
FK Field1 Field2
-- ------ ------
3 ABC DEF
4 JKL PQR
I need to calculate a cumulative sum (group based, column GroupNr) that resets after exceeding some number, in this example - 330.
Can this be done using a function or CTE? If so, how?
Current Table
GroupNr Name Sum Cumsum
1 Mary 0.00 0.00
1 Jane 179.00 179.00
1 Tom 106.00 285.00
1 Joseph 175.00 460.00
1 Arthur 253.00 713.00
2 Mary 0.00 0.00
2 Jane 365.00 365.00
2 Tom 365.00 730.00
2 Joseph 365.00 1095.00
2 Arthur 365.00 1460.00
Expected Table
GroupNr Name Sum Cumsum Resetcumsum
1 Mary 0.00 0.00 0.00
1 Jane 179.00 179.00 179.00
1 Tom 106.00 285.00 285.00
1 Joseph 175.00 460.00 460.00 -- Reset point
1 Arthur 253.00 713.00 253.00
2 Mary 0.00 0.00 0.00
2 Jane 365.00 365.00 365.00
2 Tom 365.00 730.00 365.00
2 Joseph 365.00 1095.00 365.00
2 Arthur 365.00 1460.00 365.00
Code for tables
CREATE TABLE Table1 (
GroupNr int,
Name varchar(7),
Sum numeric(14, 2),
Cumsum numeric(14, 2)
)
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (1, 'Mary', 0, 0);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (1, 'Jane', 179, 179);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (1, 'Tom', 106, 285);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (1, 'Joseph', 175, 460);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (1, 'Arthur', 253, 713);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (2, 'Mary', 0, 0);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (2, 'Jane', 365, 365);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (2, 'Tom', 365, 730);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (2, 'Joseph', 365, 1095);
INSERT INTO Table1 (GroupNr, Name, Sum, Cumsum)
VALUES (2, 'Arthur', 365, 1460);
Capping a cumulative SUM by using standard SUM() OVER() is not possible due to threshold. One way to achieve such result is recursive CTE:
WITH cte_r AS (
SELECT t.*, ROW_NUMBER() OVER(PARTITION BY GroupNr ORDER BY (SELECT 1)) AS rn
FROM Table1 t
), cte AS (
SELECT GroupNr, Name, [Sum], [CumSum],
CAST([Sum] AS INT) AS ResetCumSum,
rn
FROM cte_r
WHERE rn = 1
UNION ALL
SELECT cte_r.GroupNr, cte_r.Name, cte_r.[Sum], cte_r.[CumSum],
CAST(CASE WHEN cte.ResetCumSum >= 330 THEN 0 ELSE cte.ResetCumSum END + cte_r.[Sum] AS INT)
AS ResetCumSum,
cte_r.rn
FROM cte
JOIN cte_r
ON cte.rn = cte_r.rn-1
AND cte.GroupNr = cte_r.GroupNr
)
SELECT GroupNr, Name, [Sum], [CumSum], ResetCumSum
FROM cte
ORDER BY GroupNr, rn;
Output:
db<>fiddle demo
Warning: Table by design is unordered set so to get stable result a order column is required(like unqiue id, timestamp). Here to emulate insert ROW_NUMBER() OVER(PARTITION BY GroupNr ORDER BY (SELECT 1)) AS rn was used but it is not stable.
Related:
Conditional SUM and the same using MATCH_RECOGNIZE - in my opinion the cleanest way
Extra:
Quirky UPDATE: Running Total until specific condition is true
Disclaimer: "DO NOT USE IT AT PRODUCTION!!!"
-- source table to be extended with id and Resetcumsum columns
CREATE CLUSTERED INDEX IX_ROW_NUM ON Table1(GroupNr, id);
DECLARE #running_total NUMERIC(14,2) = 0
,#prev_running_total NUMERIC(14,2) = 0
,#prev_GroupNr INT = 0;
UPDATE Table1
SET
#prev_running_total = #running_total
,#running_total = Resetcumsum = IIF(#prev_GroupNr != GroupNr
OR #running_total >= 330, 0, #running_total)
+ [Sum]
,#prev_GroupNr = GroupNr
FROM Table1 WITH(INDEX(IX_ROW_NUM))
OPTION (MAXDOP 1);
SELECT *
FROM Table1
ORDER BY id;
db<>fiddle demo - 2
How to group rows with same values in sql and can't disturb after applying order by.
Schema (PostgreSQL v13)
CREATE TABLE IF NOT EXISTS products (
id int NOT NULL,
title varchar(200) NOT NULL,
description varchar(200) NOT NULL,
price int NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO products VALUES
(1, 'test', 'test',2222),
(2, 'test', 'test2',1111),
(3, 'test3', 'test3',1111),
(4, 'test3.2', 'test3.2',555),
(5, 'test3.3', 'test3.3',1111),
(6, 'test4', 'test4 desc',1111);
Query #1
SELECT DISTINCT
tempId,pemId,
title,
description,
(CASE priceno WHEN 1 THEN price ELSE price END )AS price
FROM
(
SELECT
ROW_NUMBER() OVER(PARTITION BY price) AS priceno,
tempId,pemId,title,description,price
FROM (
select id as tempId,id as pemId,title,description,price from products
group by
grouping sets((tempId,price),(tempId,pemId,title,description))
) As b
) As s
order by
tempId, price asc;
tempid
pemid
title
description
price
1
2222
1
1
test
test
2
1111
2
2
test
test2
3
1111
3
3
test3
test3
4
555
4
4
test3.2
test3.2
5
1111
5
5
test3.3
test3.3
6
1111
6
6
test4
test4 desc
Expected Output:
Please suggest any solution in sql. Thank you!
View on DB Fiddle
Schema (PostgreSQL v13)
CREATE TABLE IF NOT EXISTS products (
id int NOT NULL,
title varchar(200) NOT NULL,
description varchar(200) NOT NULL,
price int NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO products VALUES
(1, 'test', 'test',2222),
(2, 'test', 'test2',1111),
(3, 'test3', 'test3',1111),
(4, 'test3.2', 'test3.2',555),
(5, 'test3.3', 'test3.3',1111),
(6, 'test4', 'test4 desc',1111);
Query #1
select id,
title,
description,
(CASE WHEN prno=1 THEN price::varchar ELSE '' END )AS price
from(
select DISTINCT id, title, description, price, prno
from (
select id, 1, null, null, null, price::varchar, 1 as prno from products
union all
select id, 2, id::varchar, title, description, price::varchar, 2 as prno from products
) temp1 (xid, xord, id, title, description,price,prno)
order by price, prno
) as temp2;
id
title
description
price
1111
2
test
test2
6
test4
test4 desc
3
test3
test3
5
test3.3
test3.3
2222
1
test
test
555
4
test3.2
test3.2
View on DB Fiddle
I have the following simple table (Table1), where each row is a student_ID and their name, and each student has one or multiple wins (Wins). I would like to output: Student_ID, Student_name, count of Wins, sorted by count of Wins (descending) and then Student_ID (ascending), excluding those students who have the same count of Wins which is less than the max of the Wins (i.e.5). In other words, Lizzy and Mark have the same count of wins, and 3 is lower than 5, so the output will exclude the two students, Lizzy and Mark.
From comments: "Betty, David and Cathy should be excluded", also.
Table1:
student_id
student_name
wins
1
John
YES
1
John
YES
1
John
YES
1
John
YES
1
John
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
2
Brandon
YES
3
Lizzy
YES
3
Lizzy
YES
3
Lizzy
YES
4
Mark
YES
4
Mark
YES
4
Mark
YES
5
Betty
YES
6
David
YES
7
Cathy
YES
8
Joe
YES
8
Joe
YES
Desired output:
student_id
student_name
cnt_wins
1
John
5
2
Brandon
5
8
Joe
2
Here is my SQL in Oracle. I can't figure out what went wrong. The log says "(SELECT b.cnt_wins, count(b.student_id) has too many values".
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte b
WHERE b.cnt_wins <
(SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1);
There are too many values selected inside the 'in' select:
WHERE a.cnt_wins -- 1 value
not in
(SELECT b.cnt_wins, count(b.student_id) -- 2 values
FROM st_cte b
you shoud either do :
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte ...
or
WHERE (a.cnt_wins, count(something)) not in
(SELECT b.cnt_wins, count(b.student_id)
FROM st_cte ...
Updated based on updated requirements...
The requirement was ambiguous in that Betty, David, and Cathy seem to also meet the criteria to be removed from the result. This requirement was clarified and those rows should have been removed.
Logic has been added to allow only all max_cnt rows, plus any students with a unique count.
Also note that if wins can be any other non-null value, COUNT(wins) is not correct.
Given all that, maybe something like this is a starting point:
Fiddle
WITH cte AS (
SELECT student_id, student_name
, COUNT(wins) cnt_wins
, MAX(COUNT(wins)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
and to handle wins that can be other non-null values:
WITH cte AS (
SELECT student_id, student_name
, COUNT(CASE WHEN wins = 'YES' THEN 1 END) cnt_wins
, MAX(COUNT(CASE WHEN wins = 'YES' THEN 1 END)) OVER () AS max_cnt
FROM Table1
GROUP BY student_id, student_name
)
, cte2 AS (
SELECT cte.*
, COUNT(*) OVER (PARTITION BY cnt_wins) AS cnt_students
FROM cte
)
SELECT student_id, student_name, cnt_wins
FROM cte2
WHERE max_cnt = cnt_wins
OR cnt_students = 1
ORDER BY cnt_wins DESC, student_id
;
Result (with data to test the new requirement, one student (Joe) with unique counts (2)):
STUDENT_ID
STUDENT_NAME
CNT_WINS
1
John
5
2
Brandon
5
8
Joe
2
Setup:
CREATE TABLE table1 (
Student_ID int
, Student_Name VARCHAR2(20)
, Wins VARCHAR2(10)
);
BEGIN
-- Assume only wins are stored.
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 1, 'John', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 2, 'Brandon', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 3, 'Lizzy', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 4, 'Mark', 'YES');
INSERT INTO table1 VALUES ( 5, 'Betty', 'YES');
INSERT INTO table1 VALUES ( 6, 'David', 'YES');
INSERT INTO table1 VALUES ( 7, 'Cathy', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
INSERT INTO table1 VALUES ( 8, 'Joe', 'YES');
END;
/
Correction to the original query in the question:
WITH st_cte AS
(SELECT student_id, student_name, count(wins) cnt_wins
FROM Table1
GROUP BY student_id, student_name
ORDER BY count(wins) DESC, student_id
)
SELECT *
FROM st_cte a
WHERE a.cnt_wins not in
(SELECT b.cnt_wins
FROM st_cte b
WHERE b.cnt_wins < (SELECT max(c.cnt_wins) FROM st_cte c)
GROUP BY b.cnt_wins
HAVING count(b.student_id) > 1
)
;
Before
After
Is there any way to make 'Before' to 'After'?
if a function or procedure is required, please write one for me.
please help me
drop table test;
create table test (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date, rate number);
insert into test values ('1', 'U', '01-JAN-20','15-JAN-20',10);
insert into test values ('1', 'U', '06-JAN-20','01-FEB-20',11);
insert into test values ('1', 'N', '02-FEB-20','15-MAR-20',5);
insert into test values ('1', 'N', '16-MAR-20','15-JUN-20',6);
insert into test values ('2', 'N', '01-JAN-20','11-JAN-20',20);
insert into test values ('2', 'U', '12-JAN-20','12-FEB-20',100);
insert into test values ('2', 'N', '13-FEB-20','19-MAR-20',25);
insert into test values ('2', 'N', '20-MAR-20','21-JUN-20',30);
drop table result;
create table result (employee_code varchar2(8), status varchar2(1), effective_date date, expiry_date date);
insert into result values ('1', 'U', '01-JAN-20','01-FEB-20');
insert into result values ('1', 'N', '02-FEB-20','15-JUN-20');
insert into result values ('2', 'N', '01-JAN-20','11-JAN-20');
insert into result values ('2', 'U', '12-JAN-20','12-FEB-20');
insert into result values ('2', 'N', '13-FEB-20','21-JUN-20');
select * from test;
select * from result;
You just need to use GROUP BY and analytical function as follows:
SQL> Select employee_code,
2 status,
3 min(effective_date) effective_date,
4 max(expiry_date) expiry_date
5 From
6 (Select t.*,
7 Sum(case when lgst is null or lgst <> status then 1 end)
8 over (partition by employee_code order by effective_date) as sm
9 From
10 (Select t.*,
11 Lag(status) over (partition by employee_code order by effective_date) as lgst
12 From test t) t
13 )
14 Group by employee_code, sm, status
15 order by employee_code, effective_date;
EMPLOYEE S EFFECTIVE EXPIRY_DA
-------- - --------- ---------
1 U 01-JAN-20 01-FEB-20
1 N 02-FEB-20 15-JUN-20
2 N 01-JAN-20 11-JAN-20
2 U 12-JAN-20 12-FEB-20
2 N 13-FEB-20 21-JUN-20
SQL>