How to join var array values - sql

SELECT
person.bu_id,
key.key_val,
obj_person_add.person_contact_freq,
obj_person_add.person_contact_best,
OBJ_PERSON_ADD.PERSON_CONTACT_INST,
OBJ_PERSON_ADD.PERSON_CONTACT_DATE,
OBJ_PERSON_ADD.PERSON_BROCHURES,
OBJ_PERSON_ADD.PERSON_NEWSLETTER
FROM obj_person person
INNER JOIN obj_person_add
ON person.obj_id = obj_person_add.obj_id
LEFT JOIN obj_rel_key key
ON key.obj_id = person.obj_id
WHERE person.bu_id in (6,7)
AND person.close_date IS NULL
AND key.obj_key_id = 806
AND (
obj_person_add.person_contact_freq IS NOT NULL
OR obj_person_add.person_contact_best IS NOT NULL
OR OBJ_PERSON_ADD.PERSON_CONTACT_INST IS NOT NULL
OR OBJ_PERSON_ADD.PERSON_CONTACT_DATE IS NOT NULL
OR OBJ_PERSON_ADD.PERSON_BROCHURES IS NOT NULL
OR OBJ_PERSON_ADD.PERSON_NEWSLETTER IS NOT NULL
);
I have built this query, however person_brochures and person_newsletters are stored in a var array. This means that they return (6172,6544...) for example. I want to do a left join to match the ID's in the var array to a name in a different table.
Do I have to loop the var array and somehow match the ID's then?

You might try something called collection unnesting:
SELECT
person.bu_id,
...
OBJ_PERSON_ADD.PERSON_BROCHURES,
OBJ_PERSON_ADD.PERSON_NEWSLETTER
FROM obj_person person
INNER JOIN obj_person_add
ON person.obj_id = obj_person_add.obj_id
...,
TABLE(OBJ_PERSON_ADD.PERSON_BROCHURES) x
INNER JOIN your_other_table
ON x.column_value = your_other_table.id
...
column_value is the name of Oracle pseudocolumn that is assigned to the VARRAY elements.

I am not 100% clear, I am guessing that you are writing this code in C# .Net.
You need to decide where the problem is going to be solved in , are you going to solve it in your App tier or persistence tier ?
I am assuming that you want to solve it in the persistence tier , you have couple of options
1. Generate a prepared statement and execute the statement ..instead of join you will have to rely on IN operator ..etc
Use a stored procedure , pass the two arrays as comma separated string to a stored procedure
you can look at this example if you need to solve it in Oracle function ..so you can do a join as you originally thought.
Splitting string into multiple rows in Oracle
use oracle object relational features and map the data structure appropriately to a structure and use oracle MULTISET like collections to solve the query in a stored procedure ..etc

Related

Oracle Invalid Number in Join Clause

I am getting an Oracle Invalid Number error that doesn't make sense to me. I understand what this error means but it should not be happening in this case. Sorry for the long question, but please bear with me so I can explain this thoroughly.
I have a table which stores IDs to different sources, and some of the IDs can contain letters. Therefore, the column is a VARCHAR.
One of the sources has numeric IDs, and I want to join to that source:
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
In most cases this works, but depending on random things such as what columns are in the select clause, if it uses a left join or an inner join, etc., I will start seeing the Invalid Number error.
I have verified multiple times that all entries in AGG_MATCHES where AGGSRC = 'source_a' do not contain non numeric characters in the AGGPROJ_ID column:
-- this returns no results
SELECT AGGPROJ_ID
FROM AGG_MATCHES
WHERE AGGSRC = 'source_a' AND REGEXP_LIKE(AGGPROJ_ID, '[^0-9]');
I know that Oracle basically rewrites the query internally for optimization. Going back to the first SQL example, my best guess is that depending on how the entire query is written, in some cases Oracle is trying to perform the JOIN before the sub query. In other words, it's trying to join the entire AGG_MATCHES tables to SOURCE_A instead of just the subset returned by the sub query. If so, there would be rows that contain non numeric values in the AGGPROJ_ID column.
Does anyone know for certain if this is what's causing the error? If it is the reason, is there anyway for me to force Oracle to execute the sub query part first so it's only trying to join a subset of the AGG_MATCHES table?
A little more background:
This is obviously a simplified example to illustrate the problem. The AGG_MATCHES table is used to store "matches" between different sources (i.e. projects). In other words, it's used to say that a project in sourceA is matched to a project in sourceB.
Instead of writing the same SQL over and over, I've created views for the sources we commonly use. The idea is to have a view with two columns, one for SourceA and one for SourceB. For this reason, I don't want to use TO_CHAR on the ID column of the source table, because devs would have to remember to do this every time they are doing a join, and I'm trying to remove code duplication. Also, since the ID in SOURCE_A is a number, I feel that any view storing SOURCE_A.ID should go ahead and convert it to a number.
You are right that Oracle is executing the statement in a different order than what you wrote, causing conversion errors.
The best ways to fix this problem, in order, are:
Change the data model to always store data as the correct type. Always store numbers as numbers, dates as dates, and strings as strings. (You already know this and said you can't change your data model, this is a warning for future readers.)
Convert numbers to strings with a TO_CHAR.
If you're on 12.2, convert strings to numbers using the DEFAULT return_value ON CONVERSION ERROR syntax, like this:
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID default null on conversion error);
Add a ROWNUM to an inline view to prevent optimizer transformations that may re-write statements. ROWNUM is always evaluated at the end and it forces Oracle to run things in a certain order, even if the ROWNUM isn't used. (Officially hints are the way to do this, but getting hints right is too difficult.)
SELECT *
FROM (
SELECT AGGPROJ_ID -- this column is a VARCHAR
FROM AGG_MATCHES -- this is the table storing the matches
WHERE AGGSRC = 'source_a'
--Prevent optimizer transformations for type safety.
AND ROWNUM >= 1
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
I think the simplest solution uses case, which has more guarantees on the order of evaluation:
SELECT a.*
FROM AGG_MATCHES m JOIN
SOURCE_A a
ON a.ID = (CASE WHEN m.AGGSRC = 'source_a' THEN TO_NUMBER(m.AGGPROJ_ID) END);
Or, better yet, convert to strings:
SELECT a.*
FROM AGG_MATCHES m JOIN
SOURCE_A a
ON TO_CHAR(a.ID) = m.AGGPROJ_ID AND
m.AGGSRC = 'source_a' ;
That said, the best advice is to fix the data model.
Possibly the best solution in your case is simply a view or a generate column:
create view v_agg_matches_a as
select . . .,
(case when regexp_like(AGGPROJ_ID, '^[0-9]+$')
then to_number(AGGPROJ_ID)
end) as AGGPROJ_ID
from agg_matches am
where m.AGGSRC = 'source_a';
The case may not be necessary if you use a view, but it is safer.
Then use the view in subsequent queries.

Changing data types and using LIKE in LEFT OUTER JOIN

I an using a database that in my opinion was not set up in the most intuitive way. I have two tables I am trying to join. The store table has info about the store and has a store_id field that is stored as a 7 digit number in the varchar format. The transaction table has a transaction_id field that is set up as hstore and stored as "package_id"=>"10000417". I am trying to join the two tables on these fields.
I have attempted to cast the transaction_id field as a varchar, at the same time tried to use a subquery in the join statement to add the "package_id"=>" portion to the store_id field. Yielding no results. So I tried to use LIKE in the JOIN statement to add wildcards. Still having problems. Here is the code I tried when using LIKE:
SELECT t.amount
,t.quantity_sold
,t.unit
,cast(t.transaction_details as varchar) as join_field
,s.product_name
,s.product_category
,s.metrc_id
FROM transactions t
LEFT JOIN store_info s ON t.transaction_details = s.store_id
WHERE t.transaction_details LIKE '%store_id%'
I have tried a number of other variations. I am hoping someone can provide a hint for me on a better direction to go with trying to link these two tables.
Thanks in advance,
EDIT
I am used to using SQL Server as opposed to Postgresql. I have since done more research on the hstore data type and understand it slightly better. I am still having trouble joining the two tables. I managed to create a new column with only the ID number by using the following query.
SELECT *, transaction_details->'package_id' as ID
FROM transactions
I tried to JOIN by then calling that column to no avail, so I tried the following:
SELECT *
FROM transactions t
LEFT JOIN store_info s ON t.transaction_details->'package_id' = s.store_id
I also tried to cast to integer or varchar using the following
SELECT *
FROM transactions t
LEFT JOIN store_info s ON cast(t.transaction_details->'package_id'
as varchar) = s.store_id
Sorry for being such a noob. Thanks for helping.
Your code doesn't work for two reasons.
The store id and transaction details don't match, by your descriptions of the values in these columns.
The like pattern is simply a string that looks like '%store_id%'. The column value is not in the string (see below on how to do this).
You need something like this:
SELECT . . .
FROM transactions t LEFT JOIN
store_info s
ON t.transaction_details LIKE '%' || s.store_id || '%';
However, I doubt that will do want you want. The store id should be at the beginning or end of transaction details, suggesting:
ON t.transaction_details LIKE '%' || s.store_id
or:
ON t.transaction_details LIKE s.store_id || '%'
And, you are correct that this is a very bad data structure. At the very least, the ETL process should be parsing out the store information from the transaction.

What is the difference between CROSS JOIN and CROSS JOIN TABLE

In the following code :
SELECT ... FROM ... CROSS JOIN TABLE ... WHERE ...;
What does CROSS JOIN TABLE mean ?
I searched on the net, but all I can find is about CROSS JOIN only.
I suppose CROSS JOIN TABLE acts on a table, meaning a table like that :
CREATE OR REPLACE ... AS TABLE OF ...;
Here is the full query :
SELECT prog.id_oct_prog_tran_a_participati, prog.code_ressource, prog.instant_depart, prog.commentaire, prog.en_hors_economie_de_comb, discr.delai, discr.valeur_point, MOD(delai, 60) AS H24
FROM req_prog prog
CROSS JOIN TABLE(POINTS_DISCRETS(pIdChronique=>id_chr_substitution)) discr
WHERE horizon <= 'J1'
AND delai > 0
ORDER BY id_oct_prog_tran_a_participati, instant_depart, horizon, delai
POINTS_DISCRETS is a function that returns an element of type TYPE_TAB_POINT. And TYPE_TAB_POINT is a type that the DBA created as following :
create or replace TYPE "TYPE_TAB_POINT" AS TABLE OF TYPE_POINT;
Where TYPE_POINT is a type created as following :
create or replace TYPE "TYPE_POINT" AS OBJECT
(
ID_CHRONIQUE NUMBER,
HORIZON VARCHAR2(2),
NUM_POINT NUMBER(4),
DELAI NUMBER(5),
VALEUR_POINT FLOAT
);
So, as you see here, CROSS JOIN TABLE acts on a table but not a table as we usually mean in a database, more a table like an array.
Is it the case ? And, if yes, how can it be considered like a real table ?
You're parsing the statement incorrectly, which is confusing you I think. CROSS JOIN TABLE isn't one fragment; it's aCROSS JOIN (but could be any join type) between your real table req_prog and, separately, TABLE(...) to tell the query to treat the return value of the function call as a table.
TABLE(...) is the table collection expression:
The table_collection_expression lets you inform Oracle that the value of collection_expression should be treated as a table for purposes of query and DML operations. The collection_expression can be a subquery, a column, a function, or a collection constructor.
Here POINTS_DISCRETS is the collection constructor - returning TYPE_TAB_POINT, which is a collection, most of which you already figured out.
In this case your collection is a table of objects, and treating it as a table allows you both to join and to refer to the attributes of the object that forms each 'row' as if it were a column. The TABLE() is aliased as discr, so you are able to refer to discr.delai - where delai is one of the attributes of the TYPE_POINT type. If you were using an inner or outer join, rather than a cross join, you would use the same construction in the join condition, e.g. ON discr.x = prog.x. Once you apply the table collection expression you just treat it as a table in the rest of the statement.

Pass In "WHERE" parameters to PostgreSQL View?

I have a rather complicated query on my PostgreSQL database spanning 4 tables via a series of nested subqueries. However, despite the slightly tricky looking appearance and setup, ultimately it will return two columns (from the same table, if that helps the situation) based on that matching of two external parameters (two strings need to match with fields in different tables). I'm fairly new to database design in PostgreSQL, so I know that this seemingly magical thing called Views exist, and that seems like it could help me here, but perhaps not.
Is there some way I can move my complex query inside a view and somehow just pass it the two values I need to match? That would greatly simplify my code on the front-end (by shifting the complexities to the database structure). I can create a view that wraps my static example query, and that works just fine, however that only works for one pair of string values. I need to be able to use it with a variety of different values.
Thus my question is: is it possible to pass parameters into an otherwise static View and have it become "dynamic"? Or perhaps a View is not the right way to approach it. If there's something else that would work better, I'm all ears!
*Edit: * As requested in comments, here's my query as it stands now:
SELECT param_label, param_graphics_label
FROM parameters
WHERE param_id IN
(SELECT param_id
FROM parameter_links
WHERE region_id =
(SELECT region_id
FROM regions
WHERE region_label = '%PARAMETER 1%' AND model_id =
(SELECT model_id FROM models WHERE model_label = '%PARAMETER 2%')
)
) AND active = 'TRUE'
ORDER BY param_graphics_label;
Parameters are set off by percent symbols above.
You could use a set returning function:
create or replace function label_params(parm1 text, parm2 text)
returns table (param_label text, param_graphics_label text)
as
$body$
select ...
WHERE region_label = $1
AND model_id = (SELECT model_id FROM models WHERE model_label = $2)
....
$body$
language sql;
Then you can do:
select *
from label_params('foo', 'bar')
Btw: are you sure you want:
AND model_id = (SELECT model_id FROM models WHERE model_label = $2)
if model_label is not unique (or the primary key) then this will throw an error eventually. You probably want:
AND model_id IN (SELECT model_id FROM models WHERE model_label = $2)
In addition to what #a_horse already cleared up, you could simplify your SQL statement with JOIN syntax instead of nested subqueries. Performance will be similar, but the syntax is much shorter and easier to manage.
CREATE OR REPLACE FUNCTION param_labels(_region_label text, _model_label text)
RETURNS TABLE (param_label text, param_graphics_label text)
LANGUAGE sql AS
$func$
SELECT p.param_label, p.param_graphics_label
FROM parameters p
JOIN parameter_links l USING (param_id)
JOIN regions r USING (region_id)
JOIN models m USING (model_id)
WHERE p.active
AND r.region_label = $1
AND m.model_label = $2
ORDER BY p.param_graphics_label;
$func$;
If model_label is not unique or something else in the query produces duplicate rows, you may want to make that SELECT DISTINCT p.param_graphics_label, p.param_label - with a matching ORDER BY clause for best performance. Or use GROUP BY.
Since Postgres 9.2 you can use the declared parameter names in place of $1 and $2 in SQL functions. (Has been possible for PL/pgSQL functions for a long time).
To avoid naming conflicts, I prefix parameter names with _ (those are visible most everywhere inside the function) and table-qualify column names in queries.
I simplified WHERE p.active = 'TRUE' to WHERE p.active, assuming the column active is type boolean.
USING in the JOIN condition only works if the column names are unambiguous across all tables to the left. Else use the more explicit syntax: ON l.param_id = p.param_id
In most cases the set-returning function is the way to go, but in the event that you want to both read and write to the set, a view may be more appropriate. And it is possible for a view to read a session parameter:
CREATE VIEW widget_sb AS
SELECT * FROM widget
WHERE column = cast(current_setting('mydomain.myparam') as int)
SET mydomain.myparam = 0
select * from widget_sb
[results]
SET mydomain.myparam = 1
select * from widget_sb
[distinct results]
I don't think a "dynamic" view as you stated is possible.
Why not write a stored procedure that takes 2 arguments instead?
I would rephrase the query as the following:
SELECT p.param_label, p.param_graphics_label
FROM parameters p
where exists (
select 1
from parameter_links pl
where pl.parameter_id = p.id
and exists (select 1 from regions r where r.region_id = pl.region_id
) and p.active = 'TRUE'
order by p.param_graphics_label;
Assuming that you have indexes on the various id columns, this query should be significantly faster than using the IN operator; the exists parameters here will use only the index values without even touching the data table except for getting the final data from the parameters table.

SQL syntax to select an entire table with values replaced with values in other tables

I have an SQLite DB with one table that holds IDs for several strings, like so:
id sym_const
1 play
2 go
3 fill
4 say
The DB also has several tables other with several columns of integer data, with the integers being IDs corresponding to strings in the above table:
attr val_const
1 3
4 2
I need to do a query to grab the rows from the second table, but replacing the integers with their corresponding strings from the first table, like so:
attr val_const
play fill
say go
How would I do this? FYI, this is for exploring the semantic memory database of a Soar application.
Assuming Table Names:
SELECT attr = m1.sym_const,
val_const = m2.sym_const
FROM SecondTable s
JOIN MainTable m1 on m1.id = s.attr
JOIN MainTable m2 on m2.id = s.val_const
Use a JOIN or a LEFT JOIN. Look up SQL syntax documentation if you're not sure what that means.