I have four tables in my database: Products, Printers, PC, Laptops.
In all these tables I have attribute "Model". All models from: Printers, PC and Laptops is in table Products. I need to create a Trigger that interdicts deletion of models that have the price >300 and this models are produced by the "A" manufacturer.
select distinct
products.model
from
products
left join
PC on products.model = PC.model
left join
laptops on products.model = laptops.model
left join
printers on products.model = printers.model
where
manufacturer = 'A'
and (PC.price > 300 or laptops.price > 300 or printers.price > 300);
This SELECT returns the models that satisfy this conditions. I tried to create a DML Trigger.
[1]
CREATE TRIGGER TASK3
ON products
FOR DELETE
AS
IF ((SELECT deleted.model FROM deleted)
IN (select products.model
from products
left join PC on products.model = PC.model
left join laptops on products.model = laptops.model
left join printers on products.model = printers.model
where manufacturer = 'A'
and (PC.price > 300 or laptops.price > 300 or printers.price > 300)))
begin
raiserror ( 'This model can't be deleted, because price is greater than 300 and manufacturer is 'A',2,1)
rollback transaction;
end
else
print 'That model will be deleted'
I tried to compare the attribute value that will be deleted (from deleted table) with values returned by SELECT like [1], in case if the value is met in the list of models that respect this condition (price>300 && manufacturer = 'A') then the Trigger to interdict his deletion.
Note that in SQL Server, deletions can affect multiple records. So, your first subquery can cause a problem of returning too many records.
If you want to prevent the execution of the entire delete when even one record fails, then:
if (exists (select 1
from deleted d
where d.manufacturer = 'A' and
(exists (select 1 from pc p where p.model = d.model and p.price > 300) or
exists (select 1 from laptops l where l.model = d.model and l.price > 300) or
exists (select 1 from printers p where p.model = d.model and p.price > 300)
)
)
)
begin
raiseerror . . .
end;
)
Related
I have this database and I've created a function to update the GPA of a student.
CREATE OR ALTER FUNCTION fGPA
(#Mno varchar(9))
RETURNS DECIMAL(10,4)
BEGIN
DECLARE #gpa decimal(10,4);
SET #gpa = (SELECT
(SUM(CASE e.Grade
WHEN 'A' THEN 4.0
WHEN 'B' THEN 3.0
WHEN 'C' THEN 2.0
WHEN 'D' THEN 1.0
WHEN 'F' THEN 0.0
END * c.Credit) / SUM(c.Credit))
FROM Enroll AS e
INNER JOIN Section AS s ON s.Sid = e.Sid
INNER JOIN Course AS c ON c.Cno = s.Cno
WHERE #Mno = e.Mno);
RETURN #gpa;
END
I am now trying to create a trigger so that whenever a grade is updated in Enroll, the fGPA function is called and updates the GPA as well. Here is what I have:
CREATE OR ALTER TRIGGER update_enroll
ON Enroll
AFTER UPDATE
AS
IF (UPDATE(Grade))
BEGIN
DECLARE #updateGPA DECIMAL(10,4);
SET #updateGPA = (SELECT dbo.fGPA(e.Mno)
FROM Enroll as e
INNER JOIN inserted i ON i.Mno = e.Mno);
UPDATE Student
SET student.GPA = #updateGPA;
END
The error I get is:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
This happens when I try to perform this statement
UPDATE ENROLL
SET Grade = 'B'
WHERE Sid = '17504';
…
BEGIN
/*
DECLARE #updateGPA DECIMAL(10,4);
SET #updateGPA = (SELECT dbo.fGPA(e.Mno)
FROM Enroll as e
INNER JOIN inserted i ON i.Mno = e.Mno);
*/
UPDATE Student
SET GPA = dbo.fGPA(Mno)
WHERE Mno IN (SELECT i.Mno FROM inserted AS i)
END
…
You are making a very common mistake with triggers: the inserted table may have multiple (or zero) rows. So you should do this as a joined update.
Note that the UPDATE() function only tells you if the column is present in the UPDATE statement, not if it was actually updated. You should also join the deleted table to exclude such rows.
This leads on to another common issue: using scalar User-Defined Functions. They are slow, and should generally be avoided. Instead, use an inline Table Valued Function.
You are also updating the entire Student table, you are not joining by anything.
CREATE OR ALTER FUNCTION fGPA
(#Mno varchar(9))
RETURNS TABLE
AS RETURN
SELECT
GPA = SUM(CASE e.Grade
WHEN 'A' THEN 4.0
WHEN 'B' THEN 3.0
WHEN 'C' THEN 2.0
WHEN 'D' THEN 1.0
WHEN 'F' THEN 0.0
END * c.Credit) / SUM(c.Credit)
FROM Enroll AS e
INNER JOIN Section AS s ON s.Sid = e.Sid
INNER JOIN Course AS c ON c.Cno = s.Cno
WHERE #Mno = e.Mno;
Then you simply CROSS APPLY it.
CREATE OR ALTER TRIGGER update_enroll
ON Enroll
AFTER UPDATE
AS
SET NOCOUNT ON;
IF (NOT UPDATE(Grade) OR NOT EXISTS (SELECT 1 FROM inserted)) -- bail out for no rows
RETURN;
UPDATE s
SET s.GPA = gpa.GPA
FROM inserted i
JOIN Student s ON s.Mno = i.Mno
CROSS APPLY dbo.fGPA(i.Mno) gpa;
I will say that I don't think triggers are really the right answer to this problem.
Ideally you should not keep two copies of the same information in two places. Instead create a view that has this calculation in it.
CREATE VIEW vGPA
AS
SELECT
s.Mno,
GPA = SUM(CASE e.Grade
WHEN 'A' THEN 4.0
WHEN 'B' THEN 3.0
WHEN 'C' THEN 2.0
WHEN 'D' THEN 1.0
WHEN 'F' THEN 0.0
END * c.Credit) / SUM(c.Credit)
FROM dbo.Student AS s
INNER JOIN dbo.Enroll AS e ON e.Mno = s.Mno
INNER JOIN dbo.Section AS s ON s.Sid = e.Sid
INNER JOIN dbo.Course AS c ON c.Cno = s.Cno
GROUP BY
s.Mno;
If you are worried about performance, you can index the view to support that. The server will ensure the index always remains in sync with the base table.
Unfortunately, you cannot index a calculated expression on aggregates (ie SUM / SUM). Instead, create a base view which has the two SUMs, then another standard view which selects the value from it.
CREATE VIEW vGPA_indexed
WITH SCHEMABINDING -- needs schema binding
AS
SELECT
s.Mno,
SumGrade = SUM(CASE e.Grade
WHEN 'A' THEN 4.0
WHEN 'B' THEN 3.0
WHEN 'C' THEN 2.0
WHEN 'D' THEN 1.0
WHEN 'F' THEN 0.0
END * c.Credit),
SumCredit = SUM(c.Credit),
TotalRows = COUNT_BIG(*) -- needs count in grouped view
FROM dbo.Student AS s
INNER JOIN dbo.Enroll AS e ON e.Mno = s.Mno
INNER JOIN dbo.Section AS s ON s.Sid = e.Sid
INNER JOIN dbo.Course AS c ON c.Cno = s.Cno
GROUP BY
s.Mno;
CREATE UNIQUE CLUSTERED INDEX CX_vGPA_indexed on vGPA_indexed (Mno);
CREATE VIEW dbo.vGPA
AS
SELECT
s.Mno,
GPA = SumGrade / SumCredit
FROM dbo.vGPA_indexed WITH (NOEXPAND); -- needs NOEXPAND in standard edition, and anyway better for performance
I have these tables Users, UserPackages, Packages, Refers.
Users table has columns UserRef_NO (his own unique number), RefNo (if he registered using someones reference number) and Refarning (where he/she gets earning if someone registered using his refNo and bought a package and package status changed to 'True').
UserPackages table has columns U_ID (foreign key to Users), P_ID (foreign key to Packages), PackageStatus (true if bought false if not), BuyDate (generated as soon as PackageStatus changes to true), ExpiryDate (calculated from the join of Packages table and BuyDate where validity is total days of Packages)
Packages table has columns Price, ReferComission (in percentage), Validity (in days).
EDIT:- Third Edit this works fine for single row updates but fails for multi row updates saying subquery returned more then one value
alter TRIGGER [dbo].[HeavyTriggerTest] ON [dbo].[UserPackages]
after update
as
update UP set
BuyDate = GETDATE(), Expirydate = dateadd(dd, P.Validitiy, getdate())
from dbo.UserPackages UP
inner join Inserted I on I.P_ID = UP.P_ID and I.U_ID = UP.U_ID
inner join dbo.Packages P on P.PID = I.P_ID
where UP.PackageStatus = 'True';
;
with firstCte(Name,UID,PName,Price,ReferComission,ReferredBy)
as
(
select distinct Users.Name,Users.ID,Packages.PName,Packages.Price,Packages.ReferCommission,(select DISTINCT ID from Users
where Users.UserRef_No=Refers.RefOf )
from Users inner join UserPackages on UserPackages.U_ID=Users.ID
inner join Packages on Packages.PID=UserPackages.P_ID
inner join Refers on Users.Ref_No=Refers.RefOf
inner join Inserted I on I.U_ID = UserPackages.U_ID and I.P_ID = UserPackages.P_ID
and UserPackages.PackageStatus='true' AND UserPackages.U_ID=i.U_ID
AND Refers.RefOf=(SELECT users.Ref_No where UserPackages. U_ID=i.U_ID)
)
update Users set RefEarning+= Price*ReferComission/100 from firstCte where ID=ReferredBy ;
update Users set Active='True' where ID=(select U_ID from inserted) and Active='False'
and here's the single update query which i tried to replace with above last two updates but it gives wrong results plus it also doesn't work for multiple row updates
update Users set RefEarning+=(
case when ID=firstCte.ReferredBy then firstCte.Price*ReferComission/100 else RefEarning end)
,Active=case when ID=(select U_ID from inserted) and Active='false' then 'True'
when firstCte.ReferredBy=(select U_ID from inserted) then 'true' else Active end
from firstCte
Your second query, first update, you need to join Inserted on as:
update UP set
BuyDate = GETDATE(), Expirydate = dateadd(dd, P.Validitiy, getdate())
from dbo.UserPackages UP
inner join Inserted I on I.P_IP = UP_P_ID and I.U_ID = UP.U_ID
inner join dbo.Packages P on P.PID = I.P_ID
where UP.PackageStatus = 'True';
Note the table aliases which I recommended to you in your last question - they make it much easier to read through a query.
Also note its best practice to schema qualify your tables.
Second query, second update:
with firstCte ([Name], [UID], PName, Price, ReferComission, ReferredBy)
as
(
select U.[Name], U.ID, P.PName, P.Price, P.ReferCommission
, max(U.ID) over () as referedby
from Users U
inner join UserPackages UP on UP.U_ID = U.ID
inner join Packages P on P.PID = UP.P_ID
inner join Refers R on R.RefOf = U.Ref_No
inner join Inserted I on I.U_ID = UP.U_ID and I.P_ID = UP.P_ID
where UP.PackageStatus='true'
)
update U set
Active = 'True'
, RefEarning += Price*ReferComission/100
from Users U
inner join firstCte on ReferredBy = U.id
where Active = 'False';
Note the window function max to avoid repeating the query in a sub-query.
Note joining the CTE to the Users table to perform the update.
(NOTE: My question is detailed and specific to our system so I will apologize for the length of the explanation before I get to my actual question in advance.)
We have a number of tables that need to have a portion of the data contained therein aggregated to another table.
I have tried, to the best of my ability, to ensure the trigger can handle multiple rows and not just single row transactions:
TRIGGER [dbo].[TR_SavedFiles_PlanLibraryMetrics] on [dbo].[SavedFiles]
AFTER INSERT, UPDATE, DELETE
AS
SET NOCOUNT ON
BEGIN
IF EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it
ON m.PlanLibCode = it.PlanLibCode
JOIN inserted AS i
ON m.AccountID = i.AccountID
AND (it.ItemTypeID = i.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON i.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
UPDATE PlanLibraryMetrics
SET MetricValue = x.newValue
FROM (SELECT COUNT(s.AccountID) AS newValue, it.PlanLibCode, i.AccountID
FROM SavedFiles AS s
JOIN PlanLibraryItems AS it
ON (s.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
JOIN inserted AS i
ON s.AccountID = i.AccountID
AND s.CISFileID = i.CISFileID
Group By PlanLibCode, i.AccountID) AS x
JOIN PlanLibraryMetrics AS m
ON x.PlanLibCode = m.PlanLibCode
AND x.AccountID = m.AccountID
END
ELSE IF EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it
ON m.PlanLibCode = it.PlanLibCode
JOIN deleted AS d
ON m.AccountID = d.AccountID
AND (it.ItemTypeID = d.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON d.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
UPDATE PlanLibraryMetrics
SET MetricValue = x.newValue
FROM (SELECT COUNT(s.AccountID) AS newValue,it.PlanLibCode, d.AccountID
FROM SavedFiles AS s
RIGHT OUTER JOIN deleted AS d
ON s.AccountID = d.AccountID
AND s.CISFileID = d.CISFileID
JOIN PlanLibraryItems AS it
ON (d.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
Group By PlanLibCode, d.AccountID) AS x
JOIN PlanLibraryMetrics AS m
ON x.PlanLibCode = m.PlanLibCode
AND x.AccountID = m.AccountID
END
ELSE IF NOT EXISTS (
SELECT m.AccountID, m.PlanLibCode
FROM PlanLibraryMetrics AS m
JOIN PlanLibraryItems AS it ON m.PlanLibCode = it.PlanLibCode
JOIN inserted AS i
ON m.AccountID = i.AccountID
AND (it.ItemTypeID = i.CISFileID AND it.ItemType = 'FILE')
JOIN Accounts AS a ON i.AccountID = a.AccountID
WHERE a.Status = 0
)
BEGIN
INSERT INTO PlanLibraryMetrics
SELECT i.AccountID, it.PlanLibCode, COUNT(s.AccountID)
FROM SavedFiles AS s
JOIN PlanLibraryItems AS it
ON (s.CISFileID = it.ItemTypeID AND it.ItemType = 'FILE')
JOIN inserted AS i
ON s.AccountID = i.AccountID
AND s.CISFileID = i.CISFileID
Group By PlanLibCode, i.AccountID
END
END
And it appears to work, until we get to a system we have in place to "merge" accounts whereby all the records from the old account(s) have their accountid's changed to the primary account.
The behavior I am trying to accomplish is to update any existing records in the aggregate table and insert new records for anything that doesn't already exist for the primary account.
So my questions: does the existence of the If exists/elseif exists clauses actually make it so this trigger (and the rest of the triggers modeled on this one) does not actually handle multiple rows? If so, I don't think my problem would be solved by writing 2 or 3 separate triggers for each table as I still have to check for an existing record every time (i.e. an update/delete trigger and a separate insert trigger)? Should I move all of this to a stored procedure and pass in, for instance, a table variable that contains all the records from the inserted/deleted tables (something I thought of cruising around the forums)?
SQL is not my strong suit and triggers even less so. Any help from the more experienced would be immensely appreciated.
Short answer - no. The use of "if exists" does not prevent the correct handling of multi-row statements.
But let's be honest. If you struggle with tsql and triggers, then you should be writing separate triggers for each action UNTIL you have confidence in your approach and logic.
Your logic appears to be overly complicated, but no one knows your schema and how your tables are used. It also appears to be at least one error. In your insert section, you check for existence based on a.Status = 0. That same logic is not included in the associated insert statement. This has been propagated to the other 2 sets of logic.
For example, assume 5 rows inserted in SavedFiles. 1 matches a row in PlanLibraryMetrics, the others don't. What does your code do? After execution, only one row in PlanLibraryMetrics will be updated - you have skipped the logic needed to aggregate/insert the other 4 rows. Your deletion logic seems highly suspect with the right join; without knowing the actual and logic keys to the various tables it is difficult to understand.
And on that note - now is a good time to start commenting your code to help others (including yourself at a later date) understand what the code should be doing and perhaps why. And yes - please consider Damien's suggestion. A view, indexed or not, might be (probably is) a better approach - and certainly a safer, more easily maintained one.
We are using following materialized view and underlying views to realize some reporting. The materialized view is refreshed completely manually from our application (by DBMS_SNAPSHOT.REFRESH( '"OVERALL_WEEKLY"','C');). After this call the view is in FRESH state, but after any DML operation is done to underlying tables the materialized view gets NEEDS_COMPILE state.
The queries for views are as follows.
The undetlying tables EBR_CYCLE_TIME and EBR_AREA are changing very frequently. So refresh on commit is not an option for our purpose.
Is there any way to avoid the NEEDS_COMPILE state? Or better what causes the NEED_COMPILE state?
CREATE OR REPLACE FORCE VIEW "OTD_WEEKLY" AS
SELECT
otd.WEEK,
otd.SITE_ID,
otd.AREA_ID,
otd.OTD_METRIC AS CT_METRIC,
ROUND(100 * (SUM(otd.SUCCESS) / SUM(otd.CT_TOTAL_COUNT)), 2) AS OTD_VALUE
FROM
(SELECT
FC.MFL_FISCAL_YR_NUM * 100 + FC.MFL_FISCAL_WK_NUM AS WEEK,
r.BUSINESS_UNIT_ID AS PAL2_ID,
a.site_id AS SITE_ID,
a.area_def_id AS AREA_ID,
a.PRIORITY AS PRIORITY,
r.EBR_BUILD_SUBTYPE AS NPI,
r.CORPORATE_TD AS CTD,
ctd.NAME AS OTD_METRIC,
COUNT(r.ebr_number) AS CT_TOTAL_COUNT,
COUNT(
CASE
WHEN (ct.TIME_ELAPSED) > (ct.TARGET * 86400)
THEN NULL
ELSE r.ebr_number
END) AS SUCCESS,
COUNT(
CASE
WHEN (ct.TIME_ELAPSED) > (ct.TARGET * 86400)
THEN r.ebr_number
ELSE NULL
END) AS MISSED,
COUNT(DISTINCT r.ebr_number) AS TOTAL_NUMBER_OF_EBR
FROM ebr_cycle_time ct
JOIN ebr_area a
ON (a.id = ct.ebr_area_id
AND ct.status = 'FINISHED'
AND a.ship_date IS NOT NULL)
JOIN ebr_request r
ON (a.ebr_id = r.id AND r.submit_date >= to_date((select STRING_VALUE from EBR_STATUS_TABLE where key = 'REPORT_DATE_FROM'),'DD.MM.YY'))
JOIN EBR_GROUP_CYCLE_TIME_DEF gctd
ON (ct.CYCLE_TIME_GROUP_DEF = gctd.ID)
JOIN EBR_CYCLE_TIME_DEF ctd
ON (ctd.ID = gctd.CYCLE_TIME_DEF_ID
AND ctd.OTD_METRIC = 'Y')
JOIN EBR_CYCLE_TIME_GROUP ctg
ON (ctg.id = gctd.CYCLE_TIME_GROUP_ID)
JOIN EEBR_MC_LCL_FISCAL FC
ON (FC.MFL_QUERY_DT = TRUNC(a.ship_date) AND fc.MFL_QUERY_DT > add_months(trunc(sysdate, 'MM'), -8) AND fc.MFL_QUERY_DT <= sysdate)
GROUP BY
r.BUSINESS_UNIT_ID,
a.site_id,
a.area_def_id,
ctd.name,
fc.mfl_fiscal_yr_num,
fc.mfl_fiscal_wk_num,
ct.target,
a.PRIORITY,
r.CORPORATE_TD,
r.EBR_BUILD_SUBTYPE
) otd
GROUP BY
otd.WEEK,
otd.PAL2_ID,
otd.SITE_ID,
otd.AREA_ID,
otd.OTD_METRIC;
CREATE OR REPLACE FORCE VIEW "LAGGING_SCORE_WEEKLY" AS
SELECT
dsc.SITE_ID,
dsc.AREA_ID,
dsc.CT_METRIC,
(dsc.ARCHIVE_YEAR * 100 + dsc.ARCHIVE_WEEK) AS WEEK,
ROUND(AVG(SCORE), 4) AS SCORE
FROM
(SELECT
cts.site_id AS SITE_ID,
ls.AREA_DEF_ID AS AREA_ID,
ctd.name AS CT_METRIC,
ctd.id AS CT_ID,
fc.MFL_QUERY_DT AS ARCHIVE_DAY,
fc.MFL_FISCAL_WK_NUM AS ARCHIVE_WEEK,
fc.MFL_FISCAL_MTH_NUM AS ARCHIVE_MONTH,
fc.MFL_FISCAL_YR_NUM AS ARCHIVE_YEAR,
(fc.MFL_FISCAL_YR_NUM * 100 + fc.MFL_FISCAL_WK_NUM) AS WEEK,
CASE
WHEN SUM(cts.PENALTY) > 0
THEN SUM(cts.PENALTY)
ELSE 0
END AS EBR_PENALTY,
COUNT(DISTINCT cts.ebr_number) AS NUMBER_OF_EBR,
COUNT(DISTINCT (
CASE
WHEN cts.LAGGING_TIME > 0
THEN cts.ebr_number
ELSE NULL
END)) AS NUMBER_OF_LAGGING_EBR,
CASE
WHEN SUM(cts.PENALTY) > 0
THEN greatest(100 - 100 * (SUM(cts.PENALTY) / COUNT(DISTINCT cts.ebr_number)), 0)
ELSE 100
END AS SCORE
FROM EBR_CYCLE_TIME_SNAPSHOT cts
JOIN EBR_REQUEST r
ON (r.ebr_number = cts.ebr_number AND r.submit_date >= to_date((select STRING_VALUE from EBR_STATUS_TABLE where key = 'REPORT_DATE_FROM'),'DD.MM.YY'))
RIGHT JOIN EBR_LAGGING_STATISTIC ls
ON ((TRUNC(ls.stat_date) = TRUNC(cts.SNAPSHOT_TIME))
AND ls.site_id = cts.site_id
AND cts.AREA_DEF_ID = ls.AREA_DEF_ID
AND ls.CT_DEF_ID = cts.CYCLE_TIME_DEF_ID)
JOIN EBR_CYCLE_TIME_DEF ctd
ON (ls.CT_DEF_ID = ctd.id
AND ctd.OTD_METRIC = 'Y')
JOIN EEBR_MC_LCL_FISCAL fc
ON (TRUNC(ls.STAT_DATE) = TRUNC(fc.MFL_QUERY_DT) AND fc.MFL_QUERY_DT > add_months(trunc(sysdate, 'MM'), -8))
GROUP BY
cts.site_id,
ls.AREA_DEF_ID,
ctd.name,
ctd.id,
fc.MFL_QUERY_DT,
fc.MFL_FISCAL_WK_NUM,
fc.MFL_FISCAL_MTH_NUM,
fc.MFL_FISCAL_YR_NUM,
ls.NUMBER_OF_EBR,
ls.NUMBER_OF_LAGGING_EBR,
TRUNC(ls.STAT_DATE)
) dsc
GROUP BY dsc.SITE_ID,
dsc.AREA_ID,
dsc.CT_METRIC,
dsc.ARCHIVE_WEEK,
dsc.ARCHIVE_MONTH,
dsc.ARCHIVE_YEAR;
CREATE OR REPLACE FORCE VIEW "START_COMPLIANCE_WEEKLY" AS
SELECT
'Starts Compliance' AS CT_METRIC,
a.site_id AS SITE_ID,
a.area_def_id AS AREA_ID,
((lstw.MFL_FISCAL_YR_NUM * 100) + lstw.MFL_FISCAL_WK_NUM) AS WEEK,
ROUND(AVG(
CASE
WHEN ((ct.START_DATE IS NOT NULL
AND TRUNC((ct.START_DATE AT TIME ZONE 'MST') AT TIME ZONE s.time_zone) >
TRUNC((a.FIRST_FIRM_START_DATE AT TIME ZONE 'MST') AT TIME ZONE s.time_zone))
OR (ct.START_DATE IS NULL
AND next_day(TRUNC(sysdate) - 7, 'Sun') >
TRUNC((a.FIRST_FIRM_START_DATE AT TIME ZONE 'MST') AT TIME ZONE s.time_zone)))
THEN 0
ELSE 100
END), 2) AS SCORE
FROM ebr_area a
JOIN ebr_request r
ON (a.ebr_id = r.id AND a.FIRST_FIRM_START_DATE IS NOT NULL
AND a.FIRST_FIRM_START_DATE <= next_day(TRUNC(sysdate) - 7, 'Sun') AND AND r.status <> 'CANCELLED'
AND r.submit_date >= to_date((select STRING_VALUE from EBR_STATUS_TABLE where key = 'REPORT_DATE_FROM'),'DD.MM.YY'))
JOIN ebr_site s
ON (s.id = a.site_id)
LEFT JOIN
(SELECT
ct.START_DATE AS START_DATE,
ct.ROUND AS ROUND,
ct.ebr_area_id AS area_id
FROM ebr_cycle_time ct
JOIN EBR_GROUP_CYCLE_TIME_DEF gctd
ON (ct.CYCLE_TIME_GROUP_DEF = gctd.ID
AND ct.status <> 'NEW')
JOIN EBR_CYCLE_TIME_DEF ctd
ON (ctd.ID = gctd.CYCLE_TIME_DEF_ID
AND ctd.code = 'SITE_PROCESSING')
) ct ON (ct.area_id = a.id)
JOIN EEBR_MC_LCL_FISCAL lstw
ON (lstw.MFL_QUERY_DT = TRUNC(FIRST_FIRM_START_DATE) AND lstw.MFL_QUERY_DT > add_months(trunc(sysdate, 'MM'), -8))
GROUP BY
a.site_id,
s.time_zone,
a.area_def_id,
lstw.MFL_FISCAL_YR_NUM,
lstw.MFL_FISCAL_WK_NUM;
CREATE MATERIALIZED VIEW "OVERALL_WEEKLY"
AS SELECT s.code AS SITE_CODE,
s.name AS SITE_NAME,
reports.SITE_ID,
ad.NAME AS AREA,
ad.CODE AS AREA_CODE,
reports.AREA_ID,
reports.REPORT_TYPE,
reports.CT_METRIC,
reports.WEEK,
reports.SCORE
FROM (
(SELECT 'Starts Compliance' AS REPORT_TYPE,
AREA_ID,
SITE_ID,
CT_METRIC,
WEEK,
SCORE
FROM START_COMPLIANCE_WEEKLY
)
UNION
(SELECT 'OTD' AS REPORT_TYPE,
AREA_ID,
SITE_ID,
OTD_WEEKLY.CT_METRIC,
OTD_WEEKLY.WEEK,
OTD_WEEKLY.OTD_VALUE AS SCORE
FROM OTD_WEEKLY
)
UNION
(SELECT 'Lagging' AS REPORT_TYPE,
AREA_ID,
SITE_ID,
LAGGING_SCORE_WEEKLY.CT_METRIC,
LAGGING_SCORE_WEEKLY.WEEK,
LAGGING_SCORE_WEEKLY.SCORE
FROM LAGGING_SCORE_WEEKLY
)) reports
JOIN EBR_SITE s
ON (s.id = reports.SITE_ID)
JOIN EBR_AREA_DEF ad
ON (ad.id = reports.area_id);
This is expected behaviour. The NEEDS_COMPILE status means that the MView data is different than the source data. It's only there for information purposes. If refresh on commit is not suitable for your usage, then you can ignore MViews in NEEDS_COMPILE status.
See MOS Doc ID 264036.1:
Dependencies related to MVs are automatically maintained to ensure
correct operation. When an MV is created, the materialized view
depends on the master tables referenced in its definition. Any DML
operation, such as an INSERT, or DELETE, UPDATE, or DDL operation on
any dependency in the materialized view will cause it to become
invalid.
I have a bunch of bank transactions in a table in SQL.
Example: http://sqlfiddle.com/#!6/6b2c8/1/0
I need to identify the transactions that are made between these 2 linked accounts. The Accounts table (not shown) links these 2 accounts to the one source (user).
For example:
I have an everyday account, and a savings account. From time to time, I may transfer money from my everyday account, to my savings account (or vice-versa).
The transaction descriptions are usually similar (Transfer to xxx/transfer from xxx), usually on the same day, and obviously, the same dollar amount.
EDIT: I now have the following query (dumbed down), which works for some scenarios
Basically, I created 2 temp tables with all withdrawals and deposits that met certain criteria. I then join them together, based on a few requirements (same transaction amount, different account # etc). Then using the ROW_NUMBER function, I have ordered which ones are more likely to be inter-account transactions.
I now have an issue where if, for example:
$100 transferred from Account A to Account B
$100 Transferred from Account B to Account C
My query will match the transfer between Account A and C, then there is only one transaction for account B, and it will not be matched. So essentially, instead of receiving 2 rows back (2 deposits, lined up with 2 withdrawals), I only get 1 row (1 deposit, 1 withdrawal), for a transfer from A to B :(
INSERT INTO #Deposits
SELECT t.*
FROM dbo.Customer c
INNER JOIN dbo.Source src ON src.AppID = app.AppID
INNER JOIN dbo.Account acc ON acc.SourceID = src.SourceID
INNER JOIN dbo.Tran t ON t.AccountID = acc.AccountID
WHERE c.CustomerID = 123
AND t.Template = 'DEPOSIT'
INSERT INTO #Withdrawals
SELECT t.*
FROM dbo.Customer c
INNER JOIN dbo.Source src ON src.AppID = app.AppID
INNER JOIN dbo.Account acc ON acc.SourceID = src.SourceID
INNER JOIN dbo.Tran t ON t.AccountID = acc.AccountID
WHERE c.CustomerID = 123
AND t.Template = 'WITHDRAWAL'
;WITH cte
AS ( SELECT [...] ,
ROW_NUMBER() OVER ( PARTITION BY d.TranID ORDER BY SUM( CASE WHEN d.TranDate = d.TranDate THEN 2 ELSE 1 END), w.TranID ) AS DepRN,
ROW_NUMBER() OVER ( PARTITION BY w.TranID ORDER BY SUM( CASE WHEN d.TranDate = d.TranDate THEN 2 ELSE 1 END ), d.TranID ) AS WdlRN
FROM #Withdrawal w
INNER JOIN d ON w.TranAmount = d.TranAmount -- Same transaction amount
AND w.AccountID <> d.AccountID -- Different accounts, same customer
AND w.TranDate BETWEEN d.TranDate AND DATEADD(DAY, 3, d.TranDate) -- Same day, or within 3 days
GROUP BY [...]
)
SELECT *
FROM cte
WHERE cte.DepRN = cte.WdlRN
Maybe this is a start? I don't think we have enough info to say whether this would be reliable or would cause a lot of "false positives".
select t1.TransactionID, t2.TransactionID
from dbo.Transactions as t1 inner join dbo.Transactions as t2
on t2.AccountID = t2.AccountID
and t2.TransactionDate = t1.TransactionDate
and t2.TransactionAmount = t1.TransactionAmount
and t2.TransactionID - t1.TransactionID between 1 and 20 -- maybe??
and t1.TransactionDesc like 'Transfer from%'
and t2.TransactionDesc like 'Transfer to%'
and t2.TransactionID > t1.TransactionID