Vague ORA-00904 Invalid Identifier error - sql

I have a query where I build a table of values and I union it with another query. I wish to return a set of results where one value between the 2 tables does not match. (query example below). Every time I try to execute, I get the dreaded invalid identifier error, but I have no idea why. Any suggestions would be appreciated!
select * from (
select '1234567' as empno, 'A' as status, 'Active' as st_name from dual union all
select '89012345' as empno, 'DA' as status, 'Inactive' as st_name from dual) ft
union
select id,status,statusnm from second_table st
where st.id = ft.empno
and st.statusnm <> ft.st_name;
Any thoughts/advice?

You don't want union, you want join . . . and it should be explicit:
select st.*
from (select '1234567' as empno, 'A' as status, 'Active' as st_name from dual
union all
select '89012345' as empno, 'DA' as status, 'Inactive' as st_name from dual
) ft join
second_table st
on st.id = ft.empno and st.statusnm <> ft.st_name

Related

How to compare different values within the same column

I having two tables emp and type.
create table EMP(ID number(10), effective_date date);
EID Effective_date
--------------------
1 02/14/2023
2 02/15/2023
3 04/30/2023
4 03/24/2023
create table type(ID number(10),contract_type varchar2(2));
TID contract_type
------------------
1 P
1 S
1 P
2 S
2 S
3 P
3 S
4 S
I am looking EID which is having contract type is 'S' in type table. (or emp table with effective date is greater than sysdate and in the type table with only contract_type ='S')
Actual result :
2
4
My query is not giving the correct results.
select emp.EID
from emp,type
where EID = TID
contract_type ='S'
effective_date >= sysdate
group by TID
having count(TID) >= 1;
If you want to keep your idea with COUNT and GROUP BY, you should count other contract types than the 'S' ones and check this is 0:
SELECT e.eid
FROM emp e
JOIN type t ON e.eid = t.tid
WHERE
e.effective_date >= sysdate
GROUP BY e.eid
HAVING COUNT(CASE WHEN t.contract_type <> 'S' THEN 1 END) = 0;
This query will return 2 and 4 for your sample data.
Try out: db<>fiddle
Another option is as already said here using NOT EXISTS.
Take care of following difference to the NOT EXISTS approach: The query in Tim's answer will also fetch id's of table "emp" that don't appear at all in table "type". My query here will not fetch such id's.
It's up to you to decide whether this is possible at all and what to do in this case.
Changing JOIN to LEFT JOIN in above query will eliminate this difference.
I would use exists logic here:
SELECT EID
FROM EMP e
WHERE effective_date >= SYSDATE AND
NOT EXISTS (
SELECT 1
FROM "type" t
WHERE t.TID = e.EID AND
t.contract_type <> 'S'
);
You could use Count() Over() analytic function to check for type 'S' and number of different types per ID.
SELECT DISTINCT ID
FROM ( Select e.EID "ID",
Count(CASE t.CONTRACT_TYPE WHEN 'S' THEN 'S' END) Over(Partition By t.ID Order By t.ID) "NUM_OF_S",
Count(Distinct t.CONTRACT_TYPE) Over(Partition By t.ID) "NUM_OF_TYPES",
TRUNC(e.EFFECTIVE_DATE) - TRUNC(SYSDATE) "DAYS_AFTER_SYSDATE"
From emp_cte e
Inner Join type_cte t ON(t.ID = e.EID) )
WHERE NUM_OF_S > 0 And -- Type 'S' exists for ID AND
NUM_OF_TYPES = 1 And -- It is the only type AND
DAYS_AFTER_SYSDATE > 0 -- EFFECTIVE_DATE is after SYSDATE
With your sample data ...
WITH
emp_cte(EID, EFFECTIVE_DATE) AS
(
Select 1, To_Date('02/14/2023', 'mm/dd/yyyy') From Dual Union All
Select 2, To_Date('02/15/2023', 'mm/dd/yyyy') From Dual Union All
Select 3, To_Date('04/30/2023', 'mm/dd/yyyy') From Dual Union All
Select 4, To_Date('03/24/2023', 'mm/dd/yyyy') From Dual
),
type_cte(ID, CONTRACT_TYPE) AS
(
Select 1, 'P' From Dual Union All
Select 1, 'S' From Dual Union All
Select 1, 'P' From Dual Union All
Select 2, 'S' From Dual Union All
Select 2, 'S' From Dual Union All
Select 3, 'P' From Dual Union All
Select 3, 'S' From Dual Union All
Select 4, 'S' From Dual
)
... result would be ...
-- ID
-- ----------
-- 2
-- 4

How to pick records based on specific condition

I have a table named as "ticket_lc" look like below
From the above table i need to pick only the records which satisfy the condition
condition: the ticket status should be in "assigned" and "closed" and "resolved"
so in the above table only 102 ticket is satisfying the condition, if the ticket contains other than these 3 then my query should not pick those tickets.
can anybody help me on this..!!!
Thanks
Below is for BigQuery Standard SQL
#standardSQL
SELECT ticket_id
FROM `project.dataset.ticket_lc`
GROUP BY ticket_id
HAVING COUNT(DISTINCT status) = 3
AND COUNTIF(LOWER(status) NOT IN ('assigned', 'closed', 'resolved')) = 0
Yo can test, play with above using sample data from your question as in below example
#standardSQL
WITH `project.dataset.ticket_lc` AS (
SELECT 101 ticket_id, 'Assigned' status UNION ALL
SELECT 101, 'Pending' UNION ALL
SELECT 101, 'Resolved' UNION ALL
SELECT 101, 'Closed' UNION ALL
SELECT 102, 'Assigned' UNION ALL
SELECT 102, 'Resolved' UNION ALL
SELECT 102, 'Closed' UNION ALL
SELECT 103, 'Assigned' UNION ALL
SELECT 103, 'Pending' UNION ALL
SELECT 103, 'Pending' UNION ALL
SELECT 103, 'Assigned' UNION ALL
SELECT 103, 'Resolved' UNION ALL
SELECT 103, 'Closed'
)
SELECT ticket_id
FROM `project.dataset.ticket_lc`
GROUP BY ticket_id
HAVING COUNT(DISTINCT status) = 3
AND COUNTIF( LOWER(status) NOT IN ('assigned', 'closed', 'resolved')) = 0
with result
Row ticket_id
1 102
You can do aggregation :
select ticket_id
from table t
group by ticket_id
having sum( case when status not in ('assigned', 'closed', 'resolved') then 1 else 0 end ) = 0 and
count(*) = 3;
If you have a duplicate status for ticket then use distinct inside count().
Kind of an interesting problem. I came up with this checking if everything is covered and the amount is correct:
WITH t AS (SELECT * FROM UNNEST(
[struct(101 as ticket_id, 'assigned' as status),(101,'closed'),
(102,'assigned'),(102,'resolved'),(102,'closed'),
(104,'assigned'),(104,'assigned'),(104,'closed'),
(103,'assigned'),(103,'pending'),(103,'pending'),(103,'assigned'),(103,'resolved'),(103,'closed')]
)
)
SELECT ticket_id, array_agg(distinct status) as st
FROM t
group by 1
having (SELECT count(1)=3 FROM unnest(st) a left join unnest(['assigned','resolved','closed']) b on a=b)
Includes adjusted sample data to cover more problem cases.

Condition in subquery- select one value if subquery return 2 records else the actual value

I have a subquery inside a big query which returns multiple values sometime and some time only one value. Below is my query and the returned values
select tran.customer_type from transaction_record tran where tran.TRANSACTION_ID=txn.id
customer_type can be 2 records - "LP" and "NA"
or
customer_type can be 2 records - "SOEMTHING ELSE" and "NA"
or
customer_type can be 1 records - "NA"
Here my probem is if i have 2 records i have to print value without NA and if i have one record i have to print what ever be the value is
Not exectly efficient (2 queries), but it should work!
Inner query counts status, id combinatios per group and outer query
removes all NA statuses that have another record on same ID.
Innermost query is just for table simulation (I like it more than create table, insert scripts).
SELECT * FROM
(
SELECT status, id, count(*)
OVER (PARTITION BY id ORDER BY 3 ) AS rn
from (
SELECT 'NA' status, 1 id FROM dual
UNION ALL
SELECT 'LP' status, 1 id FROM dual
UNION ALL
SELECT 'NA' status, 2 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 2 id FROM dual
UNION ALL
SELECT 'NA' status, 3 id FROM dual
UNION ALL
SELECT 'NA' status, 5 id FROM dual
UNION ALL
SELECT 'LP' status, 5 id FROM dual
UNION ALL
SELECT 'NA' status, 6 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 6 id FROM dual
UNION ALL
SELECT 'NA' status, 22 id FROM dual
))
WHERE NOT (status = 'NA' AND rn=2)

Diff between two tables (using sql) -> incremental changes

I have a need to identify differences between two tables. I have looked at sql query to return differences between two tables but it was a bit too different for me to extrapolate with my current SQL skills.
Table A is a snapshot of a certain group of people where the snapshot was taken yesterday, where each row is a unique person and certain characteristics about the person. Table B is the same snapshot taken 24 hours later. Within the 24 hour period:
New people may have been added.
People from yesterday may have been removed.
People from yesterday may have changed (i.e., original row is there, but one or more column values have changed).
My output should have the following:
a row for each new person added
a row for each person removed
a row for each person who has changed
I would grateful for any ideas. Thanks!
This type of problem has a very simple and efficient solution that does not use joins (it doesn't even use a union of the results of two MINUS operations) - it just uses one union and a GROUP BY operation. The solution was developed in a thread on AskTom many years ago, it is surprising that it is not more widely known and used. For example (but not only): https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:24371552251735
In your case, assuming there is a primary key constraint on PERSON_ID (which makes the solution simpler):
select max(flag) as flag, PERSON_ID, first_name, last_name, (etc. - all the columns)
from ( select 'old' as flag, t1.*
from old_table t1
union all
select 'new' as flag, t2.*
from new_table t2
)
group by PERSON_ID, first_name, last_name, (etc.)
having count(*) = 1
order by PERSON_ID -- optional
;
If for a PERSON_ID all the data is the same in both tables, that will result in a count of 2 for that group. So it won't pass the HAVING condition. The only groups that will have a count of 1 (and therefore will be just one row each!) are either rows that are in one table but not the other. If a person was added, that will show only one row, with the flag = 'new'. If a person was deleted, you will get only one row, with the flag 'old'. If there were updates, the same PERSON_ID will appear twice, but since at least one field is different, the two rows (one with flag 'new' and the other with 'old') will be in different groups, they will pass the HAVING filter, and they will BOTH be in the output.
Which is slightly different from what you requested; you will get both the old AND the new information for updates, labeled as 'old' and 'new'. You said you wanted only one of those but didn't state which one. This will give you both (which makes more sense anyway), but if you really only want one, it can be done easily in the query above.
Note - the outer select must have max(flag) rather than flag because flag is not a GROUP BY column; but it's the max() over exactly one row, so it WILL be the flag for that row anyway.
Added - OP indicated he would like to get only the "new" row for a person with updated (changed, modified) data. The approach shown below will change the flag to "changed" in this case.
with old_table ( person_id, first_name, last_name ) as (
select 101, 'John', 'Smith' from dual union all
select 102, 'Mary', 'Green' from dual union all
select 103, 'July', 'Dobbs' from dual union all
select 104, 'Will', 'Scott' from dual
),
new_table ( person_id, first_name, last_name ) as (
select 101, 'Joe' , 'Smith' from dual union all
select 102, 'Mary', 'Green' from dual union all
select 104, 'Will', 'Scott' from dual union all
select 105, 'Andy', 'Brown' from dual
)
-- end of test data; solution (SQL query) begins below this line
select case ct when 1 then flag else 'changed' end as flag,
person_id, first_name, last_name
from (
select max(flag) as flag, person_id, first_name, last_name,
count(*) over (partition by person_id) as ct,
row_number() over (partition by person_id order by max(flag)) as rn
from ( select 'old' as flag, t1.*
from old_table t1
union all
select 'new' as flag, t2.*
from new_table t2
)
group by person_id, first_name, last_name
having count(*) = 1
)
where rn = 1
order by person_id -- ORDER BY clause is optional
;
Output:
FLAG PERSON_ID FIRS_NAME LAST_NAME
------- ---------- --------- ---------
changed 101 Joe Smith
old 103 July Dobbs
new 105 Andy Brown
The first 2 parts are easy:
select 'New', name from B where not exists (select name from A where A.name=B.name)
union select 'Removed', name from A where not exists (select name from B where B.name = A.name)
The last one is where you need to compare characteristics. How many of them are there? Do you want to list what has changed or only that they have changed?
For argument's sake, let us only say that the characteristics are address and telephone #:
union select 'Phone', name from A,B where A.name = B.name and A.telephone != B.telephone
union select 'Address', name from A,B where A.name = B.name and A.address != B.address
Note: The question isn't currently tagged with the dbms. I use sql-server, so that's what I used to write the below. There may be slight differences in another dbms.
You can do something along these lines:
select *
from TableA a
left join TableB b on b.ID = a.ID
where a.ID is null -- added since yesterday
union
select *
from TableA a
left join TableB b on b.ID = a.ID
where b.ID is null -- removed since yesterday
union
select *
from TableA a
inner join TableB b on b.ID = a.ID -- restrict to records in both tables
where a.SomeValue <> b.SomeValue
or a.SomeOtherValue <> b.SomeOtherValue
--etc
Each select handles one portion of your expected output. In this manner, they'd all be joined into 1 result set. If you drop the union, you'll end up with a separate set for each.
I suggest to use Except to get the changed records. The below query should work if the db is sql server.
-- added since yesterday
SELECT B.*
FROM TableA A
LEFT Outer Join TableB B on B.ID = A.ID
WHERE A.ID IS NULL
UNION
-- removed since yesterday
SELECT A.*
FROM TableA A
LEFT OUTER JOIN TableB B on B.ID = A.ID
WHERE B.ID IS NULL
UNION
-- Those changed with values from yesterdady
SELECT B.* FROM TableB B WHERE EXISTS(SELECT A.ID FROM TableA A WHERE A.ID = B.ID)
EXCEPT
SELECT A.* FROM TableA A WHERE EXISTS(SELECT B.ID FROM TableB B WHERE B.ID = A.ID)
Assuming you have a unique id for each person in the able, you can use full outer join:
select coalesce(ty.customerid, tt.customerid) as customerid,
(case when ty.customerid is null then 'New'
when tt.customerid is null then 'Removed'
else 'Modified'
end) as status
from tyesterday ty full outer join
ttoday tt
on ty.customerid= tt.customerid
where ty.customerid is null or
tt.customerid is null or
(tt.col1 <> ty.col1 or tt.col2 <> ty.col2 or . . . ); -- may need to take `NULL`s into account
mathguy provided a successful answer to my initial problem. I asked him for a revision (to make it even better). He provided a revision, but I am getting a "missing keyword" error when executing against my code. Here is my code:
select case when ct = 1 then flag else 'changed' as flag, PERSON_ID, FIRSTNAME, LASTNAME
from (
select max(flag), PERSON_ID, FIRSTNAME, LASTNAME
count() over (partition by PERSON_ID) as ct,
row_number() over (partition by PERSON_ID
order by case when flag = 'new' then 0 end) as rn
from ( select 'old' as flag, t1.*
from YESTERDAY_TABLE t1
union all
select 'new' as flag, t2.*
from TODAY_TABLE t2
)
group by PERSON_ID, FIRSTNAME, LASTNAME
having count(*) = 1
)
where rn = 1
order by PERSON_ID;

Join two subqueries with ON or USING

I have two queries I need to LEFT JOIN the first one with the second one. The purpose is to wrap all of this inside of something else bigger. I got both the first and second queries working alone but cannot get them to join.
First Query:
SELECT *
FROM (
SELECT Source as system, DT as ts, Status as statusCode
FROM (
(SELECT 'SOURCE1' Source FROM Dual
UNION SELECT 'SOURCE2' FROM Dual
UNION SELECT 'SOURCE3' FROM Dual
UNION SELECT 'SOURCE4' FROM Dual
) system
CROSS JOIN (
SELECT
TO_DATE('09-30-2013','MM-DD-YYYY') - 1 + LEVEL dt
FROM dual
CONNECT BY
LEVEL <= ( TO_DATE('10/05/2013','MM/DD/YYYY')
- TO_DATE('09/30/2013','MM/DD/YYYY')) + 1
) ts
CROSS JOIN (
SELECT 'O' Status FROM Dual
UNION SELECT 'C' FROM Dual
) statusCode
)--For some reason cannot name this so need to wrap in another select *
)Duals
Second Query: (there would be a LEFT JOIN) between here
LEFT JOIN
Was tried
Select * FROM(
SELECT myTable1.system, TO_CHAR(maxResults.ts,'YYYY-MM-DD') as ts, myTable1.statusCode
FROM (
SELECT table_id, MAX(ts) as ts
FROM myTable1_history
WHERE ts BETWEEN TO_TIMESTAMP('2013-09-29','yyyy-mm-dd') AND TO_TIMESTAMP('2013-10-06','yyyy-mm-dd')
GROUP BY table_id )maxResults
JOIN myTable1
ON maxResults.table_id = myTable1.table_id
WHERE myTable1.statusCode = 'C'
UNION ALL
SELECT myTable1.system as "system", TO_CHAR(myTable1.ts,'YYYY-MM-DD') as "ts", 'O' as "statusCode"
FROM myTable1
WHERE myTable1.ts BETWEEN TO_TIMESTAMP('2013-09-29','yyyy-mm-dd') AND TO_TIMESTAMP('2013-10-06','yyyy-mm-dd')
--AND myTable1.statusCode = 'O'
)Records
and
USING (system, ts, statusCode)
I tried just sticking in a LEFT JOIN in the middle of the two queries but didn't work (I am probably doing it wrong) as shown
EDIT: Added the JOIN and USING as example of what was not working, receiving "invalid table name"
This is a guess, assuming you want to join on all columns?
(SELECT * FROM (
SELECT system, ts, statuscode
FROM (SELECT Source as system, DT as ts, Status as statusCode
FROM ((SELECT 'SOURCE1' Source FROM Dual
UNION SELECT 'SOURCE2' FROM Dual
UNION SELECT 'SOURCE3' FROM Dual
UNION SELECT 'SOURCE4' FROM Dual
) system CROSS JOIN (SELECT TO_DATE('09-30-2013','MM-DD-YYYY') - 1 + LEVEL dt
FROM dual
CONNECT BY
LEVEL <= ( TO_DATE('10/05/2013','MM/DD/YYYY')
- TO_DATE('09/30/2013','MM/DD/YYYY')) + 1
) ts CROSS JOIN (SELECT 'O' Status FROM Dual
UNION SELECT 'C' FROM Dual) statusCode
)
))duals LEFT JOIN
(Select * FROM(
SELECT myTable1.system, TO_CHAR(maxResults.ts,'YYYY-MM-DD') as ts, myTable1.statusCode
FROM (
SELECT table_id, MAX(ts) as ts
FROM myTable1_history
WHERE ts BETWEEN TO_TIMESTAMP('2013-09-29','yyyy-mm-dd') AND TO_TIMESTAMP('2013-10-06','yyyy-mm-dd')
GROUP BY table_id )maxResults
JOIN myTable1
ON maxResults.table_id = myTable1.table_id
WHERE myTable1.statusCode = 'C'
UNION ALL
SELECT myTable1.system, TO_CHAR(myTable1.ts,'YYYY-MM-DD') as ts, 'O' as statusCode
FROM myTable1
WHERE myTable1.ts BETWEEN TO_TIMESTAMP('2013-09-29','yyyy-mm-dd') AND TO_TIMESTAMP('2013-10-06','yyyy-mm-dd')
--AND myTable1.statusCode = 'O'
) Records ON duals.system = records.system AND duals.ts = records.ts AND duals.statusCode = records.statusCode