Filemaker: If statement multiples - filemaker

Filemaker Pro 13, using a windows 10 comp. The Database is used from both Mac and Windows computers.
I have a calculation field that detects the string-value of another field to define itself.
Field 1: Name Field2: Position
If ( Field1 = "Bob" or "Joe" or "Carl" ; "Tech Assist")
If ( Field1 = "Susan" or "Hank" or "Alex" ; "Employee")
Filemaker only picks up on the first value of the If statement, in this example, "Bob" and "Susan". All others are left blank.
Like so:
Name: Bob Position: Tech Assist
Name: Joe Position: ___________
How do I get Filemaker to view all possibilities?

There are (at least) two problems with your attempt:
The test Field1 = "Bob" or "Joe" or "Carl" is evaluated as if you have written (Field1 = "Bob") or ("Joe") or ("Carl") - so it will only ever return true when Field1 = "Bob";
You cannot string two If() instructions together like that. Although
you could nest them, you really should be using the Case()
function here, say:
Case (
Field1 = "Bob" or Field1 = "Joe" or Field1 = "Carl" ; "Tech Assist" ;
Field1 = "Susan" or Field1 = "Hank" or Field1 = "Alex" ; "Employee"
)
The more serious problem here is that you are hard-coding data that will almost certainly change at some point into a calculation formula.
What you should have is a related table of Staff with fields for StaffID, Name and Position. Then, when you fill Field1 in this table with the appointed staff member's StaffID, the corresponding position will be taken from that staff member's record in the Staff table via a relationship.

After much fiddling, Big Boss and I figured it out. Seems a bit redundant:
If ( Field1 = "Bob or Field1 = "Joe" or Field1 = "Carl" ; "Tech Assist")

Alternatively...
Case(
Not IsEmpty( FilterValues( List( "Bob" ; "Joe" ; "Carl" ) ; Field1 ) ) ;
"Tech Assist" ;
Not IsEmpty( FilterValues( List( "Susan" ; "Hank" ; "Alex" ) ; Field1 ) ) ;
"Employee"
)

A longer term solution would be to have a table of names and departments. See the image in case it is not clear.
Staff Table Sample
Then you will build a relationship between Field1 in your current table and the "Name" field in the sample image. From there, the relationship will output the name of the department so that you do not need if, else, or case. Otherwise, you will find yourself updating this script every time there is a change to your roster or their roles in your company. The relationship will also allow you to easily add new departments without the need to update this calculation.

Related

Deleting a jsonb array item by name

I have the following table
CREATE TABLE country (
id INTEGER NOT NULL PRIMARY KEY ,
name VARCHAR(50),
extra_info JSONB
);
INSERT INTO country(id,extra_info)
VALUES (1, '{ "name" : "France", "population" : "65000000", "flag_colours": ["red", "blue","white"]}');
INSERT INTO country(id,extra_info)
VALUES (2, '{ "name": "Spain", "population" : "47000000", "borders": ["Portugal", "France"] }');
and i can add an element to the array like this
UPDATE country SET extra_info = jsonb_set(extra_info, '{flag_colours,999999999}', '"green"', true);
and update like this
UPDATE country SET extra_info = jsonb_set(extra_info, '{flag_colours,0}', '"yellow"');
I now would like to delete an array item with a known index or name.
How would i delete a flag_color element by index or by name?
Update
Delete by index
UPDATE country SET extra_info = extra_info #- '{flag_colours,-1}'
How can i delete by name?
As Arrays do not have direct access to items in a straightforward way, we can try to approach this differently through unnesting -> filtering elements -> stitching things back together. I have formulated a code example with ordered comments to help.
CREATE TABLE new_country AS
-- 4. Return a new array (for immutability) that contains the new desired set of colors
SELECT id, name, jsonb_set(extra_info, '{flag_colours}', new_colors, FALSE)
FROM country
-- 3. Use Lateral join to apply this to every row
LEFT JOIN LATERAL (
-- 1. First unnest the desired elements from the Json array as text (to enable filtering)
WITH prep AS (SELECT jsonb_array_elements_text(extra_info -> 'flag_colours') colors FROM country)
SELECT jsonb_agg(colors) new_colors -- 2. Form a new jsonb array after filtering
FROM prep
WHERE colors <> 'red') lat ON TRUE;
In the case you would like to update only the affected column without recreating the main table, you can:
UPDATE country
SET extra_info=new_extra_info
FROM new_country
WHERE country.id = new_country.id;
I have broken it down to two queries to improve readability; however you can also use a subquery instead of creating a new table (new_country).
With the subquery, it should look like:
UPDATE country
SET extra_info=new_extra_info
FROM (SELECT id, name, jsonb_set(extra_info, '{flag_colours}', new_colors, FALSE) new_extra_info
FROM country
-- 3. Use Lateral join to scale this across tables
LEFT JOIN LATERAL (
-- 1. First unnest the desired elements from the Json array as text (to enable filtering)
WITH prep AS (SELECT jsonb_array_elements_text(extra_info -> 'flag_colours') colors FROM country)
SELECT jsonb_agg(colors) new_colors -- 2. Form a new jsonb array after filtering
FROM prep
WHERE colors <> 'red') lat ON TRUE) new_country
WHERE country.id = new_country.id;
Additionally, you may filter rows via (As of PostgreSQL 9.4):
SELECT *
FROM country
WHERE (extra_info -> 'flag_colours') ? 'red'
Actually PG12 allows to do it without JOIN LATERAL:
SELECT jsonb_path_query_array(j #> '{flag_colours}', '$[*] ? (# != "red")'),
jsonb_set(j, '{flag_colours}', jsonb_path_query_array(j #> '{flag_colours}', '$[*] ? (# != "red")'))
FROM (SELECT '{ "name" : "France", "population" : "65000000",
"flag_colours": ["red", "blue","white"]}'::jsonb AS j
) AS j
WHERE j #? '$.flag_colours[*] ? (# == "red")';
jsonb_path_query_array | jsonb_set
------------------------+---------------------------------------------------------------------------------
["blue", "white"] | {"name": "France", "population": "65000000", "flag_colours": ["blue", "white"]}
(1 row)

Extracting all keys from a JSON object unless a certain key has a value

My Postgres jsonb-foo isn't that great but I'd appreciate some help with a query I am trying to put together.
I have this rudimentary query to extract the name of all keys in the _doc's 'answers' key. The jsonb data looks something like this
_doc = {
"answers": {
"baz": true,
"qux": true
"other": "How do i find this"
}
}
and a query might look this this:
SELECT ss.foo, count(DISTINCT (ss.bar)) FROM (
SELECT (_doc::jsonb -> 'bar')::text as bar,
jsonb_object_keys(_doc::jsonb -> 'answers' -> 'foo') as foo
FROM public."table_name"
) ss
WHERE ss.foo IS NOT NULL
GROUP BY ss.foo;
So really the output here would be the number of times each key of answers appears.
("baz" = 1, "qux" = 1, "other" = 1)
Here is my problem, I want to get the number of times each key appears, apart from in the case of other. In that case I want to get the number of times its contents appears. So I want the result to be
("baz" = 1, "qux" = 1, "How do i find this" = 1)
If possible I would love some help structuring this query.
Thank you
demo:db<>fiddle for several json records
demo:db<>fiddle for one json records which has the same key twice (strictly not recommended!)
Using the json_each_text() function to get the key/value pairs. After that take the keys or the value of other, selecting through a CASE clause
SELECT
CASE WHEN elems.key = 'other' THEN elems.value
ELSE elems.key
END AS key,
COUNT(*)
FROM data,
json_each_text(jsondata -> 'answers') AS elems
GROUP BY 1

SQL Select all nodes in a three level tree that match a criterion

I have an PostgreSQL model (generated in the context of Django) that looks something like this:
CREATE TABLE org (
id INTEGER NOT NULL,
parent_id INTEGER,
name CHARACTER VARYING(24),
org_type CHARACTER VARYING(8),
country CHARACTER VARYING(2)
)
CREATE TABLE rate (
id INTEGER NOT NULL,
org_id INTEGER NOT NULL,
rate DOUBLE PRECISION NOT NULL,
currency CHARACTER VARYING(3)
)
where org_type is one of "group", "company" and "branch". Every branch has a company, and only companies belong to a group. Given an arbitrary company or branch, and a country, I need to find all of the rates whose org_id is that of a company and branch that belongs to the same group and that are in the specified country. So, in the following diagram, for either company 123 (in Canada) or branch 124 (in Toronto), a search for rates with country = "US" would find rates belonging to the companies or branches in the box labeled "Selected":
I'm trying something like the following for companies, where $1 is a country code and $2 is an org ID:
SELECT rate.org_id, rate.rate, rate.currency
FROM rate, org
WHERE (
org.country = $1 AND
rate.org_id=org.id AND
org.parent_id = $2
) OR (
...
and then I'm stuck, trying to figure out how to ask for the branches that belong to one of the companies that I just found. I'd really prefer one big WHERE clause that brings in all of the rates by any relevant organization, so that I don't have to hammer the DB with a whole bunch of queries.
Edit
Based on lau's answer, I've tried an example (SQL fiddle), but it's only returning rates for the organization that I'm starting with.
You can:
Apply the criteria on org just like you did
Browse all the way up/down using your initial org(s) as the starting point
Combine the 2 sets with a UNION
JOIN with rates (below I have done a LEFT OUTER JOIN to make it clear what is exactly included in the recursion).
Example:
WITH RECURSIVE SelectedOrg AS (
SELECT * FROM org WHERE id = 4
),
BrowseOrg AS (
SELECT 1 AS Direction, * FROM SelectedOrg
UNION ALL
SELECT -1, * FROM SelectedOrg
UNION ALL
SELECT Direction, org.* FROM org JOIN BrowseOrg ON (direction = 1 and org.parent_id = BrowseOrg.id) OR (direction = -1 and org.id = BrowseOrg.parent_id)
)
SELECT DISTINCT rates.id, BrowseOrg.id, rates.rate FROM BrowseOrg LEFT OUTER JOIN rates ON org_id = BrowseOrg.id
This will be flexible enough to handle cases where you do not know what level of org you have selected (group, company or branch).
In the future, this also should be able to cope with a deeper hierarchy (if you ever add levels to it).
You can do it in 2 queries with some sub query.
Let company_org_ids = select id from org where country code = $1 and parent_id = $2
Select distinct rate.* from rate where orgid in ($3) or where orgid in (select id from org where parent_id in $3 and country_code =$1)
Here $3 is company_org_ids result of first query.
Also this can be done with single query by replacing $3 variable with first query.

libreoffice base create a list filtered by another list's value

I have a table of provinces and a table of cities with ProvienceID. In a form, I want to create a list of cities filtered by selected value of provience list.
How can I do that?
I can create both lists but Cities list shows all cities from all provinces but i want to show only cities from the province that I have selected in Provinces list.
I have another table "Users" with "CityID" and "ProvinceID" that my form edits it and I need to save selected values of Province and City Lists in it, not only show it in the form.
Create two example tables named "Provinces" and "Cities".
ProvinceID Name
~~~~~~~~~~ ~~~~
0 South
1 North
2 Large Midwest
3 Southeast
4 West
CityID Name ProvinceID
~~~~~~ ~~~~ ~~~~~~~~~~
0 Big City 2
1 Very Big City 2
2 Rural Village 1
3 Mountain Heights 0
4 Coastal Plains 4
5 Metropolis 2
Create a query called "ProvinceNames":
SELECT "Name" AS "Province"
FROM "Provinces"
ORDER BY "Province" ASC
Create a query called "Province of City":
SELECT "Provinces"."Name" AS "Province", "Cities"."Name" AS "City"
FROM "Cities", "Provinces" WHERE "Cities"."ProvinceID" = "Provinces"."ProvinceID"
ORDER BY "Province" ASC, "City" ASC
In the form, create a table control based on the query "ProvinceNames".
Using the Form Navigator (or the Form Wizard), create a subform for query "Province of City".
Right-click on subform and choose Properties. Under Data tab:
Link master fields "Province"
Link slave fields "Province"
Create a table control for the subform as well. Now, the cities shown in the subform control depend on the province selected in the main form control.
EDIT:
Here is an example using a filter table to store the current value of the list box. Create two more tables named "Users" and "FilterCriteria".
UserID Name ProvinceID CityID
~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~~~
0 Person1 1 2
1 Person2 2 0
RecordID ProvinceID CityID
~~~~~~~~ ~~~~~~~~~~ ~~~~~~
the only 0 0
We'll also need two Basic macros which can be stored in the document or in My Macros. Go to Tools -> Macros -> Organize Macros -> LibreOffice Basic.
Sub ReadProvince (oEvent as Object)
forms = ThisComponent.getDrawPage().getForms()
mainForm = forms.getByName("MainForm")
cityForm = forms.getByName("CityForm")
listboxProvince = mainForm.getByName("listboxProvince")
listboxCity = cityForm.getByName("listboxCity")
selectedItemID = listboxProvince.SelectedValue
If IsEmpty(selectedItemID) Then
selectedItemID = 0
End If
conn = mainForm.ActiveConnection
stmt = conn.createStatement()
strSQL = "UPDATE ""FilterCriteria"" SET ""ProvinceID"" = " & selectedItemID & _
"WHERE ""RecordID"" = 'the only'"
stmt.executeUpdate(strSQL)
listboxCity.refresh()
lCityCol = mainForm.findColumn("CityID")
currentCityID = mainForm.getInt(lCityCol)
cityForm.updateInt(cityForm.findColumn("CityID"), currentCityID)
listboxCity.refresh()
End Sub
Sub CityChanged (oEvent as Object)
listboxCity = oEvent.Source.Model
cityForm = listboxCity.getParent()
mainForm = cityForm.getParent().getByName("MainForm")
lCityCol = mainForm.findColumn("CityID")
selectedItemID = listboxCity.SelectedValue
If IsEmpty(selectedItemID) Then
selectedItemID = 0
End If
mainForm.updateInt(lCityCol, selectedItemID)
End Sub
Now we need to set up the form like this. In this example, I used two top-level forms instead of a subform. ProvinceID and CityID text boxes are not required but may be helpful in case something goes wrong.
To start creating this form, use the form wizard to create a new form and add all fields from the Users table.
Now, in the Form Navigator, create a form called "CityForm". Content type is SQL command, and Content is:
SELECT "RecordID", "ProvinceID", "CityID" FROM "FilterCriteria"
WHERE "RecordID" = 'the only'
Next, create the "listboxProvince" list box under MainForm. Data Field is "ProvinceID", and List content is the following Sql.
SELECT "Name", "ProvinceID" FROM "Provinces" ORDER BY "Name" ASC
Finally, create the "listboxCity" list box under CityForm. Data Field is "CityID", and List content is the following Sql.
SELECT "Name", "CityID" FROM "Cities" WHERE "ProvinceID" = (
SELECT "ProvinceID" FROM "FilterCriteria"
WHERE "RecordID" = 'the only')
Macros are linked under the Events tab of each control.
Assign "After record change" of the MainForm to ReadProvince().
Assign "Changed" of listboxProvince to ReadProvince().
Assign "Changed" of listboxCity control to CityChanged().
The result allows us to select the Province to filter the list of Cities. Provinces and Cities that are selected are saved in the Users table.
There is another approach which may be better that I have not had time to explore. Instead of the "FilterCriteria" table, apply a filter to the Cities list. The relevant code in ReadProvince() would look something like this.
cityForm.Filter = "ProvinceID=" & selectedItemID
cityForm.ApplyFilter = True
cityForm.reload()
cityForm.absolute(0)
Whatever approach is taken, a complete solution requires complex macro programming. To make it easier, you may decide to use a simpler solution that is not as powerful. For more information, there is a tutorial at https://forum.openoffice.org/en/forum/viewtopic.php?t=46470.
EDIT 2
A solution that requires fewer queries is at https://ask.libreoffice.org/en/question/143186/how-to-use-user-selected-value-from-combobox1-in-combobox2-select-statement/?answer=143231#post-id-143231. The second list box is based on a list of values instead of an SQL query.

Objects having a specified attribute with common value, trigger a rule. The common value is not known and cannot be hardcoded in the rule file

Let us say we have many Person() objects with two attributes "Zipcode" and "Name" . I want a rule to fire for all the objects which have same value for the "Zipcode" attribute, only once.
As an example if there are 10 Person objects p1,p2.....p10 such that 5 Person objects have Zipcode = 1 and other 5 Person objects which have Zipcode = 2 , then I want a rule which will get triggered twice , i.e once for all Person objects with Zipcode =1 and once more for all Person objects with Zipcode =2 .
rule "once for each zipcode"
when
$zcs: Set() from accumulate( Person($h: zipcode), collectSet($h) )
$zipcode: Object() from $zcs
$ps: List() from collect( Person( zipcode == $zipcode ) )
then
//...
end