Nested For Loops with Recursive Subquery - plsql

How can I run the below statement returning one or multiple rows for each employee in a different table and each date in a week or other range?
DECLARE
CURSOR emps_in IS
SELECT EMPLOYEE_ID FROM EMPLOYEES;
CURSOR days_in IS
SELECT TO_CHAR(DAYS, 'YYYYMMDD') FROM WEEKS WHERE WEEK_NUM=1;
BEGIN
FOR e IN emps_in LOOP
FOR d IN days_in LOOP
INSERT INTO acc_out_time (ROW_NUM, ENTRY_ID, ENTRY_DATE, ENTRY_TIME, TIME, PIN, DEVICE_ID, STATE, EVENT_POINT_NAME, DUPLICATE)
WITH BDEV_RPT2 AS
(SELECT *
FROM (SELECT ROW_NUMBER()OVER(ORDER BY TIME DESC) ROW_NUM, a.* FROM acc_time_logs a WHERE PIN=e AND ENTRY_DATE=d AND STATE IN ('1','2') AND DEVICE_ID IN ('18','10','6','9') ORDER BY TIME DESC)
WHERE ROW_NUM IN ('1','2') AND DEVICE_ID='18' OR (ROW_NUM='1' AND DEVICE_ID IN ('10','6','9') AND NOT EXISTS(SELECT NULL FROM (SELECT ROW_NUMBER()OVER(ORDER BY TIME DESC) ROW_NUM, a.* FROM acc_time_logs a WHERE PIN=e AND ENTRY_DATE=d AND STATE IN ('1','2') AND DEVICE_ID IN ('18','10','6','9') ORDER BY TIME DESC) WHERE ROW_NUM IN ('1','2') AND DEVICE_ID='18')))
SELECT ROW_NUMBER()OVER(ORDER BY TIME ASC) ROW_NUM, ENTRY_ID, ENTRY_DATE, ENTRY_TIME, TIME, PIN, DEVICE_ID, STATE, EVENT_POINT_NAME, DUPLICATE FROM BDEV_RPT2;
END LOOP;
END LOOP;
COMMIT;
END;

Related

Teradata macro with volatile table and CTE to insert data into a table

I need to create a teradata macro to extract information into a volatile table first, then do CTE to extract data from this volatile table and insert into a teradata table, tried different ways all fail, appreciate help!
CREATE MACRO database.macro_insertion_tablename AS (
CREATE VOLATILE TABLE vt AS
(
SELECT
id, bu,
CONCAT(TO_CHAR(comment_date, 'yyyy-mm-dd HH24:MI:SS'), ' ', action) AS full_action,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY date DESC) AS row_num,
COUNT(*) OVER (PARTITION BY id) as cnt
FROM database.table1
) WITH DATA UNIQUE PRIMARY INDEX(id, row_num) ON COMMIT PRESERVE ROWS;
WITH RECURSIVE cte (id, bu, act, rn) AS
(
SELECT
id, bu
,CAST(full_action AS VARCHAR(5000)) AS full_action
,row_num
FROM vt
WHERE row_num = cnt
UNION ALL
SELECT
vt.id, vt.bu
,cte.act || ' / ' || vt.full_action
,vt.row_num
FROM vt
JOIN cte On vt.id = cte.id AND vt.row_num = cte.rn - 1
)
INSERT INTO database.table (id, bu, full_action)
SELECT id, bu, act
FROM cte
WHERE rn = 1;
DROP TABLE vt;
);
DDL must be the only statement in a Teradata Macro.
As workaround you could switch to a Global Temporary Table which is defined once and then you simply Insert/Select into it instead of CREATE VOLATILE TABLE.
But in your case there's no need for a temp table plus inefficient recursive processing to get a "group concat":
SELECT id, max(bu) -- maybe min(bu)?
XmlAgg(Concat(To_Char(comment_date, 'yyyy-mm-dd HH24:MI:SS'), ' ', action)
ORDER BY comment_date) (VARCHAR(5000)) AS full_action
FROM database.table1
GROUP BY 1
will give you a similar result.
To follow up on my comments, you should be able to define multiple CTEs in the same statement. It may be tricky getting the RECURSIVE CTE to work, but it sounds like it's possible. Maybe something like this:
CREATE MACRO database.macro_insertion_tablename AS (
WITH vt (id, bu, full_action, row_num, cnt) AS
(
SELECT
id, bu,
CONCAT(TO_CHAR(comment_date, 'yyyy-mm-dd HH24:MI:SS'), ' ', action) AS full_action,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY date DESC) AS row_num,
COUNT(*) OVER (PARTITION BY id) as cnt
FROM database.table1
),
RECURSIVE cte (id, bu, act, rn) AS
(
SELECT
id, bu
,CAST(full_action AS VARCHAR(5000)) AS full_action
,row_num
FROM vt
WHERE row_num = cnt
UNION ALL
SELECT
vt.id, vt.bu
,cte.act || ' / ' || vt.full_action
,vt.row_num
FROM vt
JOIN cte On vt.id = cte.id AND vt.row_num = cte.rn - 1
)
INSERT INTO database.table (id, bu, full_action)
SELECT id, bu, act
FROM cte
WHERE rn = 1;
);
I don't have a Teradata system to test with, so not 100% it will work as-is, but give it a try. You may need to change RECURSIVE to WITH RECURSIVE and also the ordering of the CTE queries (i.e. put the RECURSIVE one first). Take a look at these two links:
Teradata Forum - Multiple With Clause
teradata Forum - Common Table Expressions

Is there any way to accomplish this in IBM DB2 enviroment

In DB2 is there a way to basically say:
case when sku (select * from table1 where tb1field = 'SMOMD') then 'True' end
Okay so this is my query so far, I've been going at this for at least a month now so any help would be great.
select tb4.customer, tb4.sku, tb4.qty, tb4.retqty, tb4.stipqty, tb4.lastdate, tb4.firstdate, tb4.stipdate
from(
--Table 4
select tb3.Customer as Customer, tb3.sku as SKU, tb3.qty as Qty, tb3.retqty as RetQty, tb3.stipqty as STIPQty,
case when tb3.lastdate is null then '00/0000' else substr(tb3.lastdate,5,2)||'/'||substr(tb3.lastdate,1,4) end as LastDate,
case when tb3.firstdate is null then '00/0000' else substr(tb3.firstdate,5,2)||'/'||substr(tb3.firstdate,1,4) end as FirstDate,
case when tb3.stipdate is null then '00/0000' else substr(tb3.stipdate,5,2)||'/'||substr(tb3.stipdate,1,4) end as STIPDate
from(
--Table 3
select tb2.Customer as Customer, tb2.SKU as SKU, tb2.Qty as Qty, tb2.RetQty as RetQty, tb2.STIPQty as STIPQty,
max(case when tb2.TranID in ('010','100') then tb2.datenum end) as LastDate,
min(case when tb2.TranID in ('010','100') then tb2.datenum end) as FirstDate,
case when tb2.RC = '4M' then tb2.datenum end as STIPDate
from(
--Table 2
select tb1.Customer as Customer, tb1.SKU as SKU,
sum(case when tb1.TranID in ('010','100') then abs(tb1.OrdNet) else '0' end) as Qty,
sum(case when tb1.TranID = '500' and tb1.rc != '4M' then abs(tb1.OrdNet) else '0' end) as RetQty,
count(case when tb1.rc = '4M' then tb1.sku end) as STIPQty,
tb1.datenum as datenum, tb1.TranID as tranid, tb1.RC as rc
from(
--Table 1
select distinct stkund as Customer, sthptg||space(1)||stmodl||space(1)||stvari||space(1)||stfarb||space(1)||stgroe as SKU,
stvorg as TranID, stggru as RC, stprg09 as PG9, stprg08 as PG8, stperi as datenum, ormne1 as OrdNet
from st_usus.s_stati_pv
join caspdtau.cospf440 on stadrn = jadr40
where trim(stvert) in ('111S','122S')
and sthptg != 'V'
and aktv40 = 'A'
and stprg01 in ('01','04')
and stprg02 = '01'
and stvorg in ('500','010','100')
and stperi >= '20160100'
) as tb1
group by tb1.Customer, tb1.SKU, tb1.datenum, tb1.tranid, tb1.rc
) as tb2
group by tb2.customer, tb2.sku, tb2.qty, tb2.retqty, tb2.stipqty, tb2.tranid, tb2.rc, tb2.datenum
) as tb3
group by tb3.customer, tb3.sku, tb3.qty, tb3.retqty, tb3.stipqty, tb3.lastdate, tb3.firstdate, tb3.stipdate
) as tb4
order by tb4.Customer, tb4.sku
I'm not going to try to decipher exactly what you're trying to do...
Some general advice, rather than using Nested Table Expressions (NTE)
select <..> from (select <...>from mytable)
Consider Common Table Expressions (CTE)
with
table1 as (select <...> from st_usus.s_stati_pv join caspdtau.cospf440 on stadrn = jadr40)
, table2 as (select <...> from table1)
, table3 as (select <...> from table2)
, table4 as (select <....> from table3)
select <...> from table4;
Each CTE (ie. tableX) can refer to a prior CTE or a physical table/view as needed. The final select can refer to one or more CTE's along with one or more physical tables or views.
Nice thing about building with CTE's, is that you can check your results after each step..
with
table1 as (select <...> from st_usus.s_stati_pv join caspdtau.cospf440 on stadrn = jadr40)
select * from table1;

SQL Server Multiple count dates subselect in same query

I'm trying to figure out a way to pull Order Counts per customer id as well as date of first and last order within a date range from an Orders table where each order has both a buyer_id and seller_id. The Orders table contains OrderNumber, Buyer_ID, Seller_ID, OpenDate, ClosedDate. I can run the following queries individually to achieve my goals, but I would like to have everything in the same query if possible.
Order_Table:
OrderNumber, Buyer_ID, Seller_ID, OpenDate, ClosedDate
Buyer_ID Orders:
select Buyer_ID, COUNT(*)as BuyerOrders
from
(
select Buyer_ID
from Orders
where OpenDate between #StartDate and #EndDate
)
a
group by Buyer_ID
Seller_ID Orders:
select Seller_ID, COUNT(*)as SellerOrders
from
(
select Seller_ID
from Orders
where OpenDate between #StartDate and #EndDate
)
a
group by Seller_ID
Dates of First and Last Order within that range: ??
Any input is greatly appreciated!
Since the result is a union and the same customer_id may have an entry as both buyer and seller, how can I put the information in the same row? My first attempt was to create a temporary table from the result of the Union, but I'm drawing a blank on how to display Buyer OrderCount, Seller OrderCount etc on the same row for each Customer_ID in the resulting table.
select 'Buyer' as Type,
Buyer_ID ID,
Count(*) OrderCount,
Min(OpenDate) FirstOrder,
Max(OpenDate) LastOrder
from Orders
where OpenDate between #StartDate and #EndDate
group by Buyer_ID
union
select 'Seller',
Seller_ID,
Count(*),
Min(OpenDate) FirstOrder,
Max(OpenDate) LastOrder
from Orders
where OpenDate between #StartDate and #EndDate
group by Seller_ID
[EDIT] Yes, a little bit of a cheek accepting my answer, then un-accepting it and changing the question! Anyway, try the following:
;with BuyerFirst (Buyer_ID, RowNum, BuyerCount, OrderID, OpenDate)
As
(select Buyer_ID,
ROW_NUMBER() over (partition by Buyer_ID order by OpenDate, OrderID) as RowNum,
count(*) over (partition by Buyer_ID) As BuyerCount,
OrderID,
OpenDate
from Orders
where OpenDate between #StartDate and #EndDate),
BuyerLast (Buyer_ID, RowNum, OrderID, OpenDate)
As
(select Buyer_ID,
ROW_NUMBER() over (partition by Buyer_ID order by OpenDate Desc, OrderID Desc) as RowNum,
OrderID,
OpenDate
from Orders
where OpenDate between #StartDate and #EndDate),
SellerFirst (Seller_ID, RowNum, SellerCount, OrderID, OpenDate)
As
(select Seller_ID,
ROW_NUMBER() over (partition by Seller_ID order by OpenDate, OrderID) as RowNum,
count(*) over (partition by Buyer_ID) As SellerCount,
OrderID,
OpenDate
from Orders
where OpenDate between #StartDate and #EndDate),
SellerLast (Seller_ID, RowNum, OrderID, OpenDate)
As
(select Seller_ID,
ROW_NUMBER() over (partition by Seller_ID order by OpenDate Desc, OrderID Desc) as RowNum,
OrderID,
OpenDate
from Orders
where OpenDate between #StartDate and #EndDate)
select c.*,
bf.BuyerCount,
bf.OpenDate As BuyerFirstOrderDate,
bf.OrderID As BuyerFirstOrderID,
bl.OpenDate As BuyerLastOrderDate,
bl.OrderID As BuyerLastOrderID,
sf.SellerCount,
sf.OpenDate As SellerFirstOrderDate,
sf.OrderID As SellerFirstOrderID,
sl.OpenDate As SellerLastOrderDate,
sl.OrderID As SellerLastOrderID
from Customers c
left join BuyerFirst bf on c.CustomerID = bf.Buyer_ID and bf.RowNum = 1
left join SellerFirst sf on c.CustomerID = sf.Seller_ID and sf.RowNum = 1
left join BuyerLast bl on c.CustomerID = bl.Buyer_ID and bl.RowNum = 1
left join SellerLast sl on c.CustomerID = sl.Seller_ID and sl.RowNum = 1

PL/SQL: Statement ignored

create or replace trigger perform_validations
after insert or update on xx_hr_employee
for each row
begin
validation;
END;
/
and my procedure validation is:
declare
E_ID xx_hr_employee.emp_id%type;
E_NAME xx_hr_employee.emp_name%type;
D_ID xx_hr_employee.dept_id%type;
D_NAME xx_hr_employee.dept_name%type;
S_ID xx_hr_employee.supervisor_id%type;
S_NAME xx_hr_employee.supervisor_name%type;
P_ID xx_hr_employee.project_id%type;
P_NAME xx_hr_employee.project_name%type;
SAL xx_hr_employee.salary%type;
A xx_hr_employee.age%type;
l_count number;
e_count number;
procedure validation
is
cursor my_cursor is
select EMP_ID, EMP_NAME, DEPT_ID, DEPT_NAME, SUPERVISOR_ID, SUPERVISOR_NAME, PROJECT_ID, PROJECT_NAME, SALARY, AGE
from xx_hr_employee E;
begin
open my_cursor;
loop
fetch my_cursor into E_ID, E_NAME, D_ID, D_NAME, S_ID, S_NAME, P_ID, P_NAME, SAL, A;
exit when my_cursor%notfound;
if(E_ID = 0000) then
insert into xx_stg_hr_employee( EMP_ID, EMP_NAME, DEPT_ID, DEPT_NAME, SUPERVISOR_ID, SUPERVISOR_NAME, PROJECT_ID, PROJECT_NAME, SALARY, AGE)
select EMP_ID, EMP_NAME, DEPT_ID, DEPT_NAME, SUPERVISOR_ID, SUPERVISOR_NAME, PROJECT_ID, PROJECT_NAME, SALARY, AGE from xx_hr_employee
where emp_id <> 0000;
end if;
end loop;
close my_cursor;
delete from xx_stg_hr_employee WHERE (Emp_name like '% %');
UPDATE xx_stg_hr_employee SET mycol=seq_id3.NEXTVAL;
Select count(*) into l_count From xx_hr_employee
Group By EMP_ID, EMP_NAME, DEPT_ID, DEPT_NAME, SUPERVISOR_ID, SUPERVISOR_NAME, PROJECT_ID, PROJECT_NAME, SALARY, AGE
Having Count(*) > 1;
if(l_count <> 0) then
delete from xx_stg_hr_employee where mycol NOT IN (SELECT MIN(mycol)
FROM xx_stg_hr_employee GROUP BY EMP_ID, EMP_NAME, DEPT_ID, DEPT_NAME, SUPERVISOR_ID, SUPERVISOR_NAME, PROJECT_ID, PROJECT_NAME, SALARY, AGE);
end if;
end;
begin
validation;
end;
/
it is showing Error at line 2: PL/SQL: Statement ignored..
here validation is a procedure which should get called when any insert(or update) is performed on xx_hr_employee..
I don't know how to proceed further.
I am using oracle apex.
i've tested the following and it worked for me, so please see what you missed:
For example
Your table is like Create table TEST_TABLE (X Number);
Then your stored procedure is
CREATE OR REPLACE PROCEDURE VALIDATIONS IS
BEGIN
--Do your validation over here..
UPDATE TEST_TABLE SET X = 1;
END;
then your code should work as you written:
CREATE OR REPLACE TRIGGER PERFORM_VALIDATIONS
AFTER INSERT OR UPDATE ON TEST_TABLE
FOR EACH ROW
BEGIN
VALIDATIONS;
END;
/
Important: If you create the procedure in a package, then make sure that the Package head has the declaration, and then call it like YourPackageName.VALIDATIONS instead of VALIDATIONS only

Deleting Invalid Duplicate Rows in SQL

I have a table which stores the check-in times of employees through Time Machine on the basis of a username. If an employee punches multiple times then there would be multiple records of his check-ins which would only have a time difference of few seconds in between. Obviously only the first record is valid. All the other entries are invalid and must be deleted from the Table. How can i do it if i can select all the checkin records of an employee for the current date?
The Data in the db is as follows.
Username Checktime CheckType
HRA001 7/29/2012 8:16:44 AM Check-In
HRA001 7/29/2012 8:16:46 AM Check-In
HRA001 7/29/2012 8:16:50 AM Check-In
HRA001 7/29/2012 8:16:53 AM Check-In
Try this:
;WITH users_CTE as (
select rank() over (partition by Username order by Checktime) as rnk from users
)
DELETE FROM users_CTE where rnk <> 1
--For your second requirement try this query
;WITH users_CTE as (
select *,rank() over (partition by Username order by Checktime) as rnk from users
)
,CTE2 as (select Username,MIN(CheckTime) as minTime,DATEADD(mi,1,MIN(CheckTime)) as maxTime from users_CTE
group by Username)
delete from users where Checktime in(
select c1.Checktime from users_CTE c1 left join CTE2 c2
on c1.Checktime > c2.minTime and c1.Checktime <= c2.maxTime
where c2.Username is not null and c1.Username in(
select c1.Username from users_CTE c1 left join CTE2 c2
on c1.Checktime > c2.minTime and c1.Checktime <= c2.maxTime
group by c1.Username,c2.Username
having COUNT(*) > 1))
--For your changed requirements pls check this query below
alter table users add flag varchar(2)
;WITH users_CTE as (
select *,rank() over (partition by Username order by Checktime) as rnk from users
)
,CTE2 as (select Username,MIN(CheckTime) as minTime,DATEADD(mi,1,MIN(CheckTime)) as maxTime from users_CTE
group by Username)
update u SET u.flag = 'd' from users_CTE u inner join (
select c1.Checktime from users_CTE c1 left join CTE2 c2
on c1.Checktime > c2.minTime and c1.Checktime <= c2.maxTime
where c2.Username is not null and c1.Username in(
select c1.Username from users_CTE c1 left join CTE2 c2
on c1.Checktime > c2.minTime and c1.Checktime <= c2.maxTime
group by c1.Username,c2.Username
having COUNT(*) > 1)) a
on u.Checktime=a.Checktime
--Check the latest query with DeletFlag
;WITH users_CTE as
(
select *,row_number() over (partition by Username order by Checktime) as row from users
)
,CTE as(
select row,Username,Checktime,CheckType,0 as totalSeconds,'N' as Delflag from users_CTE where row=1
union all
select t.row,t.Username,t.Checktime,t.CheckType,CASE WHEN (c.totalSeconds + DATEDIFF(SECOND,c.Checktime,t.Checktime)) >= 60 then 0 else (c.totalSeconds + DATEDIFF(SECOND,c.Checktime,t.Checktime)) end as totalSeconds,
CASE WHEN (c.totalSeconds + DATEDIFF(SECOND,c.Checktime,t.Checktime)) >= 60 then 'N' else 'Y' end as Delflag
--CASE WHEN c.totalSeconds <= 60 then 'Y' else 'N' end as Delflag
from users_CTE t inner join CTE c
on t.row=c.row+1
)
select Username,Checktime,CheckType,Delflag from CTE
Why don't you verify the check-ins before inserting them into db. If there exists any check-in for this user, between this date and that date then do nothing else insert it
You should be able to order all records by time, subtract the latest time from the previous time per employee and, if the result is less than a certain threshold, delete the row(s) with the most recent time.
You could try and RANK the records by checkin time and then delete all the records for each employee for each day which have RANK greater than 1.
Try this query: Delete from employee where employee.checkin in (select checkin from employee where count(checkin)>1);
http://codesimplified.com/2010/10/18/remove-duplicate-records-from-the-database-table/
Hope this will helps you.
DELETE FROM timesheet
WHERE timesheetRecordId <>(
SELECT TOP 1 timesheetRecordId from timesheet
WHERE checkInDate=todaysDate AND employeeId=empId ORDER BY checkInTime ASC
)
AND checkInDate=today's date AND empolyeeId=empId;
I don't think you can specify a Target Table, from a Delete statement, in a Subquery of that same statement. So you can't do it with one single Delete statement.
What you can do is write a stored procedure. In your Stored Procedure you should create a Temporary Table containing the PKs returned by this Query:
select cht.pkey
from CheckTimeTable as cht
where exists ( select pkey
from CheckTimeTable
where username = cht.userName
and checkType = 'check-IN'
and Checktime >= subtime(cht.Checktime, '0 0:0:15.000000')
and Checktime < cht.Checktime);
Then write another statement to delete those PKs from your original table, CheckTimeTable.
Note that the query above is for MySQL, so you'll need to find the way to subtract 15 seconds from a timestamp for your DBMS. In MySQL it's done like this:
subtime(cht.Checktime, '0 0:0:15.000000')
This query will return whichever CheckTime record that has another CheckTime record from the same user, with the type Check-In, and within 15 seconds earlier than its own checktime.

Resources