Trying to replace a Cross Apply with a JOIN - sql

We have a table, ProductHierarchy, which is set. The # of rows will not change. It has simple parent/child Product Hierarchy data in it.
We also have a Table-valued function which takes a ProductHierarchyId and then returns all rows where IsDescendantOf is true for that Id. Or in other words, it returns that row, plus all of its ancestors.
The problem is that with this Table-valued function, we have to use it with CROSS APPLY, and this is seriously slowing down the query.
My thought is to create either a second permanent table (or a temp table/table variable in the query in question) that has all possible results from the Table-valued function already in it. And then JOIN to that table instead of using the CROSS APPLY. In this table, we would add a column called something like QueryID. So instead of
CROSS APPLY dbo.GetProductLevels(P.ProductHierarchyId)
We could use
LEFT JOIN FutureTable ft ON ft.QueryId = P.ProductHierarchyId
I'm struggling with the query to create that table. Here's what I have so far...
SELECT
*
, 1 AS QueryId
FROM
dbo.ProductHierarchy
WHERE
(SELECT ProductHierarchyNode FROM dbo.ProductHierarchy WHERE ProductHierarchyId = 1).IsDescendantOf(ProductHierarchyNode) = 1
Ok, so that works great for the record where ProductHierarchyId = 1. But then I'd need to repeat that for ProductHierarchyId = 2:
SELECT
*
, 2 AS QueryId
FROM
dbo.ProductHierarchy
WHERE
(SELECT ProductHierarchyNode FROM dbo.ProductHierarchy WHERE ProductHierarchyId = 2).IsDescendantOf(ProductHierarchyNode) = 1
And then for 3, and then for 4, all the way to the last Id, doing a UNION each time, inside a loop -- which is hideous.
I KNOW there is a way to do this all in one query. Something like a recursive CTE. But my brain isn't getting there.

Wouldn't you just do this?
SELECT . . . -- the columns you want
INTO . . . -- where you want them
FROM dbo.ProductHierarchy ph CROSS APPLY
dbo.GetProductLevels(P.ProductHierarchyId);

Related

Select rows according to another table with a comma-separated list of items

Have a table test.
select b from test
b is a text column and contains Apartment,Residential
The other table is a parcel table with a classification column. I'd like to use test.b to select the right classifications in the parcels table.
select * from classi where classification in(select b from test)
this returns no rows
select * from classi where classification =any(select '{'||b||'}' from test)
same story with this one
I may make a function to loop through the b column but I'm trying to find an easier solution
Test case:
create table classi as
select 'Residential'::text as classification
union
select 'Apartment'::text as classification
union
select 'Commercial'::text as classification;
create table test as
select 'Apartment,Residential'::text as b;
You don't actually need to unnest the array:
SELECT c.*
FROM classi c
JOIN test t ON c.classification = ANY (string_to_array(t.b, ','));
db<>fiddle here
The problem is that = ANY takes a set or an array, and IN takes a set or a list, and your ambiguous attempts resulted in Postgres picking the wrong variant. My formulation makes Postgres expect an array as it should.
For a detailed explanation see:
How to match elements in an array of composite type?
IN vs ANY operator in PostgreSQL
Note that my query also works for multiple rows in table test. Your demo only shows a single row, which is a corner case for a table ...
But also note that multiple rows in test may produce (additional) duplicates. You'd have to fold duplicates or switch to a different query style to get de-duplicate. Like:
SELECT c.*
FROM classi c
WHERE EXISTS (
SELECT FROM test t
WHERE c.classification = ANY (string_to_array(t.b, ','))
);
This prevents duplication from elements within a single test.b, as well as from across multiple test.b. EXISTS returns a single row from classi per definition.
The most efficient query style depends on the complete picture.
You need to first split b into an array and then get the rows. A couple of alternatives:
select * from nj.parcels p where classification = any(select unnest(string_to_array(b, ',')) from test)
select p.* from nj.parcels p
INNER JOIN (select unnest(string_to_array(b, ',')) from test) t(classification) ON t.classification = p.classification;
Essential to both is the unnest surrounding string_to_array.

Pass dynamic parameters into a Table Valued Function [duplicate]

I have a table-value function that takes an ID number of a person and returns a few rows and columns. In another query, I am creating a SELECT that retrieves a lot of information about many people. How can I pass an id number from my main query to my function to sum a column and join it to my main query? I wish I didn't have a table value function since that would work easily, however, this function is used elsewhere and I'd like to reuse it. Perhaps this isn't even possible with a table-value function and I need to create a scalar one.
My main Query looks like this:
select id_num, name, balance
from listOfPeople
And the table-value function looks like this:
calculatePersonalDiscount(id_number)
I would like to do something like:
select id_num, name, balance
from listOfPeople
left join
(
SELECT id_num, SUM(discount)
FROM calculatePersonalDiscount(listOfPeople.id_num)
) x ON x.id_num = listOfPeople.id_num
But you can't pass listOfPeople.id_num into the function since it's not really the same scope.
In SQL Server 2005 you can use the CROSS APPLY syntax:
select id_num, name, balance, SUM(x.discount)
from listOfPeople
cross apply dbo.calculatePersonalDiscount(listOfPeople.id_num) x
Likewise there's an OUTER APPLY syntax for the equivalent of a LEFT OUTER join.
I needed this badly and you gave me the start, and here I was able to JOIN on another View. (another table would work too), and I included another function in the WHERE clause.
SELECT CL_ID, CL_LastName, x.USE_Inits
FROM [dbo].[tblClient]
JOIN dbo.vw_InsuranceAppAndProviders ON dbo.vw_InsuranceAppAndProviders.IAS_CL_ID = tblClient.CL_ID
CROSS APPLY dbo.ufx_HELPER_Client_CaseWorker(tblClient.CL_ID) x
WHERE dbo.vw_InsuranceAppAndProviders.IAS_CL_ID = tblClient.CL_ID
AND dbo.ufx_Client_IsActive_By_CLID(tblClient.CL_ID) = 1
AND (dbo.vw_InsuranceAppAndProviders.IAS_InsuranceType = 'Medicare')

how to use listagg operator so that the query should fetch comma separated values

SELECT (SELECT STRING_VALUE
FROM EMP_NODE_PROPERTIES
WHERE NODE_ID=AN.ID ) containedWithin
FROM EMP_NODE AN
WHERE AN.STORE_ID = ALS.ID
AND an.TYPE_QNAME_ID=(SELECT ID
FROM EMP_QNAME
where LOCAL_NAME = 'document')
AND
AND AN.UUID='13456677';
from the above query I am getting below error.
ORA-01427: single-row subquery returns more than one row
so how to change the above query so that it should fetch comma separated values
This query won't return error you mentioned because
there are two ANDs and
there's no ALS table (or its alias).
I suggest you post something that is correctly written, then we can discuss other errors.
Basically, it is either select string_value ... or select id ... (or even both of them) that return more than a single value.
The most obvious "solution" is to use select DISTINCT
another one is to include where rownum = 1
or, use aggregate functions, e.g. select max(string_value) ...
while the most appropriate option would be to join all tables involved and decide which row (value) is correct and adjust query (i.e. its WHERE clause) to make sure that desired value will be returned.
You would seem to want something like this:
SELECT LISTAGG(NP.STRING_VALUE, ',') WITHIN GROUP(ORDER BY NP.STRING_VALUE)
as containedWithin
FROM EMP_NODE N
JOIN EMP_QNAME Q
ON N.TYPE_QNAME_ID = Q.ID
LEFT JOIN EMP_NODE_PROPERTIES NP
ON NP.NODE_ID = N.ID
WHERE Q.LOCAL_NAME = 'document'
AND AN.UUID = '13456677';
This is a bit speculative because your original query would not run for the reason explained by Littlefoot.

Array field in postgres, need to do self-join with results

I have a table that looks like this:
stuff
id integer
content text
score double
children[] (an array of id's from this same table)
I'd like to run a query that selects all the children for a given id, and then right away gets the full row for all these children, sorted by score.
Any suggestions on the best way to do this? I've looked into WITH RECURSIVE but I'm not sure that's workable. Tried posting at postgresql SE with no luck.
The following query will find all rows corresponding to the children of the object with id 14:
SELECT *
FROM unnest((SELECT children FROM stuff WHERE id=14)) t(id)
JOIN stuff USING (id)
ORDER BY score;
This works by finding the children of 14 as array first, then we convert it into a table using the unnest function, and then we join with stuff to find all rows with the given ids.
The ANY construct in the join condition would be simplest:
SELECT c.*
FROM stuff p
JOIN stuff c ON id = ANY (p.children)
WHERE p.id = 14
ORDER BY c.score;
Doesn't matter for the query whether the array of children IDs is in the same table or different one. You just need table aliases here to be unambiguous.
Related:
Check if value exists in Postgres array
Similar solution:
With Postgres you can use a recursive common table expression:
with recursive rel_tree as (
select rel_id, rel_name, rel_parent, 1 as level, array[rel_id] as path_info
from relations
where rel_parent is null
union all
select c.rel_id, rpad(' ', p.level * 2) || c.rel_name, c.rel_parent, p.level + 1, p.path_info||c.rel_id
from relations c
join rel_tree p on c.rel_parent = p.rel_id
)
select rel_id, rel_name
from rel_tree
order by path_info;
Ref: Postgresql query for getting n-level parent-child relation stored in a single table

Compare Items in the "IN" Clause and the resultset

I'd like to achieve something as follows, I have the following query (As simple as this),
SELECT ENT_ID,TP_ID FROM TC_LOGS WHERE ENT_ID IN (1,2,3,4,5).
Now the table TC_LOGS may not have all the items in the IN clause. So assuming that the table TC_LOGS has only 1,2. I'd like to compare the items in the IN clause i.e. 1,2,3,4,5 with 1,2(the resultset) and get a result as FOUND - 1,2 NOT FOUND - 3,4,5. I've have implemented this by applying an XSL transformation on the resultset in the application code, but I'd like to achieve this in a query, which I feel is more of an elegant solution to this problem. Also, I tried the following query with NVL, just to separate out the FOUND and NOT FOUND items as,
SELECT NVL(ENT_ID,"NOT FOUND") FROM TC_LOGS WHERE ENT_ID IN(1,2,3,4,5)
I was expecting a result as 1,2,NOT FOUND,NOT FOUND,NOT FOUND
But the above query doesn't return any result.. I'd appreciate if someone can guide me in the right path here.. Thanks much in advance.
Assuming that the items in your IN list can (or can come) from another query, you can do something like
WITH src AS (
SELECT level id
FROM dual
CONNECT BY level <= 5)
SELECT nvl(ent_id, 'Not Found' )
FROM src
LEFT OUTER JOIN tc_logs ON (src.id = tc_logs.ent_id)
In my case, the src query is just generating the numbers 1 through 5. You could just as easily fetch that data from a different table, load the numbers into a collection that you query using the TABLE operator, load the numbers into a temporary table that you query, etc. depending on how the IN list data is determined.
NVL isn't going to work because no values (including NULLS) are returned when there is no match with the IN statement.
What you can do is something like this:
SELECT NVL(ENT_ID, "NOT FOUND")
FROM TC_LOGS
RIGHT OUTER JOIN (
SELECT 1 AS 'TempID' UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5) AS Sub ON ENT_ID = TempID
The outer join will return NULLS for ENT_ID where there are no matches. Note, I'm not an Oracle person so I can't guarantee that this syntax is perfect.
if you have a table (let's use table src )contains all (1,2,3,4,5) values, you can use full join.
You can use (WITH src AS ( SELECT level id FROM dual CONNECT BY level <= 5) as the src table also)
SELECT
ent_id,tl.tp_id,src.tp_id
FROM
src
FULL JOIN
tc_logs tl
USING (ent_id)
ORDER BY
ent_id
Here is the web site for oracle full join.http://psoug.org/snippet/Oracle-PL-SQL-ANSI-Joins-FULL-JOIN_738.htm