Oracle/SQL - Multiple Records Into One [string aggregation] - sql

I realize this is a ridiculous request, but what I'm trying to do is pull multiple records back into a single column along with some literal text.
So given a table like this
REGION CITY SID
-------------------
1 Chicago 1234
1 Palatine 567
1 Algonquin 234
1 Wauconda 987
I would like to see back a single record with a column, other columns like region are fine, but a single column like this
<option value="1234">Chicago</option><option value="567">Palatine</option><option value="234">Algonquin</option><option value="987">Wauconda</option>
Any thoughts on how to do this? I'm running Oracle 9i and cannot do this in PL/SQL
Okay the table format has changed a bit, but the idea is the same
COUNTRY STORECODE STORE_NAME
------------------------------
USA 1234 Chicago
USA 567 Palatine
CAN 987 Toronto
So I found this code going through the links listed
SELECT COUNTRY,
LTRIM(MAX(SYS_CONNECT_BY_PATH(STORECODE,','))
KEEP (DENSE_RANK LAST ORDER BY curr),',') AS COUNTRY_HTML
FROM (SELECT COUNTRY,
STORECODE,
ROW_NUMBER() OVER (PARTITION BY COUNTRY ORDER BY STORECODE) AS curr,
ROW_NUMBER() OVER (PARTITION BY COUNTRY ORDER BY STORECODE) -1 AS prev
FROM tablename)
GROUP BY COUNTRY
CONNECT BY prev = PRIOR curr AND COUNTRY = PRIOR COUNTRY
START WITH curr = 1;
And when I run it I see this output
COUNTRY COUNTRY_HTML
--------------------
USA 1234,567
CAN 987
My thought was simply to have the inner select pull from another select where I do my concat of the STORECODE and STORE_NAME along with the html required like this...
SELECT COUNTRY,
LTRIM(MAX(SYS_CONNECT_BY_PATH(RECORD_HTML,','))
KEEP (DENSE_RANK LAST ORDER BY curr),',') AS COUNTRY_HTML
FROM (SELECT COUNTRY,
RECORD_HTML,
ROW_NUMBER() OVER (PARTITION BY COUNTRY ORDER BY RECORD_HTML) AS curr,
ROW_NUMBER() OVER (PARTITION BY COUNTRY ORDER BY RECORD_HTML) -1 AS prev
FROM (SELECT COUNTRY, '<option value="' || STORECODE || '">' || STORE_NAME || '</option>' AS RECORD_HTML FROM tablename))
GROUP BY COUNTRY
CONNECT BY prev = PRIOR curr AND COUNTRY = PRIOR COUNTRY
START WITH curr = 1;
While our front end environment does accept the query when I try to review the results I get a error: the resource is invalid. You may need to re-create of fix the query before viewing.
I know that error probably isn't helpful, but any ideas why my version isn't working?
Thanks!

It's disgusting but you could do something like this:
select replace(blah2,',','')
from ( select wm_concat(blah) as blah2
from ( select '<option value="' || sid || '">' || city || '</option>' as blah
from my_table
)
)

Have you played around with DBMS_XMLGEN?

You can create an aggregate function in Oracle, see documentations.

Related

How to display result of max function with calculation? [SqlPlus]

I use SqlPlus.
There is a lot of solutions and examples out there for related problems, but I haven't been able to fix my issue.
Expected result: 1 line that gives information about a library member that borrowed a book for the longest time. (displaying the amount of time: ex. Johnson John has ...: 31 days)
My current query:
SELECT DISTINCT m.firstname || ' ' || m.lastname || ' has borrowed for the longest time: ' || ROUND(MAX(l.date_of_return - l.date_borrowed)) || ' days' "Longest time borrowed"
FROM loans l
JOIN members m
ON l.memberid = m.memberid
WHERE l.date_of_return - l.date_borrowed = (SELECT MAX(date_of_return - date_borrowed) FROM loans)
/
Tables used:
LOANS:
Name Null? Type
----------------------------------------------------- -------- ------------------------------------
ISBN NOT NULL VARCHAR2(20)
SERIAL_NO NOT NULL NUMBER(2)
DATE_BORROWED NOT NULL DATE
DATE_OF_RETURN DATE
MEMBERID NOT NULL VARCHAR2(11)
EXTEND VARCHAR2(5)
MEMBERS:
Name Null? Type
----------------------------------------------------- -------- ------------------------------------
MEMBERID NOT NULL VARCHAR2(11)
LASTNAME NOT NULL VARCHAR2(20)
FIRSTNAME VARCHAR2(20)
Error:
ERROR at line 1: ORA-00937: not a single-group group function
I think I'm overlooking a simple solution. Thanks in advance.
Try
SELECT m.firstname || ' ' || m.lastname || ' has borrowed for the longest time: ' || ROUND(l.date_of_return - l.date_borrowed) || ' days' "Longest time borrowed"
FROM loans l
JOIN members m
ON l.memberid = m.memberid
WHERE l.date_of_return - l.date_borrowed = (SELECT MAX(l2.date_of_return - l2.date_borrowed) FROM loans l2)
AND ROWNUM <=1
You might want to avoid the additional calculation based comparison outside of the inner select (which would likely end up as an additional loop over loans during execution significantly stretching the run time).
It should be possible to collect the id inside the inner select as well as the calculation result and use it outside.
Try something like this:
SELECT m.firstname, m.lastname, b.maxtime
FROM members m, loans l
INNER JOIN (
SELECT li.memberid id, MAX(li.date_of_return - li.date_borrowed) maxtime
FROM loans li
GROUP BY li.memberid
) b ON m.memberid = b.id
ORDER BY b.maxtime
BTW: There's a pretty good post covering similar topics here (just in case you haven't found this one while searching), which might contain some interesting ideas for what you're trying to do: SQL select only rows with max value on a column

Extra Data from Oracle SQL Subquery(and sub, sub query)

I have the below query/subquery. I am trying to get the CIty, State, and Zipcode from the record in the AOD table and the First and Last from the Employee Original Date table. Can anyone provide guidance on how to extract the data form the sub query? Thank you!
SELECT eod.FIRST_NAME
,eod.LAST_NAME
,eod.SSN
,aodf.SSN
,aodf.CITY
,aodf.STATE
,aodf.ZipCode
FROM EMPLOYEE_ORIG_DATE eod
JOIN
(SELECT aod.ORIG_DATE
,aod.SSN
,aod.ADDRESS_KEY
,aod.Address_1 as AddressLine1
,aod.Address_2 as AddressLine2
,aod.City
,aod.State
,aod.Zip as ZipCode
,aod.Country as CountryCode
,aod.Telephone as HomeNumber
FROM ADDRESS_ORIG_DATE aod
INNER JOIN
(SELECT SSN, MAX(ORIG_DATE) ORIG_DATE
FROM ADDRESS_ORIG_DATE
GROUP BY SSN) aod2
ON aod.SSN = aod2.SSN
AND aod.ORIG_DATE = aod2.ORIG_DATE) aodf
ON eod.SSN = aodf.SSN
AND eod.ADRESS_KEY = aodf.ADDRESS_KEY
WHERE EMPLOYEE_ORIG_DATE.P_COMPANY_ID_I = 3149
If you ask for rewriting to simplify the query, then you may consider using DENSE_RANK() analytic function to include all the matching ties for returning records :
SELECT CITY, STATE, ZipCode, FIRST_NAME, LAST_NAME
FROM
(
SELECT eod.FIRST_NAME,
eod.LAST_NAME,
eod.SSN,
aod.SSN,
aod.CITY,
aod.STATE,
aod.ZipCode,
DENSE_RANK() OVER ( PARTITION BY SSN ORDER BY ORIG_DATE DESC ) ORIG_DATE
FROM EMPLOYEE_ORIG_DATE eod
JOIN ADDRESS_ORIG_DATE aod
ON eod.SSN = aod.SSN
AND eod.ADRESS_KEY = aod.ADDRESS_KEY
WHERE P_COMPANY_ID_I = 3149
)
WHERE ORIG_DATE = 1
where PARTITION BY replaces GROUP BY, and ORIG_DATE DESC replaces MAX(ORIG_DATE)
If you just want the most recent row from ADDRESS_ORIG_DATE for each SSN in EMPLOYEE_ORIG_DATE, then I would expect window functions.
I am guessing you just want one row, even if there are duplicates on the most recent date, so I recommend ROW_NUMBER():
SELECT eod.*, aod.*
FROM EMPLOYEE_ORIG_DATE eod JOIN
(SELECT aod.*,
ROW_NUMBER() OVER (PARTITION BY aod.SSN ORDER BY ORIG_DATE DESC) as seqnum
FROM ADDRESS_ORIG_DATE aod
) aod
ON aod.SSN = eod.SSN
WHERE aod.seqnum = 1 AND
eod.P_COMPANY_ID_I = 3149

Update fields in a SQL Server table sequentially

I have a table AoObject with a column DisplayName. In name are values like Joe Smith, John Smith, Jane Smith. This goes on for thousands of records.
I want to update the table so that starting with record one up to the last record the value of DisplayName is like this. Joe Smith = Customer1, John Smith = Customer2, Jane Smith = Customer3... and so on until all the name columns sequentially say Customer with a number appended to it and not the current value of the column.
This is SQL Server 2012
***The examples below don't work and I've amended my question. My fault for not including enough details. The table is called Aoobject. The field that needs to be changed is actually called DisplayName. I need to filter the result set with something like the following.
Where ObjectDescription in ('Portfolio Description', 'Portfolio Group Description')
This seems like a bad idea -- the "customer number" should be put into a separate column. It is not part of the name. However, it is easy to do:
with toupdate as (
select t.*, row_number() over (order by recordnum) as seqnum
from table t
)
update toupdate
set fullname = fullname + ' Customer' + cast(seqnum as varchar(255));
I am assuming that you have some numbering for the records (recordnum) because the question says "with record one up to the last record".
EDIT:
If you want the names to be unique, then append a number only for duplicate names.
with toupdate as (
select t.*,
row_number() over (partition by fullname order by recordnum) as fn_seqnum,
count(*) over (partition by fullname) as fn_cnt
from table t
)
update toupdate
set fullname = fullname + ':' + cast(fn_seqnum as varchar(255));
where fn_cnt > 1;
This appends a unique number only when it needs to (for fullnames that have duplicates). And, it keeps the cardinality of the number as low as possible, so only one digit should suffice for the suffix.
Something like this will do.
Hope the Customer name is unique.
;WITH cte_emp AS
(
SELECT CustomerName , ROW_NUMBER() over (order by CustomerName) slno
FROM Customer
)
UPDATE t SET CustomerName = 'Customer ' + cast(slno as varchar)
FROM Customer t
INNER JOIN cte_emp c ON t.CustomerName = c.CustomerName

I need a sql query to group by name but return other fields based on the most recent entry

I'm querying a single table called PhoneCallNotes. The caller FirstName, LastName and DOB are recorded for each call as well as many other fields including a unique ID for the call (PhoneNoteID) but no unique ID for the caller.
My requirement is to return a list of callers with duplicates removed along with the PhoneNoteID, etc from their most recent entry.
I can get the list of users I want using a Group By on name, DOB and Max(CreatedOn) but how do I include uniqueID (of the most recent entry in the results?)
select O.CallerFName,O.CallerLName,O.CallerDOB,Max(O.CreatedOn)
from [dbo].[PhoneCallNotes] as O
where O.CallerLName like 'Public'
group by O.CallerFName,O.CallerLName,O.CallerDOB order by Max(O.CreatedOn)
Results:
John Public 4/4/2001 4/6/12 16:42
Joe Public 4/12/1988 4/6/12 16:52
John Public 1/2/1950 4/6/12 17:01
Thanks
You can also write what Andrey wrote somewhat more compactly if you select TOP (1) WITH TIES and put the ROW_NUMBER() expression in the ORDER BY clause:
SELECT TOP (1) WITH TIES
CallerFName,
CallerLName,
CallerDOB,
CreatedOn,
PhoneNoteID
FROM [dbo].[PhoneCallNotes]
WHERE CallerLName = 'Public'
ORDER BY ROW_NUMBER() OVER(
PARTITION BY CallerFName, CallerLName, CallerDOB
ORDER BY CreatedOn DESC
)
(By the way, there's no reason to use LIKE for a simple string comparison.)
Try something like that:
;WITH CTE AS (
SELECT
O.CallerFName,
O.CallerLName,
O.CallerDOB,
O.CreatedOn,
PhoneNoteID,
ROW_NUMBER() OVER(PARTITION BY O.CallerFName, O.CallerLName, O.CallerDOB ORDER BY O.CreatedOn DESC) AS rn
FROM [dbo].[PhoneCallNotes] AS O
WHERE
O.CallerLName LIKE 'Public'
)
SELECT
CallerFName,
CallerLName,
CallerDOB,
CreatedOn,
PhoneNoteID
FROM CTE
WHERE rn = 1
ORDER BY
CreatedOn
Assuming that the set of [FirstName, LastName, DateOfBirth] are unique (#shudder#), I believe the following should work, on pretty much every major RDBMS:
SELECT a.callerFName, a.callerLName, a.callerDOB, a.createdOn, a.phoneNoteId
FROM phoneCallNotes as a
LEFT JOIN phoneCallNotes as b
ON b.callerFName = a.callerFName
AND b.callerLName = a.callerLName
AND b.callerDOB = a.callerDOB
AND b.createdOn > a.createdOn
WHERE a.callerLName LIKE 'Public'
AND b.phoneNoteId IS NULL
Basically, the query is looking for every phone-call-note for a particular name/dob combination, where there is not a more-recent row (b is null). If you have two rows with the same create time, you'll get duplicate rows, though.

SQL help: select the last 3 comments for EACH student?

I have two tables to store student data for a grade-school classroom:
Behavior_Log has the columns student_id, comments, date
Student_Roster has the columns student_id, firstname, lastname
The database is used to store daily comments about student behavior, and sometimes the teacher makes multiple comments about a student in a given day.
Now let's say the teacher wants to be able to pull up a list of the last 3 comments made for EACH student, like this:
Jessica 7/1/09 talking
Jessica 7/1/09 passing notes
Jessica 5/3/09 absent
Ciboney 7/2/09 great participation
Ciboney 4/30/09 absent
Ciboney 2/22/09 great participation
...and so on for the whole class
The single SQL query must return a set of comments for each student to eliminate the human-time-intensive need for the teacher to run separate queries for each student in the class.
I know that this sounds similar to
SQL Statement Help - Select latest Order for each Customer but I need to display the last 3 entries for each person, I can't figure out how to get from here to there.
Thanks for your suggestions!
A slightly modified solution from this article in my blog:
Analytic functions: SUM, AVG, ROW_NUMBER
SELECT student_id, date, comment
FROM (
SELECT student_id, date, comment, (#r := #r + 1) AS rn
FROM (
SELECT #_student_id:= -1
) vars,
(
SELECT *
FROM
behavior_log a
ORDER BY
student_id, date DESC
) ao
WHERE CASE WHEN #_student_id <> student_id THEN #r := 0 ELSE 0 END IS NOT NULL
AND (#_student_id := student_id) IS NOT NULL
) sc
JOIN Student_Roster sr
ON sr.student_id = sc.student_id
WHERE rn <= 3
A different approach would be to use the group_concat function and a single sub select and a limit on that subselect.
select (
select group_concat( concat( student, ', ', date,', ', comment ) separator '\n' )
from Behavior_Log
where student_id = s.student_id
group by student_id
limit 3 )
from Student_Roster s