How to Use Group By on the Basis of Column Value? - sql

I am trying to use Group By but unable to achieve the output
I want to Group by with Date, shift and with Mass.
Data is like :
date | shift | mass
---------+-------+------
01-05-20 | A | 5
01-05-20 | B | 3
01-05-20 | B | 3
02-05-20 | A | 11
02-05-20 | A | 5
02-05-20 | C | 12
02-05-20 | C | 12
02-05-20 | B | 5
OutPut which i want
date | shift | mass>3 | mass>10
---------+-------+--------+--------
01-05-20 | A | 1 | 0
01-05-20 | B | 2 | 0
02-05-20 | A | 1 | 1
02-05-20 | B | 1 | 0
02-05-20 | C | 0 | 2

You can use Conditional Aggregation through GROUPing BY mydate,shift :
SELECT mydate,shift,
SUM(CASE WHEN mass > 3 THEN 1 ELSE 0 END) AS "mass>3",
SUM(CASE WHEN mass >10 THEN 1 ELSE 0 END) AS "mass>10"
FROM t
GROUP BY mydate,shift
ORDER BY mydate,shift;
By the way( as you asked for it within a comment ), you can also use DECODE() function :
SELECT mydate,shift,
SUM(DECODE(SIGN(mass-3),1,1,0)) AS "mass>3",
SUM(DECODE(SIGN(mass-10),1,1,0)) AS "mass>10"
FROM t
GROUP BY mydate,shift
ORDER BY mydate,shift
Demo

SQL> with t(mydate, shift, mass) as
2 (
3 select '01-05-2020', 'A', 5 from dual union all
4 select '01-05-2020', 'B', 3 from dual union all
5 select '01-05-2020', 'B', 3 from dual union all
6 select '02-05-2020', 'A', 11 from dual union all
7 select '02-05-2020', 'A', 5 from dual union all
8 select '02-05-2020', 'C', 12 from dual union all
9 select '02-05-2020', 'C', 12 from dual union all
10 select '02-05-2020', 'B', 5 from dual
11 )
12 SELECT mydate,shift,
13 SUM(CASE WHEN mass >=3 AND
14 mass < 10 THEN 1 ELSE 0 END) AS "mass>=3",
15 SUM(CASE WHEN mass >10 THEN 1 ELSE 0 END) AS "mass>10"
16 FROM t
17 GROUP BY mydate,shift
18 ORDER BY mydate,shift;
MYDATE S mass>=3 mass>10
---------- - ---------- ----------
01-05-2020 A 1 0
01-05-2020 B 2 0
02-05-2020 A 1 1
02-05-2020 B 1 0
02-05-2020 C 0 2
SQL>
SQL>

Related

How to calculate sum of rows that is last 1-7, 8-14, 15-21 etc. by group in sql as separate columns [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I like to calculate sum of rows that is last 1-7, 8-14, 15-21 etc. by group in sql as separate columns:
Input Data:
Expected Result:
You can use analytic functions:
SELECT t.*,
CASE COUNT(*) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN CURRENT ROW AND 6 FOLLOWING
)
WHEN 7
THEN SUM( vol ) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN CURRENT ROW AND 6 FOLLOWING
)
END AS last7,
CASE COUNT(*) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN 7 FOLLOWING AND 13 FOLLOWING
)
WHEN 7
THEN SUM( vol ) OVER (
PARTITION BY grp
ORDER BY rw
ROWS BETWEEN 7 FOLLOWING AND 13 FOLLOWING
)
END AS last8_14
FROM table_name t
Which, for your sample data:
CREATE TABLE table_name ( rw, grp, vol ) AS
SELECT 1, 'A', 1 FROM DUAL UNION ALL
SELECT 2, 'A', 2 FROM DUAL UNION ALL
SELECT 3, 'A', 3 FROM DUAL UNION ALL
SELECT 4, 'A', 4 FROM DUAL UNION ALL
SELECT 5, 'A', 2 FROM DUAL UNION ALL
SELECT 6, 'A', 3 FROM DUAL UNION ALL
SELECT 7, 'A', 4 FROM DUAL UNION ALL
SELECT 8, 'A', 5 FROM DUAL UNION ALL
SELECT 9, 'A', 5 FROM DUAL UNION ALL
SELECT 10, 'A', 6 FROM DUAL UNION ALL
SELECT 11, 'A', 7 FROM DUAL UNION ALL
SELECT 12, 'A', 3 FROM DUAL UNION ALL
SELECT 13, 'A', 4 FROM DUAL UNION ALL
SELECT 14, 'A', 5 FROM DUAL UNION ALL
SELECT 15, 'A', 4 FROM DUAL;
Outputs:
RW | GRP | VOL | LAST7 | LAST8_14
-: | :-- | --: | ----: | -------:
1 | A | 1 | 19 | 35
2 | A | 2 | 23 | 34
3 | A | 3 | 26 | null
4 | A | 4 | 29 | null
5 | A | 2 | 32 | null
6 | A | 3 | 33 | null
7 | A | 4 | 34 | null
8 | A | 5 | 35 | null
9 | A | 5 | 34 | null
10 | A | 6 | null | null
11 | A | 7 | null | null
12 | A | 3 | null | null
13 | A | 4 | null | null
14 | A | 5 | null | null
15 | A | 4 | null | null
db<>fiddle here

Oracle SQL compare dates less than two days old

Hi guys I need your support how to perform this logic.
I'm stucking currently and i really do not know how to procide further.
Target: compare ref_num, entry_date and status=1
with
ref_num, change_status_date, status 0 (always the last two rows)
Compare, if these dates change_status_date and entry_date are less then 2 days old then update the status value from status=1 to status=2, else If the days are more then 3 days old change to status=0
Any idea how to perfom a correct select sql and update sql?
+--------------+-----------------------+---------------------+----------+
| ref_num | entry_date | change_status_date | status
+--------------+-----------------------+---------------------+----------+
| x326585 | 28/04/2020 16:54:14 | | 1 |
| x326585 | 25/04/2020 13:14:00 | 27/04/2020 23:44:00 | 0 |
| x326585 | 20/04/2020 11:15:02 | 20/04/2020 23:52:01 | 0 |
| A142585 | 28/04/2020 16:55:14 | | 1 |
| A142585 | 26/04/2020 11:54:04 | 27/04/2020 22:54:51 | 0 |
| A142585 | 24/04/2020 10:44:14 | 25/04/2020 13:17:23 | 0 |
| B188532 | 29/04/2020 11:34:41 | | 1 |
| B188532 | 14/04/2020 11:44:24 | 15/05/2020 23:11:10 | 0 |
| B188532 | 11/04/2020 08:34:10 | 13/05/2020 11:44:41 | 0 |
+--------------+-----------------------+---------------------+----------+
END RESULTS:
+--------------+-----------------------+---------------------+----------+
| ref_num | entry_date | change_status_date | status
+--------------+-----------------------+---------------------+----------+
| x326585 | 28/04/2020 16:54:14 | 27/07/2020 23:47:31 | 2 | is less than 3 days (28/04/2020 16:54:14 - 27/04/2020 23:44:00) -> status 2
| x326585 | 25/04/2020 13:14:00 | 27/04/2020 23:44:00 | 0 |
| x326585 | 20/04/2020 11:15:02 | 20/04/2020 23:52:01 | 0 |
| A142585 | 28/04/2020 16:35:58 | 27/07/2020 23:47:31 | 2 | is less than 3 days (28/04/2020 16:35:58 - 27/04/2020 22:54:51) -> status 2
| A142585 | 26/04/2020 11:54:04 | 27/04/2020 22:54:51 | 0 |
| A142585 | 24/04/2020 10:44:14 | 25/04/2020 13:17:23 | 0 |
| B188532 | 29/04/2020 11:34:41 | 27/07/2020 23:47:31 | 0 | is more than 3 days (29/04/2020 11:34:41 - 15/05/2020 23:11:10) -> status 0
| B188532 | 14/04/2020 11:44:24 | 15/05/2020 23:11:10 | 0 |
| B188532 | 11/04/2020 08:34:10 | 13/05/2020 11:44:41 | 0 |
+--------------+-----------------------+---------------------+----------+
select x.ref_num, x.entry_date, x.change_status_date, x.status from kl_table x
Thank you for your support and advice
This is how I understood the question.
Sample data:
SQL> with test (ref_num, entry_date, change_status_date, status) as
2 (select 'x3', to_date('28.04.2020 16:54', 'dd.mm.yyyy hh24:mi'), null , 1 from dual union all
3 select 'x3', to_date('25.04.2020 13:14', 'dd.mm.yyyy hh24:mi'), to_date('27.04.2020 23:44', 'dd.mm.yyyy hh24:mi'), 0 from dual union all
4 select 'x3', to_date('20.04.2020 11:15', 'dd.mm.yyyy hh24:mi'), to_date('20.04.2020 23:52', 'dd.mm.yyyy hh24:mi'), 0 from dual union all
5 --
6 select 'b1', to_date('29.04.2020 11:34', 'dd.mm.yyyy hh24:mi'), null , 1 from dual union all
7 select 'b1', to_date('14.04.2020 11:44', 'dd.mm.yyyy hh24:mi'), to_date('15.05.2020 23:11', 'dd.mm.yyyy hh24:mi'), 0 from dual union all
8 select 'b1', to_date('11.04.2020 08:34', 'dd.mm.yyyy hh24:mi'), to_date('13.05.2020 11:44', 'dd.mm.yyyy hh24:mi'), 0 from dual
9 ),
Max change_status_date for that ref_num whose status = 0; it'll be compared to entry_date
10 temp as
11 (select
12 a.ref_num,
13 a.entry_date,
14 a.change_status_date,
15 --
16 (select max(b.change_status_date)
17 from test b
18 where b.ref_num = a.ref_num
19 and b.status = 0
20 ) compare_change_status_date,
21 a.status
22 from test a
23 )
Finally: I presume that change_status_date (that was NULL) should be replaced by sysdate. Difference between those dates should be ABS to eliminate negative numbers.
24 select
25 t.ref_num,
26 t.entry_date,
27 --
28 nvl(t.change_status_date, sysdate) change_status_date,
29 --
30 case when t.status = 1 then
31 case when abs(t.entry_date - t.compare_change_status_date) < 2 then 2
32 when abs(t.entry_date - t.compare_change_status_date) > 3 then 0
33 end
34 else t.status
35 end status
36 from temp t
37 order by t.ref_num desc, t.entry_date desc;
RE ENTRY_DATE CHANGE_STATUS_DA STATUS
-- ---------------- ---------------- ----------
x3 28.04.2020 16:54 28.07.2020 08:21 2
x3 25.04.2020 13:14 27.04.2020 23:44 0
x3 20.04.2020 11:15 20.04.2020 23:52 0
b1 29.04.2020 11:34 28.07.2020 08:21 0
b1 14.04.2020 11:44 15.05.2020 23:11 0
b1 11.04.2020 08:34 13.05.2020 11:44 0
6 rows selected.
SQL>
If you want to update rows whose status = 1, code I posted above can be reused for e.g. MERGE:
merge into test a
using (with temp
as (select a.ref_num,
a.entry_date,
a.change_status_date,
--
(select max (b.change_status_date)
from test b
where b.ref_num = a.ref_num
and b.status = 0)
compare_change_status_date,
a.status
from test a)
select t.ref_num,
t.entry_date,
--
nvl (t.change_status_date, sysdate) change_status_date,
--
case
when t.status = 1
then
case
when abs (
t.entry_date - t.compare_change_status_date) <
2
then
2
when abs (
t.entry_date
- t.compare_change_status_date) > 3
then
0
end
else
t.status
end
status
from temp t) x
on ( a.ref_num = x.ref_num
and a.entry_date = x.entry_date)
when matched
then
update set a.status = x.status
where a.status = 1;
Your solution will look like this.
update [tablename] set status=2 where DATEDIFF(day, [tablename].entry_date, [tablename].change_status_date) < 2
update [tablename] set status=0 where DATEDIFF(day, [tablename].entry_date, [tablename].change_status_date) > 3
Thanks

Oracle : SQL Request with a Group By and a Percentage on two differents tables

I'm currently blocked on an complex request... (with a join) :
I have this table "DATA":
order | product
----------------
1 | A
1 | B
2 | A
2 | D
3 | A
3 | C
4 | A
4 | B
5 | Y
5 | Z
6 | W
6 | A
And this table "DICO":
order | couple | first | second
-------------------------------
1 | A-B | A | B
2 | A-D | A | D
3 | A-C | A | C
4 | A-B | A | B
5 | Y-Z | Y | Z
6 | W-A | W | A
I would like to obtain, on one line :
order | count | total1stElem | %1stElem | total2ndElem | %1ndElem
------------------------------------------------------------------
A-B | 2 | 5 | 40% | 2 | 100%
A-D | 1 | 5 | 20% | 1 | 100%
A-C | 1 | 5 | 20% | 1 | 100%
Y-Z | 1 | 1 | 100% | 1 | 100%
W-A | 1 | 1 | 100% | 5 | 20%
I'm totally blocked on the jointure part of my request. Somebody can help me ?
Without any joins - just using UNPIVOT and PIVOT:
Oracle Setup:
CREATE TABLE DICO ( "order", couple, first, second ) AS
SELECT 1, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 2, 'A-D', 'A', 'D' FROM DUAL UNION ALL
SELECT 3, 'A-C', 'A', 'C' FROM DUAL UNION ALL
SELECT 4, 'A-B', 'A', 'B' FROM DUAL UNION ALL
SELECT 5, 'Y-Z', 'Y', 'Z' FROM DUAL UNION ALL
SELECT 6, 'W-A', 'W', 'A' FROM DUAL;
Query:
SELECT "order",
"count",
"1stElem_TOTAL" AS Total1stElem,
100*"count"/"1stElem_TOTAL" AS "%1stElem",
"2ndElem_TOTAL" AS Total2ndElem,
100*"count"/"2ndElem_TOTAL" AS "%2ndElem"
FROM (
SELECT couple AS "order",
key,
COUNT(*) OVER ( PARTITION BY COUPLE )/2 AS "count",
COUNT(*) OVER ( PARTITION BY VALUE ) AS num_value
FROM DICO
UNPIVOT ( Value FOR Key IN ( first AS 1, second AS 2 ) )
)
PIVOT ( MAX( NUM_VALUE ) AS Total FOR key IN ( 1 AS "1stElem", 2 AS "2ndElem" ) );
Results:
order count TOTAL1STELEM %1stElem TOTAL2NDELEM %2ndElem
----- ----- ------------ -------- ------------ --------
A-D 1 5 20 1 100
A-B 2 5 40 2 100
A-C 1 5 20 1 100
Y-Z 1 1 100 1 100
W-A 1 1 100 5 20

Get first and last of serial group in oracle

I'm trying to select from table (sorted):
+--------+-------+
| Serial | Group |
+--------+-------+
| 0100 | 99 |
| 0101 | 99 |
| 0102 | 99 |
| 096 | 92 |
| 097 | 92 |
| 099 | 93 |
| 23 | 16 |
| 95 | 87 |
| 99 | 90 |
| 100 | 90 |
| 101 | 90 |
| 102 | 90 |
| a | a |
| b | b |
| c | c |
+--------+-------+
and I would like table (first, last and quantity by group):fsdfsdfsdfdsfsdf
+------------+----------+----------+
| fromSerial | toSerial | quantity |
+------------+----------+----------+
| 0100 | 0102 | 3 |
| 096 | 097 | 2 |
| 099 | 099 | 1 |
| 99 | 102 | 4 |
| 23 | 23 | 1 |
| 95 | 95 | 1 |
| a | a | 1 |
| b | b | 1 |
| c | c | 1 |
+------------+----------+----------+
My query
Thanks.
you can use window analytic function row_number to partition the data based on the group column
you can also get number of elements in each partition
you can then do case based aggregation to get the from and to serial number values.
SQL Fiddle Demo
with cte
as
(
select "Serial", "Group", row_number() over ( partition by "Group" order by "Serial" ) as rn,
count(*) over ( partition by "Group") as cnt
from Table1
)
select max(case when rn =1 then "Serial" end) as "FromSerial",
max(case when rn =cnt then "Serial" end) as "ToSerial",
max(cnt) as quantity
from cte
group by "Group"
Use MIN, MAX and GROUP BY.
And, DO NOT use keyword GROUP as column name.
SQL> WITH DATA AS(
2 SELECT '0100' Serial, '99' "GROUP_1" FROM dual UNION ALL
3 SELECT '0101' , '99' FROM dual UNION ALL
4 SELECT '0102' , '99' FROM dual UNION ALL
5 SELECT '096' , '92' FROM dual UNION ALL
6 SELECT '097' , '92' FROM dual UNION ALL
7 SELECT '099', '93' FROM dual UNION ALL
8 SELECT '23' , '16' FROM dual UNION ALL
9 SELECT '95' , '87' FROM dual UNION ALL
10 SELECT '99' , '90' FROM dual UNION ALL
11 SELECT '100' , '90' FROM dual UNION ALL
12 SELECT '101' , '90' FROM dual UNION ALL
13 SELECT '102' , '90' FROM dual UNION ALL
14 SELECT 'A' , 'A' FROM dual UNION ALL
15 SELECT 'b' , 'b' FROM dual UNION ALL
16 SELECT 'c' , 'c' FROM dual
17 )
18 SELECT MIN(serial) fromserial,
19 MAX(Serial) toserial,
20 COUNT(*) quantity
21 FROM DATA
22 GROUP BY group_1
23 ORDER BY fromserial
24 /
FROM TOSE QUANTITY
---- ---- ----------
0100 0102 3
096 097 2
099 099 1
100 99 4
23 23 1
95 95 1
A A 1
b b 1
c c 1
9 rows selected.
SQL>
Try this query :
SELECT grp,
Cast(Min(Cast(serial AS INT)) AS VARCHAR2(30)) fromserial,
Cast(Max(Cast(serial AS INT)) AS VARCHAR2(30)) toserial,
Count(*) quantity
FROM yourtable
WHERE NVL(LENGTH(TRIM(TRANSLATE(serial, '0123456789', ' '))), 0) = 0
GROUP BY grp
UNION
SELECT grp,
Cast(Min(serial) AS VARCHAR2(30)) fromserial,
Cast(Max(serial) AS VARCHAR2(30)) toserial,
Count(*) quantity
FROM yourtable
WHERE NVL(LENGTH(TRIM(TRANSLATE(serial, '0123456789', ' '))), 0) != 0
GROUP BY grp
ORDER BY grp
Sqlfiddle

Count Of Distinct Records In SQL Where a Distinct Record does not exist [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I have data that looks like this:
HOUR COUNT
0 3
1 3
2 6
4 2
5 1
7 4
8 3
9 6
10 11
...
23 3
I need the data to look like this:
HOUR COUNT
0 3
1 3
2 6
3 0
4 2
5 1
6 0
7 4
8 3
9 6
10 11
...
23 3
When there is no distinct record of anything happening at a certain hour (ie hour 2 or 6) I want SQL to insert that hour with a count of 0.
Here is what my code looks like:
-- Columns selected
SELECT DISTINCT col1 AS 'HOUR', COUNT(col1) AS 'COUNT'
-- Database Used: DB
FROM DB
-- Filters
WHERE date_col BETWEEN '1/1/13' AND '1/2/13'
GROUP BY col1
ORDER BY col1
--*********************************************************************************
-- End Report
If you don't have a table of hours, then you can use the query below. Depending on your platform, you may be able to use a function in place of the CASE.
SELECT a.hour,
CASE
WHEN b.count IS NULL THEN 0
ELSE b.count
END AS 'count'
FROM (SELECT 1 AS 'hour'
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
UNION ALL
SELECT 10
UNION ALL
SELECT 11
UNION ALL
SELECT 12
UNION ALL
SELECT 13
UNION ALL
SELECT 14
UNION ALL
SELECT 15
UNION ALL
SELECT 16
UNION ALL
SELECT 17
UNION ALL
SELECT 18
UNION ALL
SELECT 19
UNION ALL
SELECT 20
UNION ALL
SELECT 21
UNION ALL
SELECT 22
UNION ALL
SELECT 23) a
LEFT JOIN tbl b
ON b.hour = a.hour
Result
| HOUR | COUNT |
----------------
| 1 | 3 |
| 2 | 6 |
| 3 | 0 |
| 4 | 2 |
| 5 | 1 |
| 6 | 0 |
| 7 | 4 |
| 8 | 3 |
| 9 | 6 |
| 10 | 11 |
| 11 | 10 |
| 12 | 11 |
| 13 | 5 |
| 14 | 7 |
| 15 | 10 |
| 16 | 7 |
| 17 | 6 |
| 18 | 8 |
| 19 | 2 |
| 20 | 7 |
| 21 | 5 |
| 22 | 6 |
| 23 | 3 |
You can use a common table expression to create all the hours:
WITH [Hours]([Hour])
AS
(
SELECT 0 AS [Hour]
UNION ALL
SELECT [Hours].[Hour] + 1 AS [Hour]
FROM [Hours]
WHERE [Hours].[Hour] < 23
)
SELECT * FROM [Hours]
Then you can use these hours to join on your query:
WITH [Hours]([Hour])
AS
(
SELECT 0 AS [Hour] --Don't forget 0, else you miss one hour on your day!
UNION ALL
SELECT [Hours].[Hour] + 1 AS [Hour]
FROM [Hours]
WHERE [Hours].[Hour] < 23
)
SELECT
[Hours].[Hour],
COUNT([table].[col1]) AS [Count]
FROM [Hours]
LEFT JOIN [table] ON [Hours].[Hour] = [table].[col1]
WHERE [table].[date_col] BETWEEN '1/1/13' AND '1/2/13'
ORDER BY [Hour] ASC
You need a case statement:
case count(col1) as 'Count'
when null
THEN 0
ELSE 'Count'
Simpler query to get 24 hours - this is only to build hours table if needed. Use of Sysdate is optional:
SELECT to_char(trunc(SYSDATE), 'hh24')-1 + LEVEL "24_hrs"
FROM dual
CONNECT BY LEVEL <= 24;