has anyone tried to use an intersect table instead of oracle-apex list of values' storing method - sql

ApEx as you know stores the multiple choices list of values in a single column by separating the values with ':' like this
qwe:rty:yui:opa:sdf:ghj
but this is not how a database should function, there should should be a middle table with foreign keys.
so my question is has anyone tried to do it the right way or do i just stick with ApEx's method? and is there no risk of errors if so?
i am not very experienced with apex so i still don't know how to insert into 2 tables at the same time, if anyone can tell me how maybe i can find a solution on my own.

The values in a multi-select are treated as colon separated lists in APEX, but you have all the freedom you want in storing your data. There is no built-in support for multi selects (probably because there are many ways to implement this on the backend) but it's not too hard to implement the logic yourself.
Allow me to illustratie with an example. There is a table TEAMS (primary key TEAM_ID) with a child table MEMBERS (primary key MEMBER_ID) and an intersect table TEAM_MEMBERS (primary key TEAM_MEMBER_ID - auto generated).
In the teams form there is a page item P1_TEAM_MEMBERS of type select list with "Allow Multi Selection" set to "on".
There are 2 parts to this:
getting the data from the intersect table into page item on page load
processing the data on submit.
(1) The first part is pretty simple. You create a computation (to run AFTER the Form Initialization Process) on P1_TEAM_MEMBERS of type "SQL Query (return colon separated values)". This type of computation is created specifically for handling multi selects. The source would be
SELECT member_id FROM team_members WHERE team_id = :P1_TEAM_ID
If you want to have more control, you can also take type "SQL Query (return single value)" and us LISTAGG to convert the columns to a colon separated string.
(2) For processing the data you can use an application process to be executed AFTER the Automatic Row Processing process. This is because you need primary key value (P1_TEAM in our case) of the master table if you want to create a new team with members. In my code I use another page item P1_TEAM_MEMBERS_OLD thas has original values of team members. It also has a colon delimited string and it is computed just before this page process.
The plsql API apex_string offers a could of very useful functions. apex_string.split takes a string with separators and converts that to a pl/sql collection.
Use MULTISET to identify the differences in the old and new value.
DECLARE
l_old_team_members apex_t_varchar2;
l_new_team_members apex_t_varchar2;
l_members_added apex_t_varchar2;
l_members_removed apex_t_varchar2;
BEGIN
l_old_team_members := apex_string.split(:P1_MEMBERS_OLD,':');
l_new_team_members := apex_string.split(:P1_MEMBERS,':');
l_members_added := l_new_team MULTISET EXCEPT l_old_team;
l_members_removed := l_old_team MULTISET EXCEPT l_new_team;
-- add new team members
FOR i IN 1 .. l_members_added.COUNT LOOP
INSERT INTO team_members(team_id, member_id)
VALUES (:P1_TEAM_ID,l_members_added(i));
END LOOP;
-- delete removed team members
FOR i IN 1 .. l_members_removed.COUNT LOOP
DELETE FROM team_members WHERE team_id = :P1_TEAM_ID AND member_id = l_members_removed(i);
END LOOP;
END;
The downside to this code is that there is no lost update detection out of the box, but you can implement that manually if it is a requirement.

As far as I can tell, either you use what Apex provides (which is a colon-separated list of values), or you "invent" your own solution.
I don't know why they (Oracle) chose to do it that way, but yes - it is annoying. You can't store those values properly into a table, can't enforce referential integrity constraint, have "problems" when writing reports (as we usually store IDs and display names so we have to convert column into rows; not that you can't do it, just saying), ...
As I don't have (too) many multi-select items, I kind of live with what I have, but I don't like it.

Related

Creating a Table Using Previous Values (Iterative Process)

I'm completely new to Visual FoxPro (9.0) and I was having trouble with creating a table which uses previous values to generate new values. What I mean by this is I have a given table that is two columns, age and probability of death. Using this I need to create a survival table which has the columns Age, l(x), d(x), q(x), m(x), L(x), T(x), and q(x) Where:
l(x): Survivorship Function; Defined as l(x+1) = l(x) * EXP(-m(x))
d(x): Number of Deaths; Defined as l(x) - l(x+1)
q(x): Probability of Death; This is given to me already
m(x): Mortality Rate; Defined as -LN(1-q(x))
L(x): Total Person-Years of Cohorts in the Interval (x, x+1); Defined as l(x+1) + (0.5 * d(x))
T(X): Total Person-Years of all Cohorts in the Interval (x, N); Defined as SUM(L(x)) [From x, N]
e(x): Remaining Life Expectancy; Defined as T(x) / l(x)
Now I'm not asking how to get all of these values, I just need help getting started and maybe pointed in the right direction. As far as I can tell, in VFP there is no way to point to a specific row in a data-table, so I can't do what I normally do in R and just make a loop. I.E. I can't do something like:
for (i in 1:length(given_table$Age))
{
new_table$mort_rate[i] <- -LN(1-given_table$death_prop[i])
}
It's been a little while so I'm not sure that's 100% correct anyway, but my point is I'm used to being able to create a table, and alter the values individually by pointing to a specific row and/or column using a loop with a simple counter variable. However, from what I've read there doesn't seem to be a way to do this in VFP, and I'm completely lost.
I've tried to make a Cursor, populating it with dummy values and trying to update each value sequentially using a SCATTER NAME and SCAN/REPLACE thing, but I don't really understand what's happening or how to fine tune this each calculation/entry that I need. (This is the post I was referencing when I tried this: Multiply and subtract values in previous row for new row in FoxPro.
So, how do I go about making a table that relies on iterative process to calculate subsequent values in Visual FoxPro? Are there any good resources that explain Cursors and the Scatter/Scan thing that I was trying (I couldn't find any resources that explained it in terms I could understand)?
Sorry if I've worded things poorly, I'm fairly new to programming in general. Thank you.
You absolutely can write a loop through an existing table in VFP. Use the SCAN command. However, if you're planning to add records to the same table as you go, you're going to run into some issues. Is that what you meant here? If so, my suggestion is to put the new records into a cursor as you create them and then APPEND them to the original table after you've processed all the records that were there when you started.
If you're putting records into a different table as you loop through the original, this is straightforward:
* Assumes you've already created the table or cursor to hold the result
SELECT YourOriginalTable && substitute in the alias/name for the original table
SCAN
* Do your calculations
* Substitute appropriately for YourNewTable and the two lists
INSERT INTO YourNewTable (<list of fields>) VALUES (<list of values>)
ENDSCAN
In the INSERT command, if you refer to any fields of the original table, you need to alias them, like this: YourOriginalTable.YourField, again substituting appropriately.
A bit too late but maybe still helps.
The steps to achieve what you want are:
0. close the tables - just in case (see CLOSE DATABASE)
open the Age table (see USE in VFP help)
create the Survival table structure (see CREATE TABLE)
for this you need to know the field type for each of your l(x), d(x), etc functions
Lets say that you named the fields like your functions (i.e. Lx,Dx, etc)
select the Age table (see SELECT)
loop through Age table (see SCAN)
pass each record into variables (see SCATTER)
made your calculations starting from the Age table data (variables) using L(x),D(x),etc formulas and store it into variables named as M.Your Survival Table Field
i.e. M.mx = -LOG(1-m.Age) && see LOG
Note: in these calculations you can use any mix of Age table variables and the new created variables.
after you calculated all the fields from Survival write it into table (see APPEND && GATHER commands)
close the tables (see CLOSE DATABASE)

Optimising LIKE expressions that start with wildcards

I have a table in a SQL Server database with an address field (ex. 1 Farnham Road, Guildford, Surrey, GU2XFF) which I want to search with a wildcard before and after the search string.
SELECT *
FROM Table
WHERE Address_Field LIKE '%nham%'
I have around 2 million records in this table and I'm finding that queries take anywhere from 5-10s, which isn't ideal. I believe this is because of the preceding wildcard.
I think I'm right in saying that any indexes won't be used for seek operations because of the preceeding wildcard.
Using full text searching and CONTAINS isn't possible because I want to search for the latter parts of words (I know that you could replace the search string for Guil* in the below query and this would return results). Certainly running the following returns no results
SELECT *
FROM Table
WHERE CONTAINS(Address_Field, '"nham"')
Is there any way to optimise queries with preceding wildcards?
Here is one (not really recommended) solution.
Create a table AddressSubstrings. This table would have multiple rows per address and the primary key of table.
When you insert an address into table, insert substrings starting from each position. So, if you want to insert 'abcd', then you would insert:
abcd
bcd
cd
d
along with the unique id of the row in Table. (This can all be done using a trigger.)
Create an index on AddressSubstrings(AddressSubstring).
Then you can phrase your query as:
SELECT *
FROM Table t JOIN
AddressSubstrings ads
ON t.table_id = ads.table_id
WHERE ads.AddressSubstring LIKE 'nham%';
Now there will be a matching row starting with nham. So, like should make use of an index (and a full text index also works).
If you are interesting in the right way to handle this problem, a reasonable place to start is the Postgres documentation. This uses a method similar to the above, but using n-grams. The only problem with n-grams for your particular problem is that they require re-writing the comparison as well as changing the storing.
I can't offer a complete solution to this difficult problem.
But if you're looking to create a suffix search capability, in which, for example, you'd be able to find the row containing HWilson with ilson and the row containing ABC123000654 with 654, here's a suggestion.
WHERE REVERSE(textcolumn) LIKE REVERSE('ilson') + '%'
Of course this isn't sargable the way I wrote it here. But many modern DBMSs, including recent versions of SQL server, allow the definition, and indexing, of computed or virtual columns.
I've deployed this technique, to the delight of end users, in a health-care system with lots of record IDs like ABC123000654.
Not without a serious preparation effort, hwilson1.
At the risk of repeating the obvious - any search path optimisation - leading to the decision whether an index is used, or which type of join operator to use, etc. (independently of which DBMS we're talking about) - works on equality (equal to) or range checking (greater-than and less-than).
With leading wildcards, you're out of luck.
The workaround is a serious preparation effort, as stated up front:
It would boil down to Vertica's text search feature, where that problem is solved. See here:
https://my.vertica.com/docs/8.0.x/HTML/index.htm#Authoring/AdministratorsGuide/Tables/TextSearch/UsingTextSearch.htm
For any other database platform, including MS SQL, you'll have to do that manually.
In a nutshell: It relies on a primary key or unique identifier of the table whose text search you want to optimise.
You create an auxiliary table, whose primary key is the primary key of your base table, plus a sequence number, and a VARCHAR column that will contain a series of substrings of the base table's string you initially searched using wildcards. In an over-simplified way:
If your input table (just showing the columns that matter) is this:
id |the_search_col |other_col
42|The Restaurant at the End of the Universe|Arthur Dent
43|The Hitch-Hiker's Guide to the Galaxy |Ford Prefect
Your auxiliary search table could contain:
id |seq|search_token
42| 1|Restaurant
42| 2|End
42| 3|Universe
43| 1|Hitch-Hiker
43| 2|Guide
43| 3|Galaxy
Normally, you suppress typical "fillers" like articles and prepositions and apostrophe-s , and split into tokens separated by punctuation and white space. For your '%nham%' example, however, you'd probably need to talk to a linguist who has specialised in English morphology to find splitting token candidates .... :-]
You could start by the same technique that I use when I un-pivot a horizontal series of measures without the PIVOT clause, like here:
Pivot sql convert rows to columns
Then, use a combination of, probably nested, CHARINDEX() and SUBSTRING() using the index you get from the CROSS JOIN with a series of index integers as described in my post suggested above, and use that very index as the sequence for the auxiliary search table.
Lay an index on search_token and you'll have a very fast access path to a big table.
Not a stroll in the park, I agree, but promising ...
Happy playing -
Marco the Sane

Query a SQL Database & Edit the found data set

I know this question has probably been asked before I just can't manage to get mine going. I set up my SQL to have two tables but in this instance I will only be using one called 'Book'. It has various columns but the ones I want to work with is called 'WR', 'Customer', 'Contact', 'Model', 'SN', 'Status', 'Tech', 'WDone' and 'IN'.
I want to enter text into a editbox called edtWR and I want the button btnSearch to search the 'WR' column until it has a match (all of the entries will be different). Once it has that it must write 'Customer', 'Contact', 'Model', 'SN', 'Status' to labels, lets call them lblCustomer lblContact lblModel lblSN & lblStatus.
Once the person has verified that that is the 'WR' that they want the must enter text into edit boxes and one memo called edtTech, mmoWDone and edtIN and click on btnUpdate. that should then update that record.
I have 3 ADO Connections on called dtbOut thats my ADOConnection1, tableOut thats my ADOTable and dataOut thats by ADODataSet. dataOut's command text is Select * From Book if it helps.
I can get the whole process to work perfectly on a access database but with almost no experience on SQL I need help. I will add code for the Access database in case it is needed for reference.
procedure TFOut.btnSearchClick(Sender: TObject);
begin
dataout.Filter := 'WR = ''' + 'WR ' + edtwr.Text + '''';
dataout.Filtered := True;
dataout.First;
lblcustomer.Caption := 'Customer: ' + dataout.FieldByName('Customer').AsString;
lblcontact.Caption := 'Contact: ' + dataout.FieldByName('Contact').AsString;
lblSN.Caption := 'SN: ' + dataout.FieldByName('SN').AsString;
lblModel.Caption := 'Model: ' + dataout.FieldByName('Model').AsString;
lblstatus.Caption := 'Status: ' + dataout.FieldByName('Status').AsString;
procedure TFOut.btnUpdateClick(Sender: TObject);
begin
dataout.Edit;
dataout.FieldByName('Tech').AsString := edtTech.Text;
dataout.FieldByName('WDone').AsString := mmoWDone.Lines.GetText;
dataout.FieldByName('IN').AsString := edtIN.Text;
dataout.Post;
end
Do I need any additional components on my form for me to be able to do this in SQL, what do I need and how do I even start. Ive read a lot of things and it seems line I will need to get a ADOQuery1 but when it comes to the ADOQuery1.SQL part I fall off the wagon. I have also tried it the Access way and I can search but as soon as I try to update I get a "Insufficient key column information for updating or refreshing" Error, witch I also have no idea how to address.
If I need to state the question otherwise, please explain how to change to make it more clear and if I need to add anything in the whole explanation or code, please inform me of what.
SO isn't really the place for database tutorials, so I'm not going to
attempt one but instead focus on one basic thing that it's crucial to understand and get right in your database design before you even begin
to write a Delphi db app. (I'm going to talk about this in terms of
Sql Server, not MS Access.)
You mentioned getting an error "Insufficient key column information for updating or refreshing" which you said you had no idea how to address.
A Delphi dataset (of any sort, not just an ADO one) operates by maintaining a logical cursor which which points at exactly one row in the dataset. When you open a (non-empty) dataset, this cursor is pointing at the first row in the dataset and you can move the cursor around using various TDataSet methods such as Next & Prior, First, Last and MoveBy. Some, but not all, types of TDataSet implement its Locate method which enables you to go to a row which matches criteria you specify, other types, not. Delphi's ADO components do implement Locate (btw, Locate operates on rows you're already retrieved from the server, it's not for finding rows on the server).
One of the key ideas of Sql-oriented TDataSets such as TAdoQuery is that you can leave it to automatically generate Sql statements to do Updates, Deletes and Inserts. This is not only a significant productivity aid, but it avoids coding errors and omissions when you try to do it yourself.
If you observe ADO doing its stuff against an MS Sql Server table using SS's Profiler utility, then with a well-designed database, you'll find that it does this quite nicely and efficiently provided the database design follows one cardinal rule, namely that there must be a way to uniquely identify a particular row in a table. The most common way to do this is to include in each table, usually as the first column, an int(eger) ID column, and to define it as the "Primary key" of the table. Although there are other methods to generate a suitable ID value to go in this column, Sql Server has a specific column type, 'Identity' which takes care of this on the server.
Once a table has such a column, the ADO layer (which is a data-access layer provided by Windows that dataset components such as TAdoQuery sit upon) can automatically generate Sql statements to do Updates and Deletes, e.g.
Delete from Table1 where Table1ID = 999
and
Update Table1 set SomeCharField = 'SomeValue' where Table1ID = 666
and you can leave it to the AdoQuery to pick up the ID value for a newly-inserted row from the server.
One of the helpful aspects of leaving the Sql to be generated automatically is that it ensures that the Sql only affects a single row and so avoids affecting more rows than you intend.
Once you've got this key aspect of your database design correct, you'll find that Delphi's TDataSet descendants such as TAdoQuery and its DB-aware components can deal with most simple database applications without you having to write any Sql statements at all to update, insert or delete
rows. Usually, however, you do still need to write Sql statements to retrieve the rows you want from the server by using a 'Where' clause to restrict the rows retrieved to a sub-set of the rows on the server.
Maybe your next step should be to read up on parameterized Sql queries, to reduce your exposure to "Sql Injection":
https://en.wikipedia.org/wiki/SQL_injection
as it's best to get into the habit of writing Sql queries using parameters. Btw, Sql Injection isn't just about Sql being intercepted and modified when it's sent over the internet: there are forms of injection where a malicious user who knows what they're doing can simply type in some extra Sql statements where the app "expects" them simply to specify some column value as a search criterion.

How do I write an SQL function that takes an unknown amount of numbers as parameters?

I am trying to write an Oracle SQL function that takes a list of numbers as arguments and return a pipelined list of table rows. My main problem is the quantity of numbers that can be passed is never certain with no real upper limit. I'll try and demonstrate what I mean:
Say I have a table defined as so:
create table items (
id number primary key,
class number,
data string
);
I want to return all rows that match one of a list of class numbers that I submit. The function I'm shooting at looks a little like this:
function get_list_items_from_class([unknown number of parameters]
in items.class%type)
return tbl_list_item pipelined; -- I have types defined to handle the return values
I've been looking at ways to handle defining a function that can take an undefined amount of integers and so far the most promising search has taken me to this page which explains about using collections and records. I don't think a VARRAY is what I'm looking for as the size has to be predefined. As Associative Array may be what I'm looking for, but before I spend a lot of time trying things out, I want to make sure the tool is fit for the job. I'm pretty inexperienced with Oracle SQL right now and I'm working on a time sensitive project.
Any help that you could offer would be appreciated. I realise that there are simpler ways to achieve what I'm trying to do in this example (simply multiple calls to a function that takes one parameter is one) but this example is simplified. Other parts of the project I'm working on require me to seek a solution using this multiple parameter method.
EDIT: That being said, I would welcome other design suggestions if I'm way off base with what I'm trying to attempt. It would be a learning experience if nothing else.
Many thanks in advance for your time.
EDIT: I will be accessing the database from proprietary client software written in Java.
You could use a table parameter as I linked in the comments or you could pass in a comma separated list of values parse it to a table and join to that.
something like this (with input_lst as a string):
select *
from tbl_list_item
where tbl_list_item.class in
(
select regexp_substr(input_lst,'[^,]+', 1, level) from dual
connect by regexp_substr(input_lst, '[^,]+', 1, level) is not null
);
adapted from https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
Which choice is better depends on your expected number of entries and what is easier for your client side. I think with a small number (2-20) the comma separated is a fine choice. With a large number you probably want to pass a table.
A colleague actually suggested another way to achieve this and I think it is worth sharing. Basically, define a table type that can contain the arguments, then pass an array from the Java program that can be read from this table.
In my example, firstly define a table type of number:
create or replace type tbl_number as table of number;
Then, in the SQL package, define the function as:
function get_list_items_from_class(i_numbers in tbl_number)
return tbl_list_item pipelined;
The function in the package body has one major change (apart from the definition obviously). Use the following select statement:
select *
from tbl_list_item
where tbl_list_item.class in
(
select * from table(i_numbers)
);
This will select all the relevant items that match one of the integers that were passed to the "i_numbers" table. I like this way as it means less string parsing, both in the Java application and the SQL pacakage.
Here's how I passed the number arguments from the Java application using an ARRAY object.
ArrayDescriptor arrayDesc = ArrayDescriptor.createDescriptor("NUMBERS", con); //con is the database connection
ARRAY array = new ARRAY(arrayDesc, con, numberList.toArray()); // numberList is an ArrayList of integers which holds the arguments
array is then passed to the SQL function.

Hide Empty columns

I got a table with 75 columns,. what is the sql statement to display only the columns with values in in ?
thanks
It's true that a similar statement doesn't exist (in a SELECT you can use condition filters only for the rows, not for the columns). But you could try to write a (bit tricky) procedure. It must check which are the columns that contains at least one not NULL/empty value, using queries. When you get this list of columns just join them in a string with a comma between each one and compose a query that you can run, returning what you wanted.
EDIT: I thought about it and I think you can do it with a procedure but under one of these conditions:
find a way to retrieve column names dynamically in the procedure, that is the metadata (I never heard about it, but I'm new with procedures)
or hardcode all column names (loosing generality)
You could collect column names inside an array, if stored procedures of your DBMS support arrays (or write the procedure in a programming language like C), and loop on them, making a SELECT each time, checking if it's an empty* column or not. If it contains at least one value concatenate it in a string where column names are comma-separated. Finally you can make your query with only not-empty columns!
Alternatively to stored procedure you could write a short program (eg in Java) where you can deal with a better flexibility.
*if you check for NULL values it will be simple, but if you check for empty values you will need to manage with each column data type... another array with data types?
I would suggest that you write a SELECT statement and define which COLUMNS you wish to display and then save that QUERY as a VIEW.
This will save you the trouble of typing in the column names every time you wish to run that query.
As marc_s pointed out in the comments, there is no select statement to hide columns of data.
You could do a pre-parse and dynamically create a statement to do this, but this would be a very inefficient thing to do from a SQL performance perspective. Would strongly advice against what you are trying to do.
A simplified version of this is to just select the relevant columns, which was what I needed personally. A quick search of what we're dealing with in a table
SELECT * FROM table1 LIMIT 10;
-> shows 20 columns where im interested in 3 of them. Limit is just to not overflow the console.
SELECT column1,column3,colum19 FROM table1 WHERE column3='valueX';
It is a bit of a manual filter but it works for what I need.