Can select using dblink, cannot insert using dblink - sql

I have inherited a code which helps me to fetch data from external systems. It is a bit screwed up, but I have to use it as is.
SELECT NVL (
(
SELECT TRIM (alias.SERV_CD)
FROM schema.CX_SER#db_link alias
WHERE row_id =
(
SELECT mix.par_row_id
FROM schema.CX_SER_MI_XM#db_link mix
WHERE mix.bill = SA.BILL_AC
AND ROWNUM < 2
)
AND ROWNUM < 2
),
(
SELECT TRIM (ba.SERV_CD)
FROM schema.s_some_table#db_link ba
WHERE ba.row_id = sa.BILL_AC AND ROWNUM < 2
)
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa, schema.action_table#db_link ia, schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_AC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1);
The said code returns results when I run the select as is.
But when I try to insert these records in my schema(using simple insert into(columns) <this select statement>, I get the following error messages :
Error: ORA-00904:"SA"."BILL_ACC" :invalid identifier.
ORA - 02063 : preceding line from db_link.
A select statement like :
with c as(
SELECT NVL (
(
SELECT TRIM (alias.SERV_CD)
FROM schema.CX_SER#db_link alias
WHERE row_id =
(
SELECT mix.par_row_id
FROM schema.CX_SER_MI_XM#db_link mix
WHERE mix.bill = SA.BILL_AC
AND ROWNUM < 2
)
AND ROWNUM < 2
),
(
SELECT TRIM (ba.SERV_CD)
FROM schema.s_some_table#db_link ba
WHERE ba.row_id = sa.BILL_AC AND ROWNUM < 2
)
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa, schema.action_table#db_link ia, schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_AC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1))
select * from c where REQUIRED_CODE IS NOT NULL;
also fail with same error. However, I am able to obtain results when I query using some other column in above with clause statement gives results, for e.g where order_count>2 gives result. So the problem is in the REQUIRED_CODE section, and maybe, in the group by.
Please guide on the course of action. I need to insert the records flowing into my schema.
NOTE : All columns are either varchar2 or date

If my attempt to replicate the issue has ended up close to yours, you can avoid the error with the CTE by adding a driving_site hint:
with c as (
SELECT /*+ DRIVING_SITE (sa) */ NVL (
...
That prevents the query being written and distributed in a way that confuses the optimiser; I think it's tripping over the nested reference to SA in the subquery and it's ending up too many levels down to be recognised.
That hint doesn't have any effect on the insert though.
As mentioned in comments I've had a quick go at rewriting the query to avoid the subqueries. It's a bit rough and I'm not sure I understand everything you're currently doing, partly because of the table name changes etc. But you wanted to see it, and it might give you something to work from...
INSERT INTO t42
SELECT NVL (TRIM(MIN(t.SERV_CD) KEEP (DENSE_RANK FIRST ORDER BY NULL)),
TRIM(MIN(ba.SERV_CD) KEEP (DENSE_RANK FIRST ORDER BY NULL))
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa
LEFT JOIN (
SELECT mix.bill, alias.SERV_CD
FROM schema.CX_SER_MI_XM#db_link mix
JOIN schema.CX_SER#db_link alias
ON alias.row_id = mix.par_row_id
) t
ON t.bill = SA.BILL_ACC
LEFT JOIN schema.s_some_table#db_link ba
ON ba.row_id = sa.BILL_ACC
CROSS JOIN schema.action_table#db_link ia
CROSS JOIN schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_ACC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1);
If you can't get it working then you could use your original query in a PL/SQL block, either as a cursor and doing row-by-row inserts, or preferably (particularly if it will return a lot of data) using a collection with bulk collect and a forall insert.
If you search My Oracle Support for ORA-02063 and ORA-00904 you'll see quite a few bugs, some of which seem to apply to 11g but ought to have been fixed by my version 11.2.0.4; I can't see anything that quite matches from a quick browse but it's possible you are hitting one of those, or indeed one that hasn't been reported. It may be worth raising an SR to investigate your specific scenario.

Related

Oracle SQL use subquery value in another subquery

Before I go any further please mind that I am not well experienced with SQL.
I have one query that is getting a single value (netto value) such as:
WITH cte_value_net AS (
SELECT
nvl(min(value_net),0) as value_net
FROM (
SELECT
i.serial as serial,
nvl(lag(i.value_net) OVER (PARTITION BY i.serial ORDER BY i.month), i.value_net) as value_net
FROM
inventory i
WHERE
i.ctypde IN (
SELECT
ctypde
FROM
appar ap
WHERE
ap.serial = in_serial -- this is the variable I want to set
)
AND
i.month IN (to_char(add_months(sysdate, -1), 'YYYYMM'), to_char(add_months(sysdate, -2), 'YYYYMM'))
AND
i.serial = in_serial -- this is the variable I want to set
) vn
GROUP BY vn.serial
)
In here I have to feed in the variable in_serial that I thought I could get from another subquery such as:
SELECT
(SELECT * FROM cte_value_net) AS value_net
FROM (
SELECT
lap.serial AS in_serial
FROM
applap lap
)
but I can not wrap my head around it why this in_serial is not visible to my custom CTE. Could someone explain me how can I propagate the value from subquery like this?
The error I am obviously getting is:
SQL Error [904] [42000]: ORA-00904: "IN_SERIAL"
Unfortunately I do not have any sample data. What I want to achieve is that I could feed in the returned in_serial from main subquery to my CTE.
Before I can get value_net I need my main query to return the in_serial, otherwise I do not have access to that value.
The trick I use is to produce an extra CTE that I usually call params that includes a single row with all computed parameters. Then, it's a matter of performing a CROSS JOIN with this CTE in any other CTE, subquery or main query, as needed.
For example:
with
params as ( -- 1. Create a CTE that returns a single row
select serial as in_serial from applap
),
cte_value_net AS (
select ...
from inventory i
cross join params -- 2. cross join against the CTE anywhere you need it
where ...
and i.serial = params.in_serial -- 3. Use the parameter
)
select ...
First, your second query is not syntactically correct, as there's a ',' before the FROM. You can write your query like this:
WITH cte_value_net AS (
SELECT
serial, nvl(min(value_net),0) as value_net
FROM (
SELECT
i.serial as serial,
nvl(lag(i.value_net) OVER (PARTITION BY i.serial ORDER BY i.month), i.value_net) as value_net
FROM
inventory i
WHERE
i.ctypde IN (
SELECT
ctypde
FROM
appar ap
WHERE
ap.serial = i.serial
)
AND
i.month IN (to_char(add_months(sysdate, -1), 'YYYYMM'), to_char(add_months(sysdate, -2), 'YYYYMM'))
) vn
GROUP BY vn.serial
)
select ...
from cte_value_net s join applap lap on (lap.serial=s.serial)
(adjust query to your schema ....)

Snowflake - update with correlated subquery using timediff

I am running this query on Snowflake Database:
UPDATE "click" c
SET "Registration_score" =
(SELECT COUNT(*) FROM "trackingpoint" t
WHERE 1=1
AND c."CookieID" = t."CookieID"
AND t."page" ilike '%Registration complete'
AND TIMEDIFF(minute,c."Timestamp",t."Timestamp") < 4320
AND TIMEDIFF(second,c."Timestamp",t."Timestamp") > 0);
The Database returns Unsupported subquery type cannot be evaluated. However, if I run it without the last two conditions (with TIMEDIFF), it works without problem. I confirmed that the actual TIMEDIFF statements are alright with these queries:
select count(*) from "trackingpoint"
where TIMEDIFF(minute, '2018-01-01', "Timestamp") > 604233;
select count(*) from "click"
where TIMEDIFF(minute, '2018-01-01', "Timestamp") > 604233;
and these work without problem. I don't see a reason why the TIMEDIFF condition shoud prevent the database from returning the result. Any idea what should I alter to make it work?
so using the following setup
create table click (id number,
timestamp timestamp_ntz,
cookieid number,
Registration_score number);
create table trackingpoint(id number,
timestamp timestamp_ntz,
cookieid number,
page text );
insert into click values (1,'2018-03-20', 101, 0),
(2,'2019-03-20', 102, 0);
insert into trackingpoint values (1,'2018-03-20 00:00:10', 101, 'user reg comp'),
(2,'2018-03-20 00:00:11', 102, 'user reg comp'),
(3,'2018-03-20 00:00:13', 102, 'pet reg comp'),
(4,'2018-03-20 00:00:15', 102, 'happy dance');
you can see we get the rows we expect
select c.*, t.*
from click c
join trackingpoint t
on c.cookieid = t.cookieid ;
now there are two ways to get your count, the first as you have it, which is good if your counting only one thing, as all the rules are join filtering:
select c.id,
count(1) as new_score
from click c
join trackingpoint t
on c.cookieid = t.cookieid
and t.page ilike '%reg comp'
and TIMEDIFF(minute, c.timestamp, t.timestamp) < 4320
group by 1;
or you can (in snowflake syntax) move the count to the aggregate/select side,and thus get more than one answer if that's what you need (this is the place I find myself more, thus why I present it):
select c.id,
sum(iff(t.page ilike '%reg comp' AND TIMEDIFF(minute, c.timestamp, t.timestamp) < 4320, 1, 0)) as new_score
from click c
join trackingpoint t
on c.cookieid = t.cookieid
group by 1;
thus plugging this into the UPDATE pattern (see last example in the doc's)
https://docs.snowflake.net/manuals/sql-reference/sql/update.html
you can move to a single subselect instead of a corolated subquery which snowflake doesn't support, which is the error message you are getting.
UPDATE click c
SET Registration_score = s.new_score
from (
select ic.id,
count(*) as new_score
from click ic
join trackingpoint it
on ic.cookieid = it.cookieid
and it.page ilike '%reg comp'
and TIMEDIFF(minute, ic.timestamp, it.timestamp) < 4320
group by 1) as s
WHERE c.id = s.id;
The reason add the TIMEDIFF turns your query into a correlated sub-query, is each row of the UPDATE, now relates to the sub-query results, the correlation. The work around is to make "big but simpler" sub-query and join to that.

SQL Server : connect by level for DATE type with union

I've found basic answer for replacing the Oracle's "CONNECT BY LEVEL" in this question but my case is little bit more complicated:
Basically things that I want to replace looks like this:
...
UNION ALL
Select
adate, 'ROAD' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum
from dual
connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
Select
adate, 'PORTPACK' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum from dual connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
....
Now, the single dual connect is easy, even if this is apparently not very efficient method
WITH CTE AS (
SELECT dateadd(day,-720,CONVERT (date, GETDATE())) as Datelist
UNION ALL
SELECT dateadd(day,1,Datelist)
FROM CTE
WHERE datelist < getdate() )
SELECT *,'ROAD' as Tserv_ID , 0 as EQ_NBR FROM CTE
option (maxrecursion 0)
repeating the union is hard because I get an error:
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
There are more parts of this union that I've provided here; I've tried to use the "WITH" only at start but no luck. Am I missing something obvious here?
EDIT: There is of course big question WHY I am even trying to do such thing: Personally, I wouldn't, but at the other end of the query there is a huge Crystal Report that runs once every month and which accepts data in this particular format. End of the FULL query's output is something like
3 columns of data
3 Columns of data
...
Currentdate-732,"ROAD",0
Currentdate -731,"ROAD",0
...
Currentdate, "ROAD,"0"
Currentdate -732, "PORTPAK", 0
Currentdate -731, "PORTPAK", 0
etc.
Are you trying to do:
WITH CTE1 AS (...),
CTE2 AS (...)
SELECT stuff FROM CTE1
UNION ALL
SELECT stuff FROM CTE2;
? This is a common challenge, I guess it is not very discoverable that in order to use more than one CTE, you just separate them with a comma.
That all said, it seems like you are just trying to generate a series of dates. A recursive CTE (never mind a series of many of them) is not the most efficient way to do this. Instead of telling us you want to replace CONNECT BY LEVEL and showing us the syntax you've tried, why don't you just show or describe the output you want? We've already got an appreciation that you've tried something on your own (thanks!) but we'd rather give you an efficient solution than bridging the gap to an inefficient one.
As an example, here is something that requires a lot less redundant code, and ( think gives you what you're after:
DECLARE #n INT = 722, #d DATE = CURRENT_TIMESTAMP;
;WITH v AS (SELECT v FROM (VALUES('ROAD'),('PORTPACK')) AS v(v)),
n AS (SELECT TOP (#n) n = ROW_NUMBER() OVER (ORDER BY number)
FROM master.dbo.spt_values ORDER BY n)
SELECT Datelist = DATEADD(DAY, 2-n.n, #d), Tserv_ID = v.v, EQ_NBR = 0
FROM n CROSS JOIN v
ORDER BY Tserv_ID, Datelist;

function based index

select
<here I have functions like to_char, nvl, rtrim, ltrim, sum, decode>
from
table1
table2
where
joining conditions 1
joining conditions 2
group by
<here I have functions like to_char, nvl, rtrim, ltrim, sum, decode>
I got this query from production and looking at it need to provide few solutions to tune, I m thinking of using function based inbex for group by columns. I think select columns need not be index. I will get enviornment in couple of days but before that I need to come up with different apporaches. What all things I need to check if function by index is useful? Also, apart from explain plan which other documents I need to ask from DBAs?
I m adding actual sql here, I have asked for explain plan, which I will get in sometime :-
SELECT
D_E_TRADE.DATE_VALUE,
to_char(D_E_TRADE.DATE_VALUE,'Mon-yyyy'),
NVL(P_DIM.P_NAME,' '),
rtrim(ltrim(P_DIM.C_CTRY)),
D_E_TRADE.YEAR,
L_E_DIM.L_CODE,
NVL(D_DIM.DESCR,' '),
( decode(D_DIM.DEPT_ID,'-1',' ',D_DIM.DEPT_ID) ),
sum(A_CGE.TOTAL_CALC_NET_FEES),
L_E_DIM.L_NAME,
decode(A_CGE.E_M_CENTER,-9,0,A_CGE.E_M_CENTER),
NVL(F_DIM.S_DESC,'-1'),
sum(A_CGE.C_TOTAL_SHARES)
FROM
DATE_D D_E_TRADE,
P_DIM,
L_E_DIM,
D_DIM,
A_CGE,
F_DIM
WHERE
( D_E_TRADE.DATE_KEY=A_CGE.T_KEY )
AND ( P_DIM.PARTY_KEY=A_CGE.E_P_KEY )
AND ( F_DIM.F_T_KEY=A_CGE.F_T_KEY )
AND ( L_E_DIM.L_E_KEY=A_CGE.L_E_KEY )
AND ( D_DIM.DEPT_KEY=A_CGE.DEPT_KEY )
AND
(
rtrim(ltrim(P_DIM.C_CTRY)) = 'AC'
AND
( A_CGE.T_KEY >= (SELECT DATE_D_PROMPTS.DATE_KEY FROM DATE_D DATE_D_PROMPTS WHERE ( DATE_D_PROMPTS.DATE_VALUE = '01-01-2012 00:00:00' ) )
AND
A_CGE.T_KEY <= (SELECT DATE_D_PROMPTS.DATE_KEY FROM DATE_D DATE_D_PROMPTS WHERE ( DATE_D_PROMPTS.DATE_VALUE = '31-08-2012 00:00:00' ))
AND
A_CGE.TRANS_REGION_KEY IN (SELECT REGION_KEY FROM REGION_DIM WHERE REGION_DIM.REGION_NAME IN ('Americas') ) )
AND
( A_CGE.T_KEY >= (SELECT DATE_D_PROMPTS.DATE_KEY FROM DATE_D DATE_D_PROMPTS WHERE ( DATE_D_PROMPTS.DATE_VALUE = '01-01-2012 00:00:00' ) )
AND
A_CGE.T_KEY <= (SELECT DATE_D_PROMPTS.DATE_KEY FROM DATE_D DATE_D_PROMPTS WHERE ( DATE_D_PROMPTS.DATE_VALUE = '31-08-2012 00:00:00' ))
AND
A_CGE.TRANS_REGION_KEY IN (SELECT REGION_KEY FROM REGION_DIM WHERE REGION_DIM.REGION_NAME IN ('Americas') ) )
AND
( 'All Fees' IN ('2 - E','3 - P','4 - F','5 - C,') OR A_CGE.F_T_KEY IN (SELECT F_T_KEY FROM F_DIM WHERE (F_DIM.s_id ) || ' - ' || ( F_DIM.CHARGE_LVL1_NAME ) IN ('2 - E','3 - P','4 - F','5 - C')) )
)
GROUP BY
D_E_TRADE.DATE_VALUE,
to_char(D_E_TRADE.DATE_VALUE,'Mon-yyyy'),
NVL(P_DIM.P_NAME,' '),
rtrim(ltrim(P_DIM.C_CTRY)),
D_E_TRADE.YEAR,
L_E_DIM.L_CODE,
NVL(D_DIM.DESCR,' '),
( decode(D_DIM.DEPT_ID,'-1',' ',D_DIM.DEPT_ID) ),
L_E_DIM.L_NAME,
decode(A_CGE.E_M_CENTER,-9,0,A_CGE.E_M_CENTER),
NVL(F_DIM.S_DESC,'-1')
Generaly, indexes help you on fast retrieval of data when you have filtering conditions wich may use the indexes.
(Another case whold be when you retrieve only column that are in the index, so the engine does not need to read anything from table)
In your case, you may need indexes on filtering/join conditions in the following part:
joining conditions 1
joining conditions 2
But keep in mind. If the you get more than 15%-20% of rows of a table, is better to read from table, not to use the index. That is, the index may not be used.

Bubbling Up Columns in Sql

Pardon the convoluted example, but I believe there is something fundamental about sql I am missing and I'm not sure what it is. I have this crazy query...
SELECT *
FROM (
SELECT *
FROM (
SELECT #t1 := #t1 +1 AS leaderboard_entry_youngness_rank, 1 - #t1 /100 AS
leaderboard_entry_youngness_based_on_expiry, leaderboard_entry . * ,
NOW( ) - leaderboard_entry_timestamp AS leaderboard_entry_age_in_some_units,
TO_DAYS( NOW( ) ) - TO_DAYS( leaderboard_entry_timestamp )
AS leaderboard_entry_age_in_days
FROM leaderboard_entry) AS inner_temp
NATURAL JOIN leaderboard
NATURAL JOIN user
WHERE (
leaderboard_load_key = 'sk-en-adjectives-1'
OR leaderboard_load_key = '-sk-en-adjectives-1'
)
AND leaderboard_quiz_mode = '0'
ORDER BY leaderboard_entry_age_in_some_units ASC , leaderboard_entry_timestamp ASC
LIMIT 0 , 100
) AS outer_temp
ORDER BY leaderboard_entry_elapsed_time_ms ASC , leaderboard_entry_timestamp ASC
LIMIT 0 , 50
I added the second nested SELECT statement because the user_name in the user table was not being returned in the outermost query. But now the leaderboard_entry_youngness_based_on_expiry field, which is being generated based on a row index ratio, is not working correctly.
If I remove the second nested SELECT statement, the leaderboard_entry_youngness_based_on_expiry works as expected, but the user_name column is not returned.
How can I satisfy both? Why is this happening?
Thanks!
This stems from the following question:
Add a numbered list column to a returned MySQL query
In your inner SELECT statement, you do not have user.user_name, that's why username is not returned. Remove the outer query, do it like earlier but with user.user_name like this:
....
SELECT #t1 := #t1 +1 AS leaderboard_entry_youngness_rank, 1 - #t1 /100 AS
leaderboard_entry_youngness_based_on_expiry, leaderboard_entry . * ,
NOW( ) - leaderboard_entry_timestamp AS leaderboard_entry_age_in_some_units,
TO_DAYS( NOW( ) ) - TO_DAYS( leaderboard_entry_timestamp )
AS leaderboard_entry_age_in_days, user.user_name
....
Try putting a ORDER BY in the inner most query, since there currently is no ORDER BY clause, its wrong to say that "is not working correctly".
Check if you take away the outer SELECT * FROM..., see if there are duplicate user_name columns.
BTW, Since you are not using the row index columns in your query, why not just put this logic in the application itself? it will be more reliable doing so.