String concatenation with GROUP BY - sql

Using Postgres, I want to convert the following records:
ID serviceaccess
11 Value1
11 Value2
11 Value3
22 Value2
22 Value1
22 Value1
Into this result:
ID
11 value1, value2, value3
22 value2, value1, value1
I can't use functions, because the system I am using doesn't support those.
I could do case statements though.
I also checked the following:
SQL Server : GROUP BY clause to get comma-separated values
I tried this, but it doesn't work:
WITH RecurRanked ( dbid, rnk, serviceaccess)
AS (
SELECT t.t1.dbid, t.t1.rnk, t.t2.serviceaccess || ', ' || t.t1.serviceaccess
FROM t.t1
INNER JOIN t.t2 ON t.t1.dbid = RecurRanked.dbid AND t.t1.rnk = RecurRanked.rnk + 1)
SELECT dbid, MAX(serviceaccess)
FROM RecurRanked
GROUP BY dbid;
SELECT t.t1.dbid, t.t1.rnk, t.t2.serviceaccess || ', ' || t.t1.serviceaccess
FROM t.t1
INNER JOIN t.t2 ON t.t1.dbid = t.t2.dbid AND t.t1.rnk = t.t2.rnk + 1

I don't fully understand what you mean by "But I can't use functions, because the system, I am using doesn't support those. I am using postgres SQL though".
You can use the string_agg aggregate function in PostgreSQL.
select ID, string_agg(serviceaccess, ',') group by ID;

It's really hard to do what you want using pure SQL. It could be useful the following (which is not the perfect solution):
(e.g.) to convert the following records:
id| serviceaccess
-----------------------
11|" "
11|"Value1"
11|"Value2"
22|" "
22|"Value1"
22|"Value2"
22|"Value3"
Tested in postgresql. Unfortunately it can not be supported on the DBMS you are using:
SELECT t1.id, (max( t1.serviceaccess || ',') ||
max( t2.serviceaccess || ',') ||
max( t3.serviceaccess || ',') ||
max( t4.serviceaccess || ',') ||
max( t5.serviceaccess)) as Services
FROM test as t1
INNER JOIN test as t2 ON t1.id =t2.id AND (t2.serviceaccess > t1.serviceaccess or
t1.serviceaccess = ' ')
INNER JOIN test as t3 ON t2.id =t3.id AND (t3.serviceaccess > t2.serviceaccess or
t2.serviceaccess = ' ')
INNER JOIN test as t4 ON t3.id =t4.id AND (t4.serviceaccess > t3.serviceaccess or
t3.serviceaccess = ' ')
INNER JOIN test as t5 ON t4.id =t5.id AND (t5.serviceaccess > t4.serviceaccess or
t4.serviceaccess = ' ')
GROUP BY t1.id
Result:
id| services
------------------------------
22| " , ,Value1,Value2,Value3"
11| " , , ,Value1,Value2"
kind regards!

Related

subquery eror and too many values using xmllagg

I have issue concatenating all of my data from many large tables. I asked question yesterday regarding this but unfortunately seems listagg is not good option. the link subquery return more than one row
I tried to use the xmllagg after listagg using to truncate is not possible with my version of oracle 12.0.1 , the first code as seen below is given subquery is returning more than one row...
SELECT rtrim(xmlagg(XMLELEMENT(e,table1.DESCRIPTION,',').EXTRACT ('//text()')
).GetClobVal(),',')
FROM table1
left join table2 on table1.app = table2.app
AND LANGUAGE = 2
GROUP BY table2.app
The second code one using another method is still saying too many values in first line
SELECT nvl(max(case when language = 2 then description end), 'NULL') key_event, rtrim(xmlagg(XMLELEMENT (e,table1.DESCRIPTION,',').EXTRACT ('//text()')
).GetClobVal(),',')
FROM table1
left join table2 on table1.app = table2.app
AND LANGUAGE = 2
GROUP BY table2.app
I have tested these 2 codes in this link dbfiddle and it was working. I want the result to be as this link
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=40852eaeaa8f334f77364eef77ffbe68
I did get result testing it as small bit but when i try to use the full code just look at this code, it is not working please see the code and error below error :subquery return more than one row
SELECT
NVL(TO_CHAR(D_TRANS.TRANS), 'NULL') AS ID,
'HEADER'
, (SELECT L_APPLICATION.APPLICATION FROM L_APPLICATION L_APPLICATION WHERE LANGUAGE = 2 AND APPLICATION = D_TRANS.APPLICATION) AS CASE_TYPE
, NVL(TO_CHAR(D_TRANS.UNIT_IN_CHARGE), 'NULL') AS UNIT_IN_CHARGE
, NVL(TO_CHAR(D_TRANS.PERSON_IN_CHARGE), 'NULL') AS PERSON_IN_CHARGE
, NVL(TO_CHAR(D_TRANS.STATUS), 'NULL') AS CASE_STATUS
, NVL(TO_CHAR(D_TRANS.DEADLINE), 'NULL') AS INTERNAL_DEADLINE
(SELECT D_SYNERGI_CATEGORY.TRANS, nvl(max(case when language = 2 then description end), 'NULL') AS ADE , rtrim(xmlagg(XMLELEMENT (e,L_CASE_CATEGORY.DESCRIPTION,',').EXTRACT('//text()')
).GetClobVal(),',')
FROM L_CASE_CATEGORY
left join D_SYNERGI_CATEGORY on D_SYNERGI_CATEGORY.CASE_CATEGORY = L_CASE_CATEGORY.CASE_CATEGORY
GROUP BY D_SYNERGI_CATEGORY.TRANS
)
)
FROM D_TRANS
FULL OUTER JOIN D_SPILL
ON D_TRANS.TRANS=D_SPILL.TRANS
ORDER BY D_TRANS.TRANS DESC;
When I tested this small bit of this code below it is working.
(SELECT D_SYNERGI_CATEGORY.TRANS, nvl(max(case when language = 2 then description end), 'NULL') AS ADE , rtrim(xmlagg(XMLELEMENT(e,L_CASE_CATEGORY.DESCRIPTION,',').EXTRACT('//text()')
).GetClobVal(),',')
FROM L_CASE_CATEGORY
left join D_SYNERGI_CATEGORY on D_SYNERGI_CATEGORY.CASE_CATEGORY = L_CASE_CATEGORY.CASE_CATEGORY
GROUP BY D_SYNERGI_CATEGORY.TRANS
)
I am not expert in Oracle. Any suggestion ?
Combination of xmlagg and xQuery, it's not simple.
select xmlquery('distinct-values(//text())' passing x returning content).getclobVal(),data_type from (
select xmlelement(root, xmlagg(XMLELEMENT(e,table_name,','))) x ,data_type
from user_tab_cols where data_type in ('VARCHAR2','NUMBER')
group by data_type
)
And for you purpose it should look like this
select
app, key_event,
xmlquery('distinct-values(//text())' passing xmldoc returning content).getclobVal()
from
(select
t2.app,
coalesce(max(case when language = 2 then description end),
max(case when language = 12 then description end),
max(case when language = 27 then description end),
'NULL') key_event,
XMLELEMENT(root, xmlagg(XMLELEMENT(e, description, ','))
) xmldoc
from
table2 t2
left join
table1 t1 on t1.app = t2.app
group by
trans, t2.app
order by trans);
if you will decompose this query you will see how its work.
xquery distinct-values syntax
xmlquery syntax
with REGEXP_REPLACE we can make deduplication. Check this:
select t2.app,
coalesce(max(case when language = 2 then description end),
max(case when language = 12 then description end),
max(case when language = 27 then description end),
'NULL') key_event,
REGEXP_REPLACE(listagg(description, ',') within group (order by t1.description, t1.language), '([^,]+)(,\1)+', '\1') list
from table2 t2
left join table1 t1 on t1.app = t2.app
group by trans, t2.app
order by trans, trans;

Regarding one query error

Here is my Requirement when I am using the below query I am getting the correct response but the problem is I want to select the distinct records so please help me how can I use distinct in the below query
SELECT LISTAGG(PAC.DESCRIPTION || ' = '|| ORL.ITEM_PACKAGE_COUNT , ',') WITHIN GROUP (ORDER BY PAC.DESCRIPTION || ' = '|| ORL.ITEM_PACKAGE_COUNT)
FROM ORDER_RELEASE_LINE ORL , PACKAGED_ITEM PAC , SHIPMENT SH , ORDER_MOVEMENT OM
WHERE ORL.PACKAGED_ITEM_GID = PAC.PACKAGED_ITEM_GID
AND OM.ORDER_RELEASE_GID = ORL.ORDER_RELEASE_GID
AND OM.SHIPMENT_GID = SH.SHIPMENT_GID
AND SH.SHIPMENT_GID = 'ULA/SAO.5000072118'
Your subquery returns SELECT DISTINCT PAC.DESCRIPTION but outer query uses aliases and values from inner query LISTAGG(PAC.DESCRIPTION || ' = '|| ORL.ITEM_PACKAGE_COUNT , ',') ORL.ITEM_PACKAGE_COUNT is not returned by subquery. Try:
SELECT LISTAGG(SUBQ.DESCRIPTION || ' = '|| SUBQ.ITEM_PACKAGE_COUNT , ',')
WITHIN GROUP (ORDER BY SUBQ.DESCRIPTION || ' = '|| SUBQ.ITEM_PACKAGE_COUNT)
FROM (SELECT DISTINCT PAC.DESCRIPTION, ORL.ITEM_PACKAGE_COUNT
FROM ORDER_RELEASE_LINE ORL , PACKAGED_ITEM PAC , SHIPMENT SH , ORDER_MOVEMENT OM
WHERE ORL.PACKAGED_ITEM_GID = PAC.PACKAGED_ITEM_GID
AND OM.ORDER_RELEASE_GID = ORL.ORDER_RELEASE_GID
AND OM.SHIPMENT_GID = SH.SHIPMENT_GID
AND SH.SHIPMENT_GID = 'ULA/SAO.5000072118') SUBQ
Generally it is wrong practice to use same alias PAC in inner query for a table and in outer query for result of joined data. Another wrong practice is using implicit joins instead of defining explicit INNER JOIN ON
If you need to get distinct values from a query, and then build the LISTAGG of these distinct values, you can simply use DISTINCT in your query and wrap it with an external one where you use LISTAGG.
For example:
with dupValTab(s) as
(
select 'something' from dual union all
select 'something else' from dual union all
select 'something' from dual
)
select listagg(s, ', ') within group (order by s)
from (
select distinct s
from dupValTab
)

Oracle sql join against extracted values

I am looking to reconcile data from 2 different tables where I need to carry out a concatenation and substr to create columns that I can use to carry out a match against.The following separate queries reflect the select statements from each table that produces the matching values that reflect sitenn.zonenn (e.g. site12.zone20) as nodename.
SELECT distinct(REGEXP_SUBSTR(B.NODE_NAME,'*site*.*')) as nodename
FROM OPC_ACT_MESSAGES A,OPC_NODE_NAMES B
WHERE A.MESSAGE_GROUP = 'Ebts_Status_Alarms'
AND A.SEVERITY <> 2
AND A.NODE_ID = B.NODE_ID;
SELECT 'site'||site_id||'.zone'||zone_id as nodename
FROM aw_active_alarms
GROUP BY site_id,zone_id;
I need to write a query that select all nodenames from one table that do not exist in the other.
Use left join to find it. It is faster than minus,not in,not exists etc.
SELECT a.nodename
FROM (SELECT DISTINCT( regexp_substr(B.node_name, '*site*.*') ) AS nodename
FROM opc_act_messages A,
opc_node_names B
WHERE A.message_group = 'Ebts_Status_Alarms'
AND A.severity <> 2
AND A.node_id = B.node_id
) a
LEFT JOIN
(SELECT 'site'
|| site_id
|| '.zone'
|| zone_id AS nodename
FROM aw_active_alarms
GROUP BY site_id,
zone_id
) b
ON a.nodename = b.nodename
WHERE b.nodename IS NULL
One simple way: use MINUS
SELECT distinct(REGEXP_SUBSTR(B.NODE_NAME,'*site*.*')) as nodename
FROM OPC_ACT_MESSAGES A,OPC_NODE_NAMES B
WHERE A.MESSAGE_GROUP = 'Ebts_Status_Alarms'
AND A.SEVERITY <> 2
AND A.NODE_ID = B.NODE_ID
MINUS
SELECT 'site'||site_id||'.zone'||zone_id as nodename
FROM aw_active_alarms
GROUP BY site_id,zone_id;
would this work?
WITH t1
AS (SELECT DISTINCT
(REGEXP_SUBSTR (B.NODE_NAME, '*site*.*')) AS nodename
FROM OPC_ACT_MESSAGES A, OPC_NODE_NAMES B
WHERE A.MESSAGE_GROUP = 'Ebts_Status_Alarms'
AND A.SEVERITY <> 2
AND A.NODE_ID = B.NODE_ID),
t2
AS ( SELECT 'site' || site_id || '.zone' || zone_id AS nodename
FROM aw_active_alarms
GROUP BY site_id, zone_id)
SELECT *
FROM t1
WHERE t1.nodename NOT IN (SELECT nodename FROM t2)

SQL - Select A only if B doesn't exist

I have this SQL statement (modified, because the real query is huge):
select tblInfo.IDNum, tblAddress.PrimaryAddress
from tblInfo
join tblAddress
on tblInfo.Agent = tblAddress.Agent
where (some stuff)
And I get a table that looks roughly like this:
|| IDNum || PrimaryAddress ||
-----------------------------
|| 01234 || 1 ||
|| 23456 || 1 ||
|| abcde || 0 ||
|| abcde || 1 ||
|| zyxwv || 0 ||
I need a way to return all records that have a PrimaryAddress of 1, as well as all records that have a PrimaryAddress of 0 and don't have an IDNum already returning the PrimaryAddress of 1. i.e. In the above example, (abcde || 0) should be excluded because (abcde || 1) exists.
Use NOT EXISTS
SELECT tblInfo.IDNum, tblAddress.PrimaryAddress
FROM tblInfo
INNER JOIN tblAddress
ON tblInfo.Agent = tblAddress.Agent
WHERE tblAddress.PrimaryAddress = 1
OR ( tblAddress.PrimaryAddress = 0 AND NOT EXISTS
(
SELECT 1 FROM tblInfo t2 INNER JOIN tblAddress a2 ON t2.Agent = a2.Agent
WHERE t2.IDNum = tblInfo.IDNum AND a2.PrimaryAddress = 1
)
)
In this case, a simple GROUP BY should work for what you are trying to do. Effectively you are saying you want all IDNum values to appear once, with the PrimaryAddress value corresponding to the highest value (1 if it exists, 0 if it doesn't).
Assuming you need to preserve your original query because you're doing other work with it, you could use:
SELECT IDNum, MAX(PrimaryAddress) AS PrimaryAddress
FROM
(
select tblInfo.IDNum, tblAddress.PrimaryAddress
from tblInfo
join tblAddress
on tblInfo.Agent = tblAddress.Agent
where (some stuff)
)
GROUP BY IDNum
This should work in MS SQL Server and Oracle, not sure about other DBMSs. If the nested query doesn't work in the DBMS you're using, you should be able to populate a temporary table with the results of your first query, then perform the grouping against that table.
I hope this helps
select * from tblAddress mainTBL
where mainTBL.primaryaddress = (Case when EXISTS(select t1.IDNUM from tblAddress t1 where
mainTBL.IDNUM = t1.IDNUM AND t1.primaryAddress = 1) THEN 1 ELSE 0 END)
Check the SQL fiddle

trying to concatenate a column into a comma delimited list

i have 3 tables, 1 for products and one for categories the products are assigned to. what IM trying to do is concatenate the column called stCategoryName to a single column in a comma delimited list.
Basically I have the products table containing the primary key for each product and im trying to figure out how to concatenate all the stcategoryName column next to each product so i can have a simplified return
what im trying to get is the following.
stProductID stCategoryName
123 category1,category2,category3
SELECT
dbo.StoreItemTracking.StCategoryID,
dbo.StoreItemTracking.StProductID,
dbo.StoreItemTracking.viewOrder,
dbo.StoreCategories.StCategoryName,
dbo.Store_Products.PartNumber
FROM
dbo.StoreItemTracking
INNER JOIN dbo.StoreCategories
ON dbo.StoreItemTracking.StCategoryID = dbo.StoreCategories.StCategoryID
INNER JOIN dbo.Store_Products
ON dbo.StoreItemTracking.StProductID = dbo.Store_Products.ID
Im stuck as to how to concatenate a column where the query contains 3 tables to select from.
any help greatly appreciated
Look at using coalesce to turn category into a CSV:
See example:
DECLARE #EmployeeList varchar(100)
SELECT #EmployeeList = COALESCE(#EmployeeList + ', ', '')
+ CAST(Emp_UniqueID AS varchar(5))
FROM SalesCallsEmployees
WHERE SalCal_UniqueID = 1
SELECT #EmployeeList
You can also use CTE's or Subqueries. See:
http://archive.msdn.microsoft.com/SQLExamples/Wiki/View.aspx?title=createacommadelimitedlist
Another nice and easy example:
http://www.codeproject.com/Articles/21082/Concatenate-Field-Values-in-One-String-Using-CTE-i
This:
FId FName
--- ----
2 A
4 B
5 C
6 D
8 E
with:
;WITH ABC (FId, FName) AS
(
SELECT 1, CAST('' AS VARCHAR(8000))
UNION ALL
SELECT B.FId + 1, B.FName + A.FName + ', '
FROM (And the above query will return
SELECT Row_Number() OVER (ORDER BY FId) AS RN, FName FROM tblTest) A
INNER JOIN ABC B ON A.RN = B.FId
)
SELECT TOP 1 FName FROM ABC ORDER BY FId DESC
becomes:
FName
----------------------------
A, B, C, D, E,
Don't understand how your products and categories are connected but in general I do like this to create comma separated lists.
SELECT table1.Id
,Csv
FROM table1
CROSS APPLY (
-- Double select so we can have an alias for the csv column
SELECT (SELECT ',' + table2.Name
FROM table2
WHERE table2.Id = table1.Id
FOR XML PATH('')
) AS RawCsv
) AS CA1
CROSS APPLY (
-- Trim the first comma
SELECT RIGHT(RawCsv, LEN(RawCsv) - 1) AS Csv
) AS CA2