DB2 subquery not working using IN statement SQLCODE 115 - db2

I'm trying to execute a query in DB2. But it throws following error:
Error: DB2 SQL Error: SQLCODE=-115, SQLSTATE=42601, SQLERRMC=IN, DRIVER=4.8.86
SQLState: 42601
ErrorCode: -115
Error: DB2 SQL Error: SQLCODE=-514, SQLSTATE=26501, SQLERRMC=SQL_CURSH200C1; STMT0001, DRIVER=4.8.86
SQLState: 26501
ErrorCode: -514
Which does'nt make sense as my query looks correct:
SELECT ROW_NUMBER() OVER() AS ID,
CONCAT(TRIM(TB1.ROW1),CONCAT('_',TRIM(TB1.ROW2))) AS CODE_DESCRIPTION,
CASE
WHEN TRIM(TB1.ROW1) IN (SELECT T1.ROW1 FROM DB1.TABLE1 T1 WHERE T1.ROW3 = 'TEST')
THEN 'Valid'
ELSE 'Invalid'
END,
TB1.* FROM DB1.TABLE1 TB1
WHERE TB1.ROW3 = 'CLASS1';
SQLCode 115 means Comparison is invalid. Which is not ?
Update:
What I'm trying to accomplish here is. I have a Table Table1(Name changed for simplicity). Following is the part of the content.
**Row3** **Row1** **Row2**
KSASPREM SRQ 0 0 Auto Carry SRQ
KSASPREM SCG 0 0 BRT Buses SCG
KSASPREM SCE 0 0 Buses SCE
KSASPREM SRR 0 0 Buses SRR
KSASPREM SDC 0 0 Domestic All Risks SDC
KSASPREM SDA 0 0 Domestic Buildings SDA
Task to accomplish:
Retrieve all the values from Table1 where Row3 is KSASPREM
The result should contain one extra column 'Valid' value Yes/No if value of Row1 is not in the Values retrieved from Table1 where Row3 is 'TEST'
Hope I made myself clear and not more confusing ?
Any Help ?
Thanks
Ps. Updated the Query

As with so many things, a JOIN (here, LEFT JOIN) is the answer. Specifically, we need to put the (slightly modified) subquery as the table reference:
LEFT JOIN (SELECT DISTINCT row1, 'Valid' as valid
FROM Table1
WHERE row3 = 'TEST') AS Test
ON Test.row1 = TB1.row1
LEFT JOIN tells the query engine that "rows in this other table aren't required".
DISTINCT says, "for all value combinations in these columns, give me just one row"
Using a constant value - 'Valid' - returns that constant value.
... so this gets us a (virtual, temp) table containing unique row1 entries where row3 = 'test'.
Here's the full query:
SELECT ROW_NUMBER() OVER(ORDER BY TB1.row1) AS ID,
TRIM(TB1.ROW1) || '_' || TRIM(TB1.ROW2) AS CODE_DESCRIPTION,
COALESCE(Test.valid, 'Invalid') AS valid,
TB1.row3, TB1.row1, TB1.row2
FROM Table1 TB1
LEFT JOIN (SELECT DISTINCT row1, 'Valid' as valid
FROM Table1
WHERE row3 = 'TEST') Test
ON Test.row1 = TB1 .row1
WHERE TB1.ROW3 = 'KSASPREM'
SQL Fiddle Example
COALESCE(...) returns the first non-null value encountered in the value list. Since, if there is no Test row, Test.valid will be null, this outputs 'Invalid' for TB1 rows without a corresponding Test row. (Internally it's calling CASE, I believe, this just makes it prettier)
Note that:
I've put an ORDER BY into the OVER clause, to return (mostly) consistent results. If you only ever plan on running this once it doesn't matter, but if you need to run it multiple times and get consistent IDs, you'll need to use something that won't be shuffled.
DB2 (and apparently PostgreSQL) support || as a concat operator. It makes reading statements so much easier to understand.
Never use SELECT *, it isn't safe for several reasons. Always specify which columns you want.

Related

Full outer join not showing unmatched rows - SQL

I am trying to do a full outer join of two tables, matching them with the "PO Product Code" of table 1 and "Product Code" of table 2. Each time I attempt to do this, there are a few product codes that are left out:
CABSCABS0000, DOORINTD0015, FLORCARP0001, EXCL0001, and FLORTILE0000
Each of these product codes are in table 2, but not in table 1.
Here are the screenshots of the tables I am working with.
https://i.imgur.com/pFAfrAb.png
https://i.imgur.com/k0a3o57.png
And here is the code that I have tried:
SELECT cost.[Actual Close]
,cost.[Project Name]
,cost.[Lot]
,cost.[Model]
,cost.[Elev]
,cost.[PO Product Code]
,cost.[Invoiced + Open] AS 'Invoiced + Open'
,rev.[Gross Sale] AS 'Gross Sale'
FROM Table1 cost FULL OUTER JOIN
Table2 rev
ON rev.[Product Code] = cost.[PO Product Code]
WHERE rev.[Project Name] = cost.[Project Name] AND
rev.[Lot] = cost.[Lot];
I must also mention that the Product Code is specific to each Project Name and Lot, which is why that is added to the 'WHERE' clause.
Here is the output I got, which is missing the 5 craft codes listed above.
https://i.imgur.com/EoMFc9v.png
The FULL JOIN returns NULL values in columns for rows that do not match in either of the tables. Your WHERE clause filters these out -- because NULL comparisons almost never evaluate to "true" (the exception is IS NULL and NULL-safe comparisons).
What you want to do is move these conditions to the ON clause. That is where they belong anyway, because they are really JOIN conditions:
FROM Table1 cost FULL OUTER JOIN
Table2 rev
ON rev.[Product Code] = cost.[PO Product Code] AND
rev.[Project Name] = cost.[Project Name] AND
rev.[Lot] = cost.[Lot];
Its because of your where clause
rev.[Project Name] = cost.[Project Name]
AND rev.[Lot] = cost.[Lot];
Since those items that you mentioned are not in table A but are in table B, you are basically removing them via WHERE clause.
If you want those 5 Items to show, try removing the where clause, then run the query again.

array_agg DISTINCT and ORDER

I'm trying to make a query in PostgreSQL for include results from 2 (or more) tables using left join lateral, and I need to have one record for each record for table entidad_a_ (main table) and all the records from table entidad_b_ must be included in one field generated by array_agg. And in this array, I have to delete duplicate elements and I have to preserve order array in main table.
I need to execute this SQL query:
SELECT entidad_a_._id_ AS "_id", CASE WHEN count(entidadB) > 0 THEN array_agg(DISTINCT entidadB._id,ordinality order by ordinality)
ELSE NULL END AS "entidadB"
FROM entidad_a_ as entidad_a_, unnest(entidad_a_.entidad_b_) WITH ORDINALITY AS u(entidadb_id, ordinality)
LEFT JOIN LATERAL (
SELECT entidad_b_3._id_ AS "_id", entidad_b_3.label_ AS "label"
FROM entidad_b_ as entidad_b_3
WHERE entidad_b_3._id_ = entidadb_id
GROUP BY entidad_b_3._id_
LIMIT 1000 OFFSET 0
) entidadB ON TRUE
GROUP BY entidad_a_._id_
LIMIT 1000 OFFSET 0
But I have errors....
How can I have these results?
Edited:
My error is:
ERROR: function array_agg (integer, bigint) does not exist
SQL state: 42883
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Character: 69
If the query is:
......array_agg (DISTINCT entidadB._id order by ordinality).....
The eror is:
ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list
SQL state: 42P10
Character: 110
My problem is the combination of array_agg, DISTINCT, and ORDER by
Solved!! I've created a postgres extension with a custom aggregation.
CREATE AGGREGATE array_agg_dist (anyelement)
(
sfunc = array_agg_transfn_dist,
stype = internal,
finalfunc = array_agg_finalfn_dist,
finalfunc_extra
);
Creating functions and c code for this custom functions.

How to check an ascending ordered column value in where clause in postgresql?

I am new to postgresql. I want to join two tables if one geometry of first table is contained by the geometry of second table. So, I have written and executed this part of the query as following and it is running fine.
select edge.start_id, cls.gid
from edge_table edge
inner join cluster_info cls on st_contains(cls.geom,st_setsrid(edge.start_geom,3067));
But it is giving the start_id and its containing geom id (as mentioned cls.gid in the query) in a random order such as following:
start_id gid
26040 2493
43323 2490
26208 2400
42754 2433
43537 2434
1379 2434
43570 2904
42887 2475
43689 2495
43211 2904
But I need to insert the result in another column named start_cls in my edge table. I need to identify the row where the cls.gid should be inserted. So, I need to check the value of start_id for each row and the cls.gid corresponding to that start_id should be put in that row. Assume, four rows of my edge table are following:
gid start_id end_id start_geom end_geom start_cls end_cls
1 81608 81608 01010000007368912D8B622341E5D022EBEAF65A41 01010000007368912D8B622341E5D022EBEAF65A41
2 81557 81520 010100000085EB51F89C0723418B6CE7DB9F8E5A41 0101000000986E1203DE0723416DE7FB51A38E5A41
3 189898 80812 01010000006F1283C0A093214179E926F1A1005B41 0101000000BE9F1A6FF3942141022B871EEC005B41
4 80952 80476 0101000000666666E67F832341F2D24DBA38B45A41 0101000000736891EDB48423413BDF4F755AB45A41
I need to fill the start_cls column first. So, the cls.gid value of 81608 (first start_id) should be there at first row under start_cls column. So, I have given one where clause as following:
select edge.start_id, cls.gid
from edge_table edge
inner join cluster_info cls on st_contains(cls.geom,st_setsrid(edge.start_geom,3067))
where (select start_id from edge_table) = edge.start_id;
But, it is giving following error:
ERROR: more than one row returned by a subquery used as an expression
********** Error **********
ERROR: more than one row returned by a subquery used as an expression
SQL state: 21000
I tried with the following query too but no luck.
select edge.start_id, cls.gid
from edge_table edge
inner join cluster_info cls on st_contains(cls.geom,st_setsrid(edge.start_geom,3067))
where (select start
from (select start_id as start
from edge_table) as s) = edge.start_id;
Please help with this query. It has some geometry part but the main problem is in postgresql query organisation. So, I have raised this question in stackoverflow instead of gis.stackexchange.

How to convert oracle hierarchical queries to postgresql?

I want to convert below mentioned oracle hierarchical query to postgresql
SELECT catalog_id, sub_tree_id
FROM my_catalog
CONNECT BY PRIOR catalog_id = sub_tree_id;
I have tried using the following postgresql query but not getting the expected result
WITH RECURSIVE q AS (
SELECT po.catalog_id,po.sub_tree_id
FROM my_catalog po
UNION ALL
SELECT po.catalog_id,po.sub_tree_id
FROM my_catalog po
JOIN q ON q.catalog_id=po.sub_tree_id
)
SELECT * FROM q;
ORACLE OUTPUT(EXPECTED RESULT)
POSTGRESQL OUTPUT(ACTUAL RESULT)
In PostgreSQL recursive queries are constructed by first specifying the initial set of rows (the non-recursive term, i.e. those at the root or final level of the hierarchy). Subsequent iterations (over the recursive term, the sub-query after the UNION ALL) then add rows to the result set from the remaining rows in the input row set until no more rows are added.
In your case, the initial sub-query is unfiltered so you simply add all rows on the initial run, leaving nothing for subsequent runs.
Try the following:
WITH RECURSIVE q AS (
SELECT po.catalog_id,po.sub_tree_id
FROM my_catalog po
WHERE sub_tree_id = 0 -- this initially selects only "root" rows
UNION ALL
SELECT po.catalog_id,po.sub_tree_id
FROM my_catalog po
JOIN q ON q.catalog_id=po.sub_tree_id
)
SELECT * FROM q;

How can I query 'between' numeric data on a not numeric field?

I've got a query that I've just found in the database that is failing causing a report to fall over. The basic gist of the query:
Select *
From table
Where IsNull(myField, '') <> ''
And IsNumeric(myField) = 1
And Convert(int, myField) Between #StartRange And #EndRange
Now, myField doesn't contain numeric data in all the rows [it is of nvarchar type]... but this query was obviously designed such that it only cares about rows where the data in this field is numeric.
The problem with this is that T-SQL (near as I understand) doesn't shortcircuit the Where clause thus causing it to ditch out on records where the data is not numeric with the exception:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the nvarchar value '/A' to data type int.
Short of dumping all the rows where myField is numeric into a temporary table and then querying that for rows where the field is in the specified range, what can I do that is optimal?
My first parse purely to attempt to analyse the returned data and see what was going on was:
Select *
From (
Select *
From table
Where IsNull(myField, '') <> ''
And IsNumeric(myField) = 1
) t0
Where Convert(int, myField) Between #StartRange And #EndRange
But I get the same error I did for the first query which I'm not sure I understand as I'm not converting any data that shouldn't be numeric at this point. The subquery should only have returned rows where myField contains numeric data.
Maybe I need my morning tea, but does this make sense to anyone? Another set of eyes would help.
Thanks in advance
IsNumeric only tells you that the string can be converted to one of the numeric types in SQL Server. It may be able to convert it to money, or to a float, but may not be able to convert it to an int.
Change your
IsNumeric(myField) = 1
to be:
not myField like '%[^0-9]%' and LEN(myField) < 9
(that is, you want myField to contain only digits, and fit in an int)
Edit examples:
select ISNUMERIC('.'),ISNUMERIC('£'),ISNUMERIC('1d9')
result:
----------- ----------- -----------
1 1 1
(1 row(s) affected)
You'd have to force SQL to evaluate the expressions in a certain order.
Here is one solution
Select *
From ( TOP 2000000000
Select *
From table
Where IsNumeric(myField) = 1
And IsNull(myField, '') <> ''
ORDER BY Key
) t0
Where Convert(int, myField) Between #StartRange And #EndRange
and another
Select *
From table
Where
CASE
WHEN IsNumeric(myField) = 1 And IsNull(myField, '') <> ''
THEN Convert(int, myField) ELSE #StartRange-1
END Between #StartRange And #EndRange
The first technique is "intermediate materialisation": it forces a sort on a working table.
The 2nd relies on CASE ORDER evaluation is guaranteed
Neither is pretty or whizzy
SQL is declarative: you tell the optimiser what you want, not how to do it. The tricks above force things to be done in a certain order.
Not sure if this helps you, but I did read somewhere that incorrect conversion using CONVERT will always generate error in SQL. So I think it would be better to use CASE in where clause to avoid having CONVERT to run on all rows
Use a CASE statement.
declare #StartRange int
declare #EndRange int
set #StartRange = 1
set #EndRange = 3
select *
from TestData
WHERE Case WHEN ISNUMERIC(Value) = 0 THEN 0
WHEN Value IS NULL THEN 0
WHEN Value = '' THEN 0
WHEN CONVERT(int, Value) BETWEEN #StartRange AND #EndRange THEN 1
END = 1