getting a calculated result from mixing records in the same table - filemaker

I am a particular situation on my hands that I do not what to call it, so I am just going to explain it! I am working on File-maker pro database for a university that keeps track of students that are majoring in education one of the tables keeps track of all of the standardized test taken by students that range from the following; ACT, SAT, ETS, and Papa. before when I made this table, all test stood alone or in other words you couldn't combine scores to make a passing grade. That has now changed with two test, PAPA and ETS. In order words, you can combine scores from each test to make one grade to pass.
Here is an example
lets just say for simplicity sake that you need a grand total of 10 for the PAPA and ETS test in order to pass those individual test
Test Name | Reading | Writing | Math | Total | pass/fail
--------------------------------------------------------
PAPA | 2 | 2 | 2 | 6 | FAILED
ets | 1 | 2 | 6 | 9 | FAILED
So A couple weeks ago.. this individual would not pass there standard test, but now this indivdual would hve passed because he/she has gotten a a 6 in the ETS Math and two 2's in Reading and writing in the PAPAS.
Here's what my current calculation looks like:
If (
ReadingScore ≥ BasicTest::PassReading
and MathScore ≥ BasicTest::PassMath
and WritingScore ≥ BasicTest::PassWriting ;
"PASSED" ;
If ( Score >= BasicTest::QMScore ;
If ( ReadingScore >= BasicTest::QMReading ;
If ( MathScore >= BasicTest::QMMATH ;
If ( WritingScore >= BasicTest::QMWriting ;
"PASSED" ;
"DOES NOT MEET WRITING REQUIREMENTS"
) ;
"DOES NOT MEET MATH REQUIREMENTS"
) ;
"DOES NOT MEET READING REQUIREMENTS"
) ;
"FAILED"
)
)
So my question here today is how should I approach this with the Filemaker pro application? Should I make this a report or should a just rebuild the table completely?!?!?!?

While I'm not sure exactly how your calc matches up with your question (I think Score in your calc is the total of the other scores?), I think I can offer some pointers.
Given what you say and what the calc shows, I think the following are true:
Given multiple tests, you're interested in the maximum score for each category.
Each maximum score needs to be greater than the minimum score from either BasicTest::Pass* or BasicTest::QM*.
The test taker passes if all their max scores are greater than all the test minimum scores or the total max scores is greater than the BasicTest::QMScore.
Given the above, I came up with the following calc:
Let (
[
_reading_score = Max ( papa_reading_score ; ets_reading_score ) ;
_math_score = Max ( papa_math_score ; ets_math_score ) ;
_writing_score = Max ( papa_writing_score ; ets_writing_score ) ;
_total_score = _reading_score + _math_score + _writing_score ;
_target_reading = Min ( BasicTest::PassReading ; BasicTest::QMReading ) ;
_target_math = Min ( BasicTest::PassMath ; BasicTest::QMMath ) ;
_target_writing = Min ( BasicTest::PassWriting ; BasicTest::QMWriting ) ;
_pass_reading = ( _reading_score ≥ _target_reading ) ;
_pass_math = ( _math_score ≥ _target_math ) ;
_pass_writing = ( _writing_score ≥ _target_writing ) ;
_passed = ( _pass_reading and _pass_math and _pass_writing )
or ( _total_score ≥ BasicTest::QMScore );
_result = Case (
_passed ; "PASSED" ;
not _pass_reading ; "DOES NOT MEET READING REQUIREMENTS" ;
not _pass_math ; "DOES NOT MEET MATH REQUIREMENTS" ;
not _pass_writing ; "DOES NOT MEET WRITING REQUIREMENTS" ;
"FAILED"
)
] ;
_result
)
Even if the above isn't exactly what you need, I hope it'll provide some direction. With a complicated calc like this, break it up into pieces with each piece (in this case, each variable assignment) being easy to understand.
Note that Max and Min can take many arguments and can also take fields that have multiple values (related fields in a one-to-many relationship or repeating fields), so if you have more than two scores or two tests to check for maximum and minimum values, just add them as more parameters.

Related

How to calculate using the same measure when related date has no data (past and future)?

After trying a lot of approaches and formulas I decided to ask this question.
See this Matrix visual:
WeekDate is column in a table called Planning. It's related to another date time column Week_Imported in another table called Export.
Export table only has values for the dates: 23-Dec-19, 30-Dec-19 and 06-Jan-20 whereas Planning table has dates spanning multiple weeks in the past and future.
Cumulative Plan Count is calculating correctly as long as there are matching dates between the tables Planning and Export.
Now I'd like to keep calculating even when there's no date matching. I want to get the value 32 from the FIRSTDATE which has data (in this case 23-Dec-2019) and backfill the past with 32.
For dates in the future I'd like to use LASTDATE (06-Jan-20) value which is 89.
Something like this:
WeekDate Cumulative Plan Count
.
.
.
25-Nov-19 32
02-Dec-19 32
09-Dec-19 32
16-Dec-19 32
23-Dec-19 32 <= First WeekDate which has data [backfill past with 32]
30-Dec-19 57
06-Jan-19 89 <= Last WeekDate which has data [fill future with 89]
13-Jan-20 89
20-Jan-20 89
27-Jan-20 89
.
.
.
The formula used for the cumulative SUM is this:
Cumulative Plan Count =
CALCULATE (
ROUNDUP([1 - Target] * MAX(Planning[1 - Plan]), 0)
,
FILTER (
ALL ( Planning[WeekDate] ),
Planning[WeekDate] <= MAX(Planning[WeekDate])
)
)
####### Edit 1 #######
Using this measure below I get 1s for the past...
1 - Target =
VAR minWeek = MIN(Export[Week_Imported])
VAR targetCount =
CALCULATE (
COUNT( 'Export'[1 - Plan]),
FILTER('Export', OR(Export[1 - Plan]="YES", Export[1 - Plan]="_")))
var minTarget = CALCULATE (
COUNT( 'Export'[1 - Plan]),
FILTER('Export', OR(Export[1 - Plan]="YES", Export[1 - Plan]="_")
&& Export[Week_Imported] = minWeek))
RETURN
SWITCH(TRUE,
targetCount = BLANK(), 1, // Here is where I need to get the 1st row value (32) and fill up the column up above...
targetCount)
The problem is that no matter what I do I can't get the 1st value for 23-Dec-2019 (32) to fill up the Cumulative Plan Count column.
This is the result when I use the formula above:
WeekDate Cumulative Plan Count
.
.
.
25-Nov-19 1
02-Dec-19 1
09-Dec-19 1
16-Dec-19 1
23-Dec-19 32 <= First WeekDate which has data
30-Dec-19 57
06-Jan-19 89 <= Last WeekDate which has data
13-Jan-20 89
20-Jan-20 89
27-Jan-20 89
.
.
.
####### Edit 2 #######
I put together a simplified Sample.pbix which shows what I'm trying to
accomplish with minimum data to test things:
https://drive.google.com/drive/folders/1zxS_2VE9_0JEMXvsg9Dq196BK552RbNo?usp=sharing
This screenshot has more details:
https://drive.google.com/open?id=1_-IMEpLwuWWN6vrrT_TNWbeqZ7f1LOan
Let me introduce the solution with intermediate steps.
In your data schema, Planning and Export tables are in one to many relationship. Planning is in the grain of every week, while Export has more rows for each week.
Relationship diagram
On this basis, the measure to count the number of Export rows for each Planning week is as simple as this.
Plan Count (Basic) = COUNTROWS ( 'Export' )
When you slice by Planning[WeekDate], this measure returns the counts of Export rows for the corresponding weeks.
Actually, you need FILTER to count only rows you are interested in.
Plan Count =
COUNTROWS (
FILTER (
'Export',
OR ( 'Export'[Plan] = "YES", 'Export'[Plan] = "_" )
)
)
Here is the result we get so far.
Having this measure as the starting point, we need to extend the calculation to the periods where the data does not exist. In order to do that, we need to handle the filter context where [Plan Count] is evaluated.
We need to get the first and final weeks where Export data exists. Here is a formula that returns the first date of the data regardless of the slicer.
First Data Week =
CALCULATE (
MIN ( Planning[WeekDate] ),
REMOVEFILTERS ( Planning[WeekDate] ),
TREATAS (
CALCULATETABLE (
VALUES ( 'Export'[Week_Imported] ), -- Foreign key referencing Planning[WeekDate]
REMOVEFILTERS ( Planning )
),
Planning[WeekDate]
)
)
We can use this date to modify the filter context to calculate [Plan Count] of the first data week. In fact, below measure always returns 1064, which is the number of [Plan Count] in December 30, 2019.
First Week Plan Count =
VAR _FirstDataWeek = CALCULATE (
MIN ( Planning[WeekDate] ),
REMOVEFILTERS ( Planning[WeekDate] ),
TREATAS (
CALCULATETABLE (
VALUES ( 'Export'[Week_Imported] ),
REMOVEFILTERS ( Planning )
),
Planning[WeekDate]
)
)
RETURN
CALCULATE (
[Plan Count],
Planning[WeekDate] = _FirstDataWeek
)
Using this technique, we can expand the first and final values of [Plan Count] to the past and the future dates. Below is the final formula, which iterates over Planning table, and apply different filter context to calculate [Plan Count].
Extended Plan Count =
-- Planning[WeekDate] values where Export data exists
VAR _DataWeeks = CALCULATETABLE (
VALUES ( Planning[WeekDate] ),
REMOVEFILTERS ( Planning[WeekDate] ),
TREATAS (
CALCULATETABLE (
VALUES ( 'Export'[Week_Imported] ), -- Foreign key referencing Planning[WeekDate]
REMOVEFILTERS ( Planning )
),
Planning[WeekDate]
)
)
-- First and last Planning[WeekDate] where Export data exists
VAR _FirstDataWeek = MINX ( _DataWeeks, [WeekDate] )
VAR _FinalDataWeek = MAXX ( _DataWeeks, [WeekDate] )
-- [Plan Count] values of first and last weeks
VAR _FirstDataWeekPlanCount = CALCULATE ( [Plan Count], Planning[WeekDate] = _FirstDataWeek )
VAR _FinalDataWeekPlanCount = CALCULATE ( [Plan Count], Planning[WeekDate] = _FinalDataWeek )
RETURN
SUMX (
Planning,
SWITCH (
TRUE,
Planning[WeekDate] < _FirstDataWeek, _FirstDataWeekPlanCount,
Planning[WeekDate] > _FinalDataWeek, _FinalDataWeekPlanCount,
[Plan Count]
)
)

How to make a self referential window functions

I have a table like this:
amount type app owe
1 a 10 10
2 a 8 -2
3 a 20 12
4 i 30 10
5 a 40 10
owe is:
(type == 'a')?app - sum(owe) where amount < (amount for current row):max(app-sum(owe)where amount<(amount for current row),0)
So I'd need a window function on the column that the window function is on. There are these partition on rows between rows unlimited preceding and prior row, but it has to be on a different column, not the column I'm summing. Is there a way to reference the same column the window function is on
I tried an alias
case
when type = a
then app - sum(owe)over(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 preceding) as owe
else
greatest(0,app - sum(owe)over(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 preceding))
end as owe
But since owe doesn't exist when I made it, I get:
owe doesn't exist.
Is there some other way?
You cannot do that with window functions. Your only chance using SQL is a recursive CTE:
WITH RECURSIVE tab_owe AS (
SELECT amount, type, app,
CASE WHEN type = 'a'
THEN app
ELSE GREATEST(app, 0)
END AS owe
FROM tab
ORDER BY amount LIMIT 1
UNION ALL
SELECT t.amount, t.type, t.app,
CASE WHEN t.type = 'a'
THEN t.app - sum(tab_owe.owe)
ELSE GREATEST(t.app - sum(tab_owe.owe), 0)
END AS owe
FROM (SELECT amount, type, app
FROM tab
WHERE amount > (SELECT max(amount) FROM tab_owe)
ORDER BY amount
LIMIT 1) AS t
CROSS JOIN tab_owe
GROUP BY t.amount, t.type, t.app
)
SELECT amount, type, app, owe
FROM tab_owe;
(untested)
This would be much easier to write in procedural code, sou consider using a table function.
This is what I came up with. Of course, I'm not a real programmer, so I'm sure there's a smarter way:
insert into mort (amount, "type", app)
values
(1,'a',10),
(2,'a',8),
(3,'a',20),
(4,'i',30),
(5,'a',40)
CREATE OR REPLACE FUNCTION mort_v ()
RETURNS TABLE (
zamount int,
ztype text,
zapp int,
zowe double precision
) AS $$
DECLARE
var_r record;
charlie double precision;
sam double precision;
BEGIN
charlie = 0;
FOR var_r IN(SELECT
amount,
"type",
app
FROM mort order by 1)
LOOP
zamount = var_r.amount;
ztype = var_r.type;
zapp = var_r.app;
sam = var_r.app - charlie;
if ztype = 'a' then
zowe = sam;
else
zowe = greatest(sam, 0);
end if;
charlie = charlie + zowe;
RETURN NEXT;
END LOOP;
END; $$
LANGUAGE 'plpgsql';
select * from mort_v()
So with my limited skills you'll notice I had to add a 'z' in front of the columns that are already in the table so I can spit it out again. If your table has 30 columns you'd normally have to do this 30 times. But, I asked a real engineer and he mentioned that if you just spit out the primary key with the calculated column, you can just join it back to the original table. That's smarter than what I have. If there's an even better solution, that would be great. This does serve as a nice reference to how to do something like a cursor in postgre and how to make variables without a '#' in front like in mssqlserver.

Multiple AND statements in a FileMaker calculation

I want to calculate if a value falls outside of 10% of the last two values added to a database. This calculation is not giving me correct feedback when I have 'Weight" values close to 10, or from 100-110. Otherwise it works fine.
Case (
Get(RecordNumber) ≤ 2 ; "Continue Collecting Data" ;
(((GetNthRecord ( Weight ; Get(RecordNumber)-2))*.9) ≤ Weight) and
(((GetNthRecord ( Weight ; Get(RecordNumber)-2))*1.1) ≥ Weight) and
(((GetNthRecord ( Weight ; Get(RecordNumber)-1))*.9) ≤ Weight) and
(((GetNthRecord ( Weight ; Get(RecordNumber)-1))*1.1) ≥ Weight);
"Stable";
"Unstable")
I’m going to start with the assumption that your table includes fields for both the primary key and the creation timestamp. If not, I would highly recommend adding both to this table and every other table you create.
Assuming these fields are in place, you need to create another occurrence of the table on which this layout is based, then relate the primary key to itself via a Cartesian (×) join. Sort the relationship by creation timestamp descending. Your calculation is then:
Case (
(((GetNthRecord ( Weight ; 1 ) *.9 ) ≤ Weight) and
(((GetNthRecord ( Weight ; 1 ) *1.1 ) ≥ Weight) and
(((GetNthRecord ( Weight ; 2 ) *.9 ) ≤ Weight) and
(((GetNthRecord ( Weight ; 2 ) *1.1 ) ≥ Weight);
"Stable";
"Unstable")
Another thing I noticed is that your code is kind of complex. The Let function can make things easier to read, and your four criteria can be cut down to whether either difference is out of range. So, a simpler version becomes:
Let ( [
#weight1 = GetNthRecord ( all::weight ; 1 ) ;
#weight2 = GetNthRecord ( all::weight ; 2 )
] ; //end define Let
Case (
Abs ( #weight1 - weight ) > .1 ; "Unstable" ;
Abs ( #weight2 - weight ) > .1 ; "Unstable" ;
"Stable"
) //end Case
) //end Let
Does that help?
Assuming you are using FileMaker v12 or later, this looks like a good place to use the ExecuteSQL function (not the script step) to retrieve the last two values. You could do something like this:
Let (
sqlQuery = "
SELECT t.weight
FROM MyTable t
WHERE t.id <> ?
ORDER BY t.id DESC
FETCH FIRST 2 ROWS ONLY
" ;
ExecuteSQL ( sqlQuery ; "" ; "" ; MyTable::id )
)
This query assumes you have a unique 'id' field (i.e. a primary key) that's defined as an 'auto-enter serial' value. The WHERE clause makes sure that the current record (presumably the one the user is entering) is not included in the query. The ORDER BY DESC clause forces the last two records to the top where we can fetch the 'weight' values easily into a value list.
Assuming you use a 'Set Variable' script step to put the query results into $lastValues, you can then test for whether they are in range like so:
Let ( [
lastValue1 = GetValue ( $lastValues ; 1 ) ;
lastValue2 = GetValue ( $lastValues ; 2 ) ;
Value1_InRange = lastValue1 - Abs ( lastValue1 - weight ) >= ( 0.9 * lastValue1 ) ;
Value2_InRange = lastValue2 - Abs ( lastValue2 - weight ) >= ( 0.9 * lastValue2 )
] ;
Value1_InRange and Value2_InRange // returns 1 if both values within range, 0 if not
)
If I were doing this, I would put the above range-checking code into a custom function so it's generic and can be easily reused:
IsWithinRange ( valueToTest ; lastValue ; range ) =
lastValue - Abs ( lastValue - valueToTest ) >= ( ( 1 - range ) * lastValue )
Then the above range-checking code can be reduced to:
IsWithinRange ( MyTable::weight ; GetValue ( $lastValues ; 1 ) ; 0.1 ) &
IsWithinRange ( MyTable::weight ; GetValue ( $lastValues ; 2 ) ; 0.1 )
And one last note.. if you use the ExecuteSQL function in a calculated field, be sure to make it 'unstored' so that it only executes when needed. However, I would recommend you avoid that altogether and simply call it from a script step like 'Set Variable'. That way you can control exactly when it executes.
Hope that helps!

FMP 14 - Auto Populate a Field based on a calculation

I am using FMP 14 and would like to auto-populate field A based on the following calulation:
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v1" ; "1st" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v2" ; "2nd" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v3" ; "3rd" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v4" ; "4th" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v5" ; "5th" ) or
If ( Get ( ActiveLayoutObjectName ) = "tab_Visits_v6" ; "6th" )
The above code is supposed to auto-populate the value 1st, 2nd, 3rd ... in field A depending on the name of the object the Get (ActiveLayoutObjectName) function returns. I have named each of my tabs, but the calculation is only returning 0.
Any help would be appreciated.
thanks.
The way your calculation is written makes very little sense. Each one of the If() statements returns a result of either "1st", "2nd", etc. or nothing (an empty string). You are then applying or to all these results. Since only of them can be true, your calculation is essentially doing something like:
"" or "2nd" or "" or "" or "" or ""
which happens to return 1 (true), but has no useful meaning.
You should be using the Case() function here:
Case (
Get ( ActiveLayoutObjectName ) = "tab_Visits_v1" ; "1st" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v2" ; "2nd" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v3" ; "3rd" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v4" ; "4th" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v5" ; "5th" ;
Get ( ActiveLayoutObjectName ) = "tab_Visits_v6" ; "6th"
)
Note also that a calculation field may not always refresh itself as a result of user switching a tab. This refers to an unstored calculation field; if you are trying to use this as the formula to be auto-entered into a "regular' (e.g. Text) field, it will never update.
Added:
Here is our situation. We see a patient a maximum of 6 times. We have
a tab for each one of those 6 visits.
I would suggest you use a portal to a related table of Visits instead of a tab control. A tab control is designed to display fixed components of user interface - not dynamic data. And certainly not data split into separate records. You should have only one unique record for each patient - and as many records for each patient's visits as may be necessary (potentially unlimited).
If you like, you can use the portal rows as buttons to select a specific visit to view in more detail (similar to a tab control, except that the portal shows the "tabs" as vertical rows). A one-row portal to the same Visits table, filtered by the user selection, would work very well for this purpose, I believe.
With 1. .... it would be easy:
Right (Get ( ActiveLayoutObjectName ) ; 1) & "."
Thanks for pointing out, that my first version does not work.

Count number of facts in working memory

I need to count the number of facts satisfying a certain constraint in working memory and then fire the rule if the number of facts breaches a certain threshold. I tried the following but it is complaining about "mismatched input '$cnt' in rule "Rule1"
rule Rule1
when
accumulate ( AFact ( code == "XXXX" ); $cnt : count(1) ; $cnt > 1 )
then
// fire
end
Any help to get the syntax right would be appreciated. Thanks!
The accumulate Conditional Element (as you are using) is only available in Drools versions 5.4 and later - you must be using 5.3.0 or earlier.
You can use the old form of accumulate, available as a continuation of from:
Number( intValue > 1 )
from accumulate( AFact ( code == "XXXX" ), count(1) )