Oracle SQL: Missing identifier error in Select with Xmlagg - sql

I am new to Oracle and hope someone here can help me with this.
I have a Select that returns the following without row aggregation:
current output
My problem here is that I can have multiple rows for certain IDs in the first column whereas I need just one row per ID, like this:
required output
Select Distinct is not an option in my case and Listag doesn't allow enough characters for the second column.
After some research I think Xmlagg is exactly what I need here but I cannot get this to work and always get an error here so I think I am writing it wrong.
Latest error:
ORA-00931: missing identifier
Can someone show me how to write this properly ?
My query (shortened):
ALTER SESSION ENABLE PARALLEL QUERY;
SELECT
a.Id
, RTRIM(XMLAGG(XMLELEMENT("Details",
(
b.title || ' -' || c.item || ' -' || b.quantity) ORDER BY b.title)
).EXTRACT('//text()'), ' --- ') AS Details
, TO_CHAR(c.total, 'FM9,990.00') AS Sum
FROM
table1 d
/* joins */
WHERE
/* ... */
GROUP BY
a.Id
, b.title
, c.item
, b.quantity
ORDER BY
a.Id
Many thanks in advance.
Mike

Just a few things need to be moved here and there.
SELECT a.Id ,
RTRIM(XMLAGG(XMLELEMENT("Details", b.title
|| ' -'
|| c.item
|| ' -'
|| b.quantity ,' --- ' ).EXTRACT('//text()')
ORDER BY b.title),
' --- ')
AS
Details , TO_CHAR(c.total, 'FM9,990.00')
AS
SUM FROM table1 d
/* joins */
WHERE
* ... */
GROUP BY
a.Id
, b.title
, c.item
, b.quantity
ORDER BY
a.Id

Related

Dynamic column fields using existing column values in SQL

I have this existing query
Select
mt.First_name,
mt.Last_name as OLD_Last_name,
ot.Last_name as New_Last_name,
ot.Date as Update_Date,
from maintable as mt
JOIN othertable as ot on mt.id=ot.id
I'd like to join a new column with the following output:
[mt.First_name] [ot.Last_name], nee [mt.Last_name] changed their name on [ot.Date].
I tried using a case statement but didn't get it right.
For closure, moving #Jnevill answer from comment to actual answer:
SELECT mt.First_name || ' ' || ot.Last_name || ', nee ' || mt.last_name || ' changed their name on ' || ot.Date AS yournewcolumn, mt.First_name
, mt.Last_name as OLD_Last_name
, ot.Last_name as New_Last_name
, ot.Date as Update_Date
from maintable as mt
JOIN othertable as ot on mt.id=ot.id
Apparently OP wanted to know how to concatenate strings, which is done with ||.

How to take multiple rows and make a comma separated list in SQL

I have a query I ran which is
SELECT * FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
It gives me the below output.
What I would like to know is how can I change this query in order to display everything except the first class_id column in a comma separated list. Essentially I would like to get an output of class_idand value,value,value,value,value,value,value,value as my two columns in the output.
I am a college student just learning SQL and have not been exposed to any possible solutions to this problem in class. Any help is appreciated.
Most databases support a function such as string_agg() or listagg() that does what you want:
SELECT cl.class_id,
STRING_AGG(ability_id, ',') as ability_ids,
. . .
FROM rpg.class_primary_abilities cpab JOIN
rpg.abilities ab
ON cpab.ability_id = ab.ability_id JOIN
rpg.classes cl
ON cpab.class_id = cl.class_id
GROUP BY cl.class_id
Please tag your database for more info.
This is one way for Oracle where Ihave used a concatenate sign which is || to concatenate(put two strings together in one) and between them I have also concatenated a comma ,. You can also see that I have used double quotes for the column named desc. I did it because it is not a good practice to call your columns with keywords and word desc is used for example when you order by some column(at the end of the query) you can order by that column ascending then you use asc or descending when you can use desc. Also in both examples I used keyword as to give a name to this concatenated column.
SELECT class_id, cpab.ability_id || ',' ||
ab.ability_id || ',' ||
ab.name || ',' ||
class_id || ',' ||
cpab.name || ',' ||
hit_die || ',' ||
"desc" || ',' ||
isPlayable as values
FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
This is another for MYSQL where I have used concat to concatenate column values and I have used different single quotes for desc column.:
SELECT class_id, concat(cpab.ability_id, ',' ,
ab.ability_id, ',' ,
ab.name, ',' ,
class_id, ',' ,
cpab.name, ',' ,
hit_die, ',' ,
`desc`, ',' ,
isPlayable) as values
FROM rpg.class_primary_abilities AS cpab
INNER JOIN rpg.abilities AS ab ON cpab.ability_id = ab.ability_id
INNER JOIN rpg.classes AS cl ON cpab.class_id = cl.class_id;
In both examples you have columns with same name from different tables and theer you will have to use aliases when calling them in your select clause like I have did in my example: cpab.ability_id and ab.ability_id but please note that I do not know if they are from cpab and ab tables.

SQL query to find matches

I have written a query to provide matches with the same DB and it's giving me expected results except that I don't get few part of it. Below is the query :
select f.name, f.id, f.industry, d.name, d.id, d.industry
from product_table f, product_table d
where (f.name like '%' || d.name || '%') and
(f.industrylike '%' || d.industry|| '%') and
I know by providing this it's actually looking for matches between the 2 columns :
(..... like '%' || ..... || '%')
But what does each part of it do exactly and what does it mean?
This query is executing a self-join (here, a cross self-join) in which we query two instances of the same table for some purpose. In this case it looks like some form of data quality exercise, where we suspect we might have almost duplicate records. That is, we think we have records for the same combination of (product name and industry). The use of wild cards will identify records where the value of one column is wholly embedded in another column: for instance '%STACK%' matches 'META STACKOVERFLOW'.
The posted version has a potential flaw, in that if there are two records with an exact match you will get two hits (one for F:D, one for D:F). You can finagle that by adding a filter on id
select f.name, f.id, f.industry,
d.name, d.id, d.industry
from product_table f, product_table d
where (f.name like '%' || d.name || '%')
and (f.industrylike '%' || d.industry|| '%')
and ( ( f.name = d.name
and f.industry = d.industry
and f.id < d.id )
or f.name != d.name
or f.industry != d.industry
)
The double vertical bar (more commonly known as a pipe) is the concatenation operator. It is used for joining strings together. (Many programming languages use + but Oracle reserves that strictly for arithmetic on numbers.)
not so much clear on why we put it before and after only the second column : f.name like '%' || d.name || '%'
In this case, the query is concatenating a wild card. Given this value for f.name = 'XYZ' , we would get matches for '%' || d.name || '%' on:
'1XYZ1'
'11XYZ11'
'11XYZ'
'XYZ1'
'XYZ' <---- matching same record
We don't need to wrap f.name in wildcard operators because the query is a self-join so all the values of name will appear on the left hand side of the filter. When f.name = '1XYZ1' it match for '%' || d.name || '%' on:
'1XYZ1' <---- matching same record
'XYZ1'
'XYZ'
So you're going to get multiple hits already. Embedding both sides of the filter in wildcards will only generate more noisy duplicates.

How to replace NULL value in select with subquery

Hi I'm performing a left join on two tables. If a particular column is NULL I want to run a subquery to get a value from a completely different table. Here's what I have now:
SELECT A.ACCOUNT_NUM, A.USER_ID,
CASE B.PREFERRED_NAME
WHEN '' THEN RTRIM(B.FIRST_NAME) || ' ' || B.LAST_NAME
ELSE RTRIM(B.PREFERRED_NAME) || ' ' || B.LAST_NAME
END AS NAME
FROM TABLE_A A
LEFT JOIN TABLE_B B
ON A.USER_ID = B.USER_ID
TABLE_B sometimes doesn't contain a record that matches with TABLE_A, so I want to run a subquery from TABLE_C that contains usernames and will match on A.USER_ID.
I thought I could do something like:
CASE B.PREFERRED_NAME
WHEN NULL THEN subquery here
But I get this error:
ERROR [42703] [IBM][DB2] SQL0206N "NULL" is not valid in the context where it is used.
Probably because NULLs are not allowed for that column.
SOLVED
Thanks for the help. This is how I solved my issue:
SELECT A.ACCOUNT_NUM, A.USER_ID,
CASE
WHEN B.PREFERRED_NAME IS NULL THEN C.USER_ID
WHEN B.PREFERRED_NAME IS NOT NULL THEN
CASE PREFERRED_NAME
WHEN '' THEN RTRIM(B.FIRST_NAME) || ' ' || B.LAST_NAME
ELSE RTRIM(B.PREFERRED_NAME) || ' ' || B.LAST_NAME
END
END AS NAME
FROM TABLE_A A
LEFT JOIN TABLE_B B
ON A.USER_ID = B.USER_ID
JOIN TABLE_C C
ON A.USER_ID = C.USER_ID
Depending on your query, you can probably just add your third table as another LEFT JOIN, then add the column you want to a COALESCE function:
Also, it looks like you're storing the preferred name as spaces if there isn't one, in which case you can use the NULLIF function to convert it to a NULL, which will work with your COALESCE.
Here's an example of what I mean:
SELECT
A.ACCOUNT_NUM
,A.USER_ID
,COALESCE(
NULLIF(B.PREFERRED_NAME,'')
,B.FIRST_NAME
,C.OTHER_NAME
) || ' ' || B.LAST_NAME AS NAME
FROM TABLE_A A
LEFT JOIN TABLE_C C
ON C.USER_ID = A.USER_ID
LEFT JOIN TABLE_B B
ON A.USER_ID = B.USER_ID
If you know there is always going to be a row in C that matches A, then you could convert that to a regular (inner) JOIN.
The reason you're getting the error, though is because you can't use NULL like that in a CASE statement. If you want to have a NULL case, then you have to do it like #Abecee said in the comment with CASE WHEN B.PREFERRED_NAME IS NULL THEN ...

Find all tables with a field containing xml string values

I have an SQL 2005 database and I know that in the database there is a table which has got some xml strings in it. How can I find this table(s)?
If the fields are actually of type XML, then this query will give you what you're looking for:
select * from information_schema.columns
where DATA_TYPE = 'XML'
Marc
Run this:
select 'select distinct ''' || a.name || '.' || b.name
|| ''' from ' || b.name
|| 'where ' || b.name || ' like ''%<%/>%'' union '
from systable a
join syscolumns b on (a.id = b.id)
join systypes c on (b.type = c.xtype)
where a.type ='U' and c.name = ('CHAR', 'CHARN', 'VARCHAR', 'VARCHARN');
The first result set will have one row per character column in the database:
select distinct 'table.column' from table where column like '%<%/>%' union
Take that resultset, snip off the last union, and run the resultset as a SQL statement. It'll bring back the table name and column name for any column that has one or more rows that look XML-ish.
Edit: this is from memory; the join to systypes and the type names may be wrong, so select from systypes and check.