I am trying to run this query but I keep getting this error: ORA-00937: not a single-group group function 00937. 00000 - "not a single-group group function". How should I Group By in this case?
select trunc(dstamp) "DATE", COUNT(CODE), user_id, dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed,
ROUND(sum(update_qty / substr(sku_id, instr(sku_id, '-', 1, 1) +1 , instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)))) "CASES_PICKED",
from v_inventory_transaction
where client_id = 'USKIDS2CA'
and code = 'Pick'
and list_id IS NOT NULL
and STATION_ID LIKE 'R%'
and reference_id not like '%-FK%'
AND trunc(dstamp) = '03/23/2022'
GROUP BY ***????***
Your problem is the usage of the analytic function lag in the GROUP BYstatement.
The correct approach is to first calculate the column with the lag
SELECT trunc(dstamp) "DATE", user_id,
dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed
FROM tab
and in the second step use it in some aggregate function - here example with max
WITH dt AS (
SELECT trunc(dstamp) "DATE", user_id,
dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed
FROM tab
)
SELECT "DATE", user_id, max(elapsed)
FROM dt
GROUP BY "DATE", user_id
If you remove all aggregations/calculations temporarily
, trunc(dstamp) - lag(trunc(dstamp)) OVER (
PARTITION BY user_id ORDER BY trunc(dstamp)
) AS elapsed
, COUNT(CODE)
, ROUND(sum(update_qty / ??? )) "CASES_PICKED"
it will help you focus on the underlying needs of the group by clause, which without those calculations is almost a replica of the select clause (but also without any column aliases).
SELECT
trunc(dstamp) "DATE"
, user_id
, substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) "SUBSTRING"
FROM v_inventory_transaction
WHERE client_id = 'USKIDS2CA'
AND code = 'Pick'
AND list_id IS NOT NULL
AND STATION_ID LIKE 'R%'
AND reference_id NOT LIKE '%-FK%'
AND trunc(dstamp) = '03/23/2022'
GROUP BY
trunc(dstamp)
, user_id
, substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1))
However there are at least these problems to overcome if we attempt to finish the query
it isn't clear (without sample data and expected result) to know if you need the "elapsed" calculation performed before the grouping or if it is sufficient to do this using the truncated date.
you cannot divide a number by a string eg 100/substr('bad',1,2) is bad and produces ORA-01722: invalid number as a result. You could try casting the substring to a numeric - but this could fail also if the string can be converted.
compare the truncated date to a date, not a string e.g. trunc(dstamp) = date '2022-03-23' or use to_date()
Suggested:
SELECT
trunc(dstamp) "DATE"
, user_id
, trunc(dstamp) - lag(trunc(dstamp)) OVER (
PARTITION BY user_id ORDER BY trunc(dstamp)
) AS elapsed
, COUNT(CODE)
, ROUND(sum(update_qty / cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer))) "CASES_PICKED"
FROM v_inventory_transaction
WHERE client_id = 'USKIDS2CA'
AND code = 'Pick'
AND list_id IS NOT NULL
AND STATION_ID LIKE 'R%'
AND reference_id NOT LIKE '%-FK%'
AND trunc(dstamp) = to_date('03/23/2022','mm/dd/yyyy')
GROUP BY
trunc(dstamp)
, user_id
, cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer)
note: to use the lag function you should refer to trunc(dstamp) throughout otherwise you would need to group by the un-truncated dstamp
see: db<>fiddle here
Related
I have the following recursive CTE which splits each element coming from base per month:
with
base (id, start_date, end_date) as (
select 1, date '2022-01-15', date '2022-03-15' from dual
union
select 2, date '2022-09-15', date '2022-12-31' from dual
union
select 3, date '2023-09-15', date '2023-09-25' from dual
),
split (id, start_date, end_date) as (
select base.id, base.start_date, least(last_day(base.start_date), base.end_date) from base
union all
select base.id, split.end_date + 1, least(last_day(split.end_date + 1), base.end_date) from base join split on base.id = split.id and split.end_date < base.end_date
)
select * from split order by id, start_date, end_date;
It works on Oracle and gives the following result:
id
start_date
end_date
1
2022-01-15
2022-01-31
1
2022-02-01
2022-02-28
1
2022-03-01
2022-03-15
2
2022-09-15
2022-09-30
2
2022-10-01
2022-10-31
2
2022-11-01
2022-11-30
2
2022-12-01
2022-12-31
3
2023-09-15
2023-09-25
The two following stop conditions work correctly:
... from base join split on base.id = split.id and split.end_date < base.end_date
... from base, split where base.id = split.id and split.end_date < base.end_date
The following one fails with the message ORA-32044: cycle detected while executing recursive WITH query:
... from base join split on base.id = split.id where split.end_date < base.end_date
I fail to understand how the last one is different from the two others.
It looks like a bug as all your queries should result in identical explain plans.
However, you can rewrite the recursive sub-query without the join (and using a SEARCH clause so you may not have to re-order the query later):
WITH split (id, start_date, month_end, end_date) AS (
SELECT id,
start_date,
LEAST(
ADD_MONTHS(TRUNC(start_date, 'MM'), 1) - INTERVAL '1' SECOND,
end_date
),
end_date
FROM base
UNION ALL
SELECT id,
month_end + INTERVAL '1' SECOND,
LEAST(
ADD_MONTHS(month_end, 1),
end_date
),
end_date
FROM split
WHERE month_end < end_date
) SEARCH DEPTH FIRST BY id, start_date SET order_id
SELECT id,
start_date,
month_end AS end_date
FROM split;
Note: if you want to just use values at midnight rather than the entire month then use INTERVAL '1' DAY rather than 1 second.
Which, for the sample data:
CREATE TABLE base (id, start_date, end_date) as
select 1, date '2022-01-15', date '2022-04-15' from dual union all
select 2, date '2022-09-15', date '2022-12-31' from dual union all
select 3, date '2023-09-15', date '2023-09-25' from dual;
Outputs:
ID
START_DATE
END_DATE
1
2022-01-15T00:00:00Z
2022-01-31T23:59:59Z
1
2022-02-01T00:00:00Z
2022-02-28T23:59:59Z
1
2022-03-01T00:00:00Z
2022-03-31T23:59:59Z
1
2022-04-01T00:00:00Z
2022-04-15T00:00:00Z
2
2022-09-15T00:00:00Z
2022-09-30T23:59:59Z
2
2022-10-01T00:00:00Z
2022-10-31T23:59:59Z
2
2022-11-01T00:00:00Z
2022-11-30T23:59:59Z
2
2022-12-01T00:00:00Z
2022-12-31T00:00:00Z
3
2023-09-15T00:00:00Z
2023-09-25T00:00:00Z
fiddle
It's because WHERE and ON conditions are not evaluated at the same level:
when the condition is in the ON clause it's limiting the rows concerned by the JOIN, where it's in the WHERE it's filtering the results after the JOIN has been applied, and since a recursive CTE see all rows selected up to now...
I have the following query but I need a particular format (HH24,MI,SS) for the results that I get in column "elapsed". Can I do that with a subquery?
SELECT
trunc(dstamp) "DATE",
to_char(dstamp, 'HH24:MI:SS') "Time"
, user_id
, dstamp - lag(dstamp) OVER (
PARTITION BY user_id ORDER BY dstamp
) AS elapsed
, COUNT(CODE) "Lines_Picked"
, ROUND(sum(update_qty / cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer))) "CASES_PICKED"
FROM v_inventory_transaction
WHERE client_id = 'USKIDS2CA'
AND code = 'Pick'
AND list_id IS NOT NULL
AND STATION_ID LIKE 'R%'
AND reference_id NOT LIKE '%-FK%'
AND trunc(dstamp) = to_date('03/23/2022','mm/dd/yyyy')
GROUP BY
user_id,
dstamp
, cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer)
What is "timestamp" from the title? Which datatype is it? DATE or TIMESTAMP?
If it is TIMESTAMP, extract helps a lot:
SQL> with temp (user_id, dstamp) as
2 (select 1, to_timestamp('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_timestamp('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_timestamp('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 to_char(extract (hour from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
10 to_char(extract (minute from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
11 to_char(extract (second from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') result
12 from temp
13 order by user_id, dstamp;
USER_ID DSTAMP RESULT
---------- ----------------------------------- -----------
1 27-MAR-22 02.28.00.000000000 AM ::
1 27-MAR-22 05.30.00.000000000 AM 03:02:00
1 27-MAR-22 08.08.00.000000000 AM 02:38:00
SQL>
If it is DATE, then ELAPSED currently represents number of days between dstamp and its previous row's value per user_id. As it seems that that number is always less than 1 day (as you want result in hh24:mi:ss format), a simple option is to apply combination of
to_char(to_date(elapsed, 'sssss'), 'hh24:mi:ss')
and get the result. Step-by-step (so that you'd see which result you get for each step):
SQL> with temp (user_id, dstamp) as
2 (select 1, to_date('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_date('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_date('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed_days,
10 --
11 round((dstamp - lag(dstamp) over (partition by user_id order by dstamp)) *
12 24 * 60 * 60) elapsed_seconds,
13 --
14 to_char(to_date(round((dstamp - lag(dstamp) over (partition by user_id
15 order by dstamp)) *
16 24 * 60 * 60), 'sssss'),
17 'hh24:mi:ss') result
18 from temp
19 order by user_id, dstamp;
USER_ID DSTAMP ELAPSED_DAYS ELAPSED_SECONDS RESULT
---------- ------------------- ------------ --------------- --------
1 27.03.2022 02:28:00
1 27.03.2022 05:30:00 .126388889 10920 03:02:00
1 27.03.2022 08:08:00 .109722222 9480 02:38:00
SQL>
However, if elapsed can be larger than 1 day so that it exceeds number of seconds in a day (that's 24 * 60 * 60 = 86400), that won't work so you'll have to actually calculate the result. However, query then becomes really ugly so you'd rather use another CTE (or a subquery) to first extract elapsed and work with it, instead of using dstamp - lag(...) all the time.
Therefore, I hope it is the timestamp after all.
I have following table:
CREATE TABLE logins (
type_id BIGINT NOT NULL,
created_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT now() NOT NULL);
INSERT INTO logins VALUES
(5, '1/26/2018 5:00:00 PM'),
(5 ,'1/26/2018 3:45:44 PM'),
(5, '1/26/2018 3:45:44 PM')
When I run this code separately:
SELECT l.type_id , l.created_date FROM logins l
WHERE
l.type_id = 5 AND
l.created_date BETWEEN ((CAST('1/26/2018' AS DATE) - 1) + TIME ' 17:00:00') AND
(CAST('1/26/2018' AS DATE) + TIME ' 17:00:00')
ORDER BY l.created_date DESC
It orders the dates the way I want but when I run following code:
WITH results as (SELECT l.type_id , l.created_date FROM logins l
WHERE
l.type_id = 5 AND
l.created_date BETWEEN ((CAST('1/26/2018' AS DATE) - 1) + TIME ' 17:00:00') AND
(CAST('1/26/2018' AS DATE) + TIME ' 17:00:00')
ORDER BY l.created_date DESC)
SELECT * FROM results
UNION
SELECT 0, NULL
WHERE NOT EXISTS (SELECT * FROM results )
The order by doesn't work at all. I'd like to know the reason.
Put the order by at the end of the sql. Also, UNION removes duplicates while UNION ALL do not remove duplicates. Try this and you will get the same result as previous. Thanks.
WITH results as (SELECT l.type_id , l.created_date FROM logins l
WHERE
l.type_id = 5 AND
l.created_date BETWEEN ((CAST('1/26/2018' AS DATE) - 1) + TIME ' 17:00:00') AND
(CAST('1/26/2018' AS DATE) + TIME ' 17:00:00')
)
SELECT * FROM results
UNION
SELECT 0, NULL
WHERE NOT EXISTS (SELECT * FROM results )
ORDER BY created_date DESC
I have a table with 3 columns:
resid type date
The table is used to track steps in a workflow and a specific resid can exist multiple with different type id(numbers) and datestamps.
I want to calculate the time used between two typeshift - i.e, 1 and 17 on a specific resid
I have tried with a sql-plus syntax like this
and also tried to use aliases:
Any suggestions?
select resid, date - date
from tablename
where resid, date in
(select resid, date from tablename
where type='1')
and
where resid, date in
(select resid, date from tablename
where type='17')
and tablename.resid=tablename.resid
Your attempted query is missing parentheses around the column list before the in - so should be where (resid, date in) - but also has and where which isn't valid, and probably other issues. Mostly it doesn't do what you want, not least because both date values are coming from the same row (for type 1) so subtracting them will always give zero.
You could use conditional aggregation:
select resid,
min(case when type_id = 17 then date_stamp end)
- min(case when type_id = 1 then date_stamp end) as diff
from tablename
where type_id in (1, 17) -- optional
and resid = :some_value
group by resid;
The case gives either null or the date stamp for each matching row; the aggregation then gives you a single value from those (favouring not-null ones).
If only one of the type IDs exists then the difference will be null.
You might want to change the min() for 17 to max() if there may be multiples - depends what you really need.
Quick demo:
with tablename(resid, type_id, date_stamp) as (
select 1, 1, sysdate - 10 from dual
union all select 1, 17, sysdate - 7 from dual
union all select 2, 1, sysdate - 5 from dual
union all select 2, 17, sysdate - 3 from dual
union all select 3, 1, sysdate - 10 from dual
)
select resid,
min(case when type_id = 17 then date_stamp end)
- min(case when type_id = 1 then date_stamp end) as diff
from tablename
where type_id in (1, 17) -- optional
--and resid = 2
group by resid;
RESID DIFF
---------- ----------
1 3
2 2
3
SELECT a.resid, a."type" type1, a."date" date1, b."type" type17, b."date" date17, b."date" - a."date" AS date_diff
FROM tablename a JOIN tablename b ON a.resid = b.resid AND b."type" = '17'
WHERE a."type" = '1' AND a.resid = :resid
Please do not use oracle reserved words as column names.
When (resid, type) is unique you can do:
SELECT :resid resid,
(select "date" FROM tablename WHERE resid = :resid AND "type" = '17') -
(select "date" FROM tablename WHERE resid = :resid AND "type" = '1') date_diff
FROM DUAL
I'm creating a table to populate a weekly calendar table and my requirement is to update wk_start_date as every week sunday and populate the pr_wk_flag as Y every two weeks and
beg dt & end dt would be from two weeks before Thursday to current Wednesday
Like below, How to populate in oracle plsql
Wk_start_date pr_wk_flag beg dt end dt
2/9/2014 Y 1/30/2014 2/12/2014
2/16/2014 N NULL NULL
2/23/2014 Y 2/13/2014 2/26/2014
3/2/2014 N NULL NULL
3/9/2014 Y 2/27/2014 3/12/2014
Here's one way:
select wk_start_dt, pr_wk_flag,
case when pr_wk_flag = 'Y' then wk_start_dt - 10 end as beg_dt,
case when pr_wk_flag = 'Y' then wk_start_dt + 3 end as end_dt
from (
select next_day(date '2014-02-01' + (7 * level), 'SUN') as wk_start_dt,
decode(mod(level, 2), 1, 'Y', 'N') as pr_wk_flag
from dual
connect by level <= 6
);
The inner query is a common way to generate a sequence of values. Adjust the start date and the number of iterations to change the range of week-starts generated, and if necessary to flip the flag value. The SUN argument to the next_day function has to be in your local session language, so change that to something suitable if you're not using English. As a one-off insert I don't think the NLS-sensitivity is an issue in this case.
The outer query just uses the flag to decide whether to calculate the other two dates, or leave them null.
SQL Fiddle demo.
There you have for all 2014 year
SELECT dates+(8- to_char(TRUNC (TRUNC (dates, 'w'), 'YEAR'),'d')) wk_start_date,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2), 0, 'Y', 'N')
pr_wk_flag,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2),
0, dates + 5 - 17,
NULL)
pr_wk_flag,
DECODE (MOD (TO_NUMBER (TO_CHAR (dates, 'w')), 2), 0, dates + 3, NULL)
end_dt
FROM (SELECT TRUNC (SYSDATE, 'YEAR') + 7 * (ROWNUM - 1) dates
FROM dba_tables
WHERE TRUNC (TRUNC (SYSDATE, 'YEAR') + 7 * (ROWNUM - 1), 'YEAR') =
TRUNC (SYSDATE, 'YEAR'))
ORDER BY wk_start_date ASC