reach variable in connect by PLSQL - sql

can anybody explain me why i get invalid identifier error and why cannot be variable z.cupu reachable?
ORA-00904: "Z"."CUPU": invalid identifier
select
(select listagg(text, ', ') within group (order by kod)
from cis_chyby_pu_na_uss
where kod in (
select
regexp_substr(t.stav_full, '[^,]+', 1, l.lev) split
from PREKLAPANIE_PU_NA_USS_HIST t, (select rownum as lev
from dual
connect by level <= length (trim(regexp_replace((select stav_full
from PREKLAPANIE_PU_NA_USS_HIST h
where z.cupu = h.cislo_pu)
------------------------------------------------------------------------------------------------------------------^
, '[^,]+')))
) l
where t.cislo_pu = z.cupu
and z.stav <> 100)
)
from zz_2202 z

with tab as
(select 1 n from dual)
select * from tab root
where n in (select child1.n from tab child1
where child1.n in (select child2.n from tab child2
where child2.n = root.n));
OK
with tab as
(select 1 n from dual)
select (select * from tab child1 where child1.n = root.n)
from tab root;
OK
with tab as
(select 1 n from dual)
select (select * from (select * from tab child2 where child2.n = root.n))
from tab root;
ERROR
So there is a limit for nesting in the select list which seems to be equal to 1.
Oracle performs a correlated subquery when a nested subquery
references a column from a table referred to a parent statement one
level above the subquery. The parent statement can be a SELECT,
UPDATE, or DELETE statement in which the subquery is nested. A
correlated subquery conceptually is evaluated once for each row
processed by the parent statement. However, the optimizer may choose
to rewrite the query as a join or use some other technique to
formulate a query that is semantically equivalent. Oracle resolves
unqualified columns in the subquery by looking in the tables named in
the subquery and then in the tables named in the parent statement.

Related

Will unused CTE in a SQL statement be run

I have a question about CTEs. You can start of a sql statement by WITH and then you can construct one or multiple CTE queries, which you then in the end can make (in this case) a select on.
My question is: will all CTE queries be executed or only the ones who are being used?
E.g.
WITH cte_1
as (
Select * from table1
),
cte_2
as (
Select * from table2
)
Select * from cte_1
Will this mean that a select will be executed on table1 and table2?
To complete Master Gordon's answer (I dare) :
if you declare 2 cte, and use none of them, you also get that error message
with cte as (select 1/0 as val), cte2 as (select 1/0 as val)
select 1
Msg 422 Level 16 State 4 Line 2
Common table expression defined but not used.
But if you use at least one, the statement is accepted :
with cte_divide_by_zero as (select 1/0 as val), cte_legit as (select 'it works' as val)
select * from cte_divide_by_zero
Msg 8134 Level 16 State 1 Line 1
Divide by zero error encountered.
And if you select the other CTE, it proves that your unused CTE is never executed as no error occurs :
with cte_divide_by_zero as (select 1/0 as val), cte_legit as (select 'it works' as val)
select * from cte_legit
val
--------
it works
I get this message:
Msg 422 Level 16 State 4 Line 2
Common table expression defined but not used.
when running:
with cte as (select 1/0 as val)
select 1
Here is a db<>fiddle.

Case statement with four columns, i.e. attributes

I have a table with values "1", "0" or "". The table has four columns: p, q, r and s.
I need help creating a case statement that returns values when the attribute is equal to 1.
For ID 5 the case statement should return "p s".
For ID 14 the case statement should return "s".
For ID 33 the case statement should return 'p r s". And so on.
Do I need to come with a case statement that has every possible combination? Or is there a simpler way. Below is what I have come up with thus far.
case
when p = 1 and q =1 then "p q"
when p = 1 and r =1 then "p r"
when p = 1 and s =1 then "p s"
when r = 1 then r
when q = 1 then q
when r = 1 then r
when s = 1 then s
else ''
end
One solution could be this which uses a case for each attribute to return the correct value, surrounded by a trim to remove the trailing space.
with tbl(id, p, q, r, s) as (
select 5,1,0,0,1 from dual union all
select 14,0,0,0,1 from dual
)
select id,
trim(regexp_replace(case p when 1 then 'p' end ||
case q when 1 then 'q' end ||
case r when 1 then 'r' end ||
case s when 1 then 's' end, '(.)', '\1 '))
from tbl;
The real solution would be to fix the database design. This design technically violates Boyce-Codd 4th normal form in that it contains more than 1 independent attribute. The fact an ID "has" or "is part of" attribute p or q, etc should be split out. This design should be 3 tables, the main table with the ID, the lookup table containing info about attributes that the main ID could have (p, q, r or s) and the associative table that joins the two where appropriate (assuming an ID row could have more than one attribute and an attribute could belong to more than one ID), which is how to model a many-to-many relationship.
main_tbl main_attr attribute_lookup
ID col1 col2 main_id attr_id attr_id attr_desc
5 5 1 1 p
14 5 4 2 q
14 4 3 r
4 s
Then it would be simple to query this model to build your list, easy to maintain if an attribute description changes (only 1 place to change it), etc.
Select from it like this:
select m.ID, m.col1, listagg(al.attr_desc, ' ') within group (order by al.attr_desc) as attr_desc
from main_tbl m
join main_attr ma
on m.ID = ma.main_id
join attribute_lookup al
on ma.attr_id = al.attr_id
group by m.id, m.col1;
You can use concatenations with decode() functions
select id, decode(p,1,'p','')||decode(q,1,'q','')
||decode(r,1,'r','')||decode(s,1,'s','') as "String"
from t;
Demo
If you need spaces between letters, consider using :
with t(id,p,q,r,s) as
(
select 5,1,0,0,1 from dual union all
select 14,0,0,0,1 from dual union all
select 31,null,0,null,1 from dual union all
select 33,1,0,1,1 from dual
), t2 as
(
select id, decode(p,1,'p','')||decode(q,1,'q','')
||decode(r,1,'r','')||decode(s,1,'s','') as str
from t
), t3 as
(
select id, substr(str,level,1) as str, level as lvl
from t2
connect by level <= length(str)
and prior id = id
and prior sys_guid() is not null
)
select id, listagg(str,' ') within group (order by lvl) as "String"
from t3
group by id;
Demo
in my opinion, its a bad practice to use columns for relationships.
you should have two tables, one that's called arts and another that is called mapping art looks like this:
ID - ART
1 - p
2 - q
3 - r
4 - 2
...
and mapping maps your base-'ID's to your art-ids and looks like this
MYID - ARTID
5 - 1
5 - 4
afterwards, you should make use of oracles pivot operator. its more dynamically

select subquery using data from the select statement?

I have two tables, headers and lines. I need to grab the batch_submission_date from the header table, but sometimes a query for batch_id will return a null for batch_submission_date, but will also return a parent_batch_id, and if we query THAT parent_batch_id as a batch_id, it will then return the correct batch_submission_date.
e.g.
SELECT t1.batch_id,
t1.parent_batch_id,
t2.batch_submission_date
FROM db.headers t1, db.lines t2
WHERE t1.batch_id = '12345';
output = 12345, 99999, null
Then we use that parent batch_id as a batch_id :
SELECT t1.batch_id,
t1.parent_batch_id,
t2.batch_submission_date
FROM db.headers t1, db.lines t2
WHERE t1.batch_id = '99999';
and we get output = 99999,99999,'2018-01-01'
So I'm trying to write a query that will do this for me - anytime a batch_id's batch_submission_date is null, we find that batch_id's parent batch_id and query that instead.
This was my idea - but I just get back null both for bp_batch_submission_date and for new_submission_date.
SELECT
t1.parent_id as parent_id,
t1.BATCH_ID as bp_batch_id,
t2.BATCH_LINE_NUMBER as bp_batch_li,
t1.BATCH_SUBMISSION_DATE as bp_batch_submission_date,
CASE
WHEN t1.BATCH_SUBMISSION_DATE is null
THEN
(SELECT a.BATCH_SUBMISSION_DATE
FROM
db.headers a,
db.lines b
WHERE
a.SD_BATCH_HEADERS_SKEY = b.SD_BATCH_HEADERS_SKEY
and a.parent_batch_id = bp_batch_id
and b.batch_line_number = bp_batch_li
) END as new_submission_date
FROM
db.headers t1,
db.lines t2
WHERE
t1.SD_BATCH_HEADERS_SKEY = t2.SD_BATCH_HEADERS_SKEY
and (t1.BATCH_ID = '12345' or t1.PARENT_BATCH_ID = '12345')
and t2.BATCH_LINE_NUMBER = '1'
GROUP BY
t2.BATCH_CLAIM_LINE_STATUS_DESC,
t1.PARENT_BATCH_ID,
t1.BATCH_ID,
t2.BATCH_LINE_NUMBER,
t1.BATCH_SUBMISSION_DATE;
is what I'm trying to do possible? using the bp_batch_id and bp_batch_li variables
Use CTE (common table expression) to avoid redundant code, then use coalesce() to find parent date in case of null. In your first queries you didn't attach joining condition between two tables, I assumed it's based on sd_batch_headers_skey like in last query.
dbfiddle demo
with t as (
select h.batch_id, h.parent_batch_id, l.batch_submission_date bs_date
from headers h
join lines l on l.sd_batch_headers_skey = h.sd_batch_headers_skey
and l.batch_line_number = '1' )
select batch_id, parent_batch_id,
coalesce(bs_date, (select bs_date from t x where x.batch_id = t.parent_batch_id)) bs_date
from t
where batch_id = 12345;
You could use simpler syntax with connect by and level <= 2 but if in your data there are really rows containing same ids (99999, 99999) then we get cycle error.

How to write sub query with where Condition and match the columns values

the following is my query
select * from tbl_incometax_master where (select Slabtitle=Gender,SlabSubTitle=Senior_CTZN_Type FROM etds.dbo.tbl_Employee_Master WHERE employee_id = 1218 AND company_id = 1987)
when try to execute got following error in sql server 2008 r2 :
Msg 4145, Level 15, State 1, Line 2
An expression of non-boolean type specified in a context where a condition is expected, near ')'.
Perhaps this is what you intend to do (Uses EXISTS):
select *
from tbl_incometax_master
where exists (
select 1
from etds.dbo.tbl_Employee_Master
where employee_id = 1218
and company_id = 1987
and Slabtitle = Gender
and SlabSubTitle = Senior_CTZN_Type
)
It's a correlated subquery, where the outer query checks if there is at least one row exists in the subquery for current row.
You dont have any column in where clause of parent query, it is important to know that only column criteria can be defined with subquery.
Sample query...
select * from tbl_incometax_master where <column> (select <subquery column> FROM etds.dbo.tbl_Employee_Master WHERE employee_id = 1218 AND company_id = 1987)
You have a select clause in your Where with no operator (=,<,>,IN) ! And '=' inside the Select!
None of them are allowed in a SQL Query.
select * from tbl_incometax_master table1 LEFT JOIN
(
select Gender,Senior_CTZN_Type FROM etds.dbo.tbl_Employee_Master WHERE employee_id = 1218 AND company_id = 1987
) table2
ON
table1.Slabtitle=table2.Gender
AND table1.SlabSubTitle=table2.Senior_CTZN_Type
WHERE table2.Slabtitle IS NOT NULL

Select where record does not exists

I am trying out my hands on oracle 11g. I have a requirement such that I want to fetch those id from list which does not exists in table.
For example:
SELECT * FROM STOCK
where item_id in ('1','2'); // Return those records where result is null
I mean if item_id '1' is not present in db then the query should return me 1.
How can I achieve this?
You need to store the values in some sort of "table". Then you can use left join or not exists or something similar:
with ids as (
select 1 as id from dual union all
select 2 from dual
)
select ids.id
from ids
where not exists (select 1 from stock s where s.item_id = ids.id);
You can use a LEFT JOIN to an in-line table that contains the values to be searched:
SELECT t1.val
FROM (
SELECT '1' val UNION ALL SELECT '2'
) t1
LEFT JOIN STOCK t2 ON t1.val = t2.item_id
WHERE t2.item_id IS NULL
First create the list of possible IDs (e.g. 0 to 99 in below query). You can use a recursive cte for this. Then select these IDs and remove the IDs already present in the table from the result:
with possible_ids(id) as
(
select 0 as id from dual
union all
select id + 1 as id from possible_ids where id < 99
)
select id from possible_ids
minus
select item_id from stock;
A primary concern of the OP seems to be a terse notation of the query, notably the set of values to test for. The straightforwwrd recommendation would be to retrieve these values by another query or to generate them as a union of queries from the dual table (see the other answers for this).
The following alternative solution allows for a verbatim specification of the test values under the following conditions:
There is a character that does not occur in any of the test values provided ( in the example that will be - )
The number of values to test stays well below 2000 (to be precise, the list of values plus separators must be written as a varchar2 literal, which imposes the length limit ). However, this should not be an actual concern - If the test involves lists of hundreds of ids, these lists should definitely be retrieved froma table/view.
Caveat
Whether this method is worth the hassle ( not to mention potential performance impacts ) is questionable, imho.
Solution
The test values will be provided as a single varchar2 literal with - separating the values which is as terse as the specification as a list argument to the IN operator. The string starts and ends with -.
'-1-2-3-156-489-4654648-'
The number of items is computed as follows:
select cond, regexp_count ( cond, '[-]' ) - 1 cnt_items from (select '-1-2-3-156-489-4654648-' cond from dual)
A list of integers up to the number of items starting with 1 can be generated using the LEVEL pseudocolumn from hierarchical queries:
select level from dual connect by level < 42;
The n-th integer from that list will serve to extract the n-th value from the string (exemplified for the 4th value) :
select substr ( cond, instr(cond,'-', 1, 4 )+1, instr(cond,'-', 1, 4+1 ) - instr(cond,'-', 1, 4 ) - 1 ) si from (select cond, regexp_count ( cond, '[-]' ) - 1 cnt_items from (select '-1-2-3-156-489-4654648-' cond from dual) );
The non-existent stock ids are generated by subtracting the set of stock ids from the set of values. Putting it all together:
select substr ( cond, instr(cond,'-',1,level )+1, instr(cond,'-',1,level+1 ) - instr(cond,'-',1,level ) - 1 ) si
from (
select cond
, regexp_count ( cond, '[-]' ) - 1 cnt_items
from (
select '-1-2-3-156-489-4654648-' cond from dual
)
)
connect by level <= cnt_items + 1
minus
select item_id from stock
;